Added tooltip system.

This commit is contained in:
Daniel Schmidt 2023-05-09 12:30:38 +02:00
parent 504cab6d9a
commit a1e2f79948
3 changed files with 212 additions and 8 deletions

View File

@ -36,6 +36,7 @@ type Model struct {
inOpponentSelection bool
inEnemyView bool
animations []tea.Model
ctrlDown bool
event tea.Model
merchant tea.Model
@ -118,6 +119,24 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.selectedCard = lo.Clamp(m.selectedCard-1, 0, len(m.Session.GetFight().Hand)-1)
case tea.KeyRight:
m.selectedCard = lo.Clamp(m.selectedCard+1, 0, len(m.Session.GetFight().Hand)-1)
case tea.KeyCtrlDown:
m.ctrlDown = true
case tea.KeyCtrlU:
m.ctrlDown = false
}
// Show tooltip
if msg.String() == "x" {
for i := 0; i < m.Session.GetOpponentCount(game.PlayerActorID); i++ {
if m.zones.Get(fmt.Sprintf("%s%d", ZoneEnemy, i)).InBounds(m.LastMouse) {
cmds = append(cmds, root.TooltipCreate(root.ToolTip{
ID: "ENEMY",
Content: m.fightEnemyInspectTooltipView(),
X: m.LastMouse.X,
Y: m.LastMouse.Y,
}))
}
}
}
//
// Mouse
@ -126,6 +145,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.LastMouse = msg
if msg.Type == tea.MouseLeft {
// Kill all enemy tooltips
cmds = append(cmds, root.TooltipDelete("ENEMY"))
switch m.Session.GetGameState() {
case game.GameStateFight:
if m.zones.Get(ZoneEndTurn).InBounds(msg) {
@ -142,8 +164,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.zones.Get(fmt.Sprintf("%s%d", ZoneEnemy, i)).InBounds(msg) {
if msg.Type == tea.MouseLeft && m.selectedOpponent == i {
m = m.tryCast()
} else {
m.selectedOpponent = i
}
}
}
@ -408,6 +428,20 @@ func (m Model) fightCardViewHeight() int {
return m.Size.Height - m.fightEnemyViewHeight() - 1 - 4 - 4
}
func (m Model) fightEnemyInspectTooltipView() string {
enemy := m.Session.GetOpponents(game.PlayerActorID)[m.selectedOpponent]
intend := lipgloss.NewStyle().Bold(true).Underline(true).Foreground(style.BaseWhite).Render("Intend:") + "\n\n" + m.Session.GetActorIntend(enemy.GUID) + "\n\n"
status := lipgloss.NewStyle().Bold(true).Underline(true).Foreground(style.BaseWhite).Render("Status Effects:") + "\n\n" + strings.Join(lo.Map(enemy.StatusEffects.ToSlice(), func(guid string, index int) string {
return components.StatusEffect(m.Session, guid) + ": " + m.Session.GetStatusEffectState(guid)
}), "\n\n")
return lipgloss.NewStyle().Border(lipgloss.ThickBorder(), true).Padding(1, 2).BorderForeground(style.BaseRedDarker).Render(
lipgloss.NewStyle().Width(30).Render(intend + status),
)
}
func (m Model) fightEnemyInspectView() string {
enemy := m.Session.GetOpponents(game.PlayerActorID)[m.selectedOpponent]

132
ui/overlay/overlay.go Normal file
View File

@ -0,0 +1,132 @@
package overlay
import (
"bytes"
"strings"
"github.com/mattn/go-runewidth"
"github.com/muesli/ansi"
"github.com/muesli/reflow/truncate"
)
// Code borrowed and cut down from @mrusme and https://github.com/charmbracelet/lipgloss/pull/102
// Split a string into lines, additionally returning the size of the widest line.
func getLines(s string) (lines []string, widest int) {
lines = strings.Split(s, "\n")
for _, l := range lines {
w := ansi.PrintableRuneWidth(l)
if widest < w {
widest = w
}
}
return lines, widest
}
// PlaceOverlay places overlay on top of background.
func PlaceOverlay(x, y int, overlay, background string) string {
overlayLines, overlayWidth := getLines(overlay)
backgroundLines, backgroundWidth := getLines(background)
backgroundHeight := len(backgroundLines)
overlayHeight := len(overlayLines)
if overlayWidth >= backgroundWidth && overlayHeight >= backgroundHeight {
return overlay
}
x = clamp(x, 0, backgroundWidth-overlayWidth)
y = clamp(y, 0, backgroundHeight-overlayHeight)
var b strings.Builder
for i, backgroundLine := range backgroundLines {
if i > 0 {
b.WriteByte('\n')
}
if i < y || i >= y+overlayHeight {
b.WriteString(backgroundLine)
continue
}
pos := 0
if x > 0 {
left := truncate.String(backgroundLine, uint(x))
pos = ansi.PrintableRuneWidth(left)
b.WriteString(left)
if pos < x {
pos = x
}
}
overlayLine := overlayLines[i-y]
b.WriteString(overlayLine)
pos += ansi.PrintableRuneWidth(overlayLine)
right := cutLeft(backgroundLine, pos)
b.WriteString(right)
}
return b.String()
}
// cutLeft cuts printable characters from the left.
// This function is heavily based on muesli's ansi and truncate packages.
func cutLeft(s string, cutWidth int) string {
var (
pos int
isAnsi bool
ab bytes.Buffer
b bytes.Buffer
)
for _, c := range s {
var w int
if c == ansi.Marker || isAnsi {
isAnsi = true
ab.WriteRune(c)
if ansi.IsTerminator(c) {
isAnsi = false
if bytes.HasSuffix(ab.Bytes(), []byte("[0m")) {
ab.Reset()
}
}
} else {
w = runewidth.RuneWidth(c)
}
if pos >= cutWidth {
if b.Len() == 0 {
if ab.Len() > 0 {
b.Write(ab.Bytes())
}
if pos-cutWidth > 1 {
b.WriteByte(' ')
continue
}
}
b.WriteRune(c)
}
pos += w
}
return b.String()
}
func clamp(v, lower, upper int) int {
return min(max(v, lower), upper)
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}

View File

@ -4,6 +4,7 @@ import (
"github.com/BigJk/end_of_eden/game"
"github.com/BigJk/end_of_eden/ui"
"github.com/BigJk/end_of_eden/ui/menus/lua_error"
"github.com/BigJk/end_of_eden/ui/overlay"
tea "github.com/charmbracelet/bubbletea"
zone "github.com/lrstanley/bubblezone"
"github.com/samber/lo"
@ -17,21 +18,47 @@ func Push(model tea.Model) tea.Cmd {
}
}
type ToolTip struct {
ID string
Content string
X int
Y int
}
type ToolTipMsg ToolTip
func TooltipCreate(tip ToolTip) tea.Cmd {
return func() tea.Msg {
return ToolTipMsg(tip)
}
}
type ToolTipDeleteMsg string
func TooltipDelete(id string) tea.Cmd {
return func() tea.Msg {
return ToolTipDeleteMsg(id)
}
}
type Model struct {
zones *zone.Manager
stack []tea.Model
size tea.WindowSizeMsg
zones *zone.Manager
stack []tea.Model
size tea.WindowSizeMsg
tooltips map[string]ToolTip
}
func New(zones *zone.Manager, root tea.Model) Model {
return Model{
zones: zones,
stack: []tea.Model{root},
zones: zones,
stack: []tea.Model{root},
tooltips: map[string]ToolTip{},
}
}
func (m Model) PushModel(model tea.Model) Model {
m.stack = append(m.stack, model)
m.tooltips = map[string]ToolTip{}
return m
}
@ -56,6 +83,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg.String() == "ctrl+c" {
return m, tea.Quit
}
case ToolTipMsg:
m.tooltips[msg.ID] = ToolTip(msg)
case ToolTipDeleteMsg:
delete(m.tooltips, string(msg))
case PushModelMsg:
m = m.PushModel(msg)
}
@ -82,7 +113,14 @@ func (m Model) View() string {
if len(m.stack) == 0 {
return "stack empty!"
}
return m.zones.Scan(m.stack[len(m.stack)-1].View())
view := m.zones.Scan(m.stack[len(m.stack)-1].View())
for _, v := range m.tooltips {
view = overlay.PlaceOverlay(v.X, v.Y, v.Content, view)
}
return view
}
func CheckLuaErrors(zones *zone.Manager, s *game.Session) tea.Cmd {