package ui import ( "fmt" "image/color" "strconv" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/text/v2" "muhq.space/muhqs-game/go/font" "muhq.space/muhqs-game/go/game" ) var ( TEXTBOX_DEFAULT_BACKGROUND = color.Black TEXTBOX_DEFAULT_FOREGROUND = color.White TEXTBOX_DEFAULT_MARGIN = 3 TEXTBOX_AUTO_DIMENSION = -1 ) type TextBox struct { WidgetBase text string fg color.Color bg color.Color font *text.GoTextFace lineSpacing float64 XMargin int YMargin int center bool } func NewFixedTextBox(x, y int, width, height int, t string) *TextBox { tb := &TextBox{ WidgetBase: NewWidgetBase(x, y, width, height), text: t, fg: TEXTBOX_DEFAULT_FOREGROUND, bg: TEXTBOX_DEFAULT_BACKGROUND, font: font.Font24, lineSpacing: 1.5, XMargin: TEXTBOX_DEFAULT_MARGIN, YMargin: TEXTBOX_DEFAULT_MARGIN, } tb.renderImpl = func() *ebiten.Image { return tb.render() } return tb } func NewAutoTextBox(x, y int, text string) *TextBox { return NewFixedTextBox(x, y, TEXTBOX_AUTO_DIMENSION, TEXTBOX_AUTO_DIMENSION, text) } func (tb *TextBox) Bg(bg color.Color) *TextBox { tb.bg = bg return tb } func (tb *TextBox) Fg(fg color.Color) *TextBox { tb.fg = fg return tb } func (tb *TextBox) Centering(centering bool) *TextBox { tb.center = centering return tb } // UpdateText displays the new text in the TextBox. func (tb *TextBox) UpdateText(new string) { tb.text = new tb.ForceRedraw() } func NewUnitInfo(x, y int, u *game.Unit) *TextBox { info := fmt.Sprintf("%s\n%s\n♡ %d/%d\nUpkeep: %d", u.Card().Name, u.String(), u.Damage(), u.Health, u.UpkeepCost()) if u.Movement != game.INVALID_MOVEMENT() { info = fmt.Sprintf("%s\nMovement: %s", info, u.Movement.String()) } if u.Attack.Valid() { info = fmt.Sprintf("%s\nAttack: %s", info, u.Attack.String()) } if len(u.Pile()) > 0 { info = fmt.Sprintf("%s\nPile:", info) for _, p := range u.Pile() { info = fmt.Sprintf("%s\n %s", info, p.Card().Name) } } return NewAutoTextBox(x, y, info) } func NewGenericPermInfo(x, y int, p game.Permanent) *TextBox { info := fmt.Sprintf("%s\n%s\nDamage: %d", p.Card().Name, p.String(), p.Damage()) return NewAutoTextBox(x, y, info) } func NewPermInfo(x, y int, p game.Permanent) *TextBox { switch p := p.(type) { case *game.Unit: return NewUnitInfo(x, y, p) default: return NewGenericPermInfo(x, y, p) } } func (tb *TextBox) render() *ebiten.Image { var x, y float64 w, h := text.Measure(tb.text, tb.font, tb.font.Size*tb.lineSpacing) if tb.Width == TEXTBOX_AUTO_DIMENSION || tb.Height == TEXTBOX_AUTO_DIMENSION { tb.Width = int(w) + 2*tb.XMargin tb.Height = int(h) + 2*tb.YMargin x = float64(tb.XMargin) y = float64(tb.YMargin) } else { if !tb.center { x = float64(tb.XMargin) } else { x = (float64(tb.Width) - w) / 2 } y = (float64(tb.Height) - h) / 2 } img := ebiten.NewImage(tb.Width, tb.Height) img.Fill(tb.bg) op := &text.DrawOptions{} op.GeoM.Translate(x, y) op.ColorScale.ScaleWithColor(tb.fg) op.LineSpacing = tb.font.Size * tb.lineSpacing text.Draw(img, tb.text, tb.font, op) return img } type PocList struct { TextBox hoverCardView centerX int bottomY int Poc game.PileOfCards } func NewPocList(centerX, bottomY int, poc game.PileOfCards, c *Collection) *PocList { w := &PocList{ TextBox: *(NewAutoTextBox(-1, -1, "").Centering(true)), centerX: centerX, bottomY: bottomY, Poc: poc, } w.hoverCardView.init(w, c) w.RegisterHandler("hover", w.hoverCardView.Hover) w.renderImpl = func() *ebiten.Image { w.setText() return w.render() } return w } func (w *PocList) setText() { w.Width, w.Height = -1, -1 t := "" for _, c := range w.Poc.Cards() { t = fmt.Sprintf("%s%s\n", t, c.Name) } if t != "" { w.text = t[:len(t)-1] } else { w.text = "No cards" } width, height := text.Measure(w.text, w.font, w.font.Size*w.lineSpacing) w.X = w.centerX - int(width/2) - w.XMargin w.Y = w.bottomY - int(height) - w.YMargin } func (w *PocList) FindObjectAt(_x, _y int) any { if !w.Contains(_x, _y) { return nil } cards := w.Poc.Cards() if len(cards) == 0 { return nil } y := _y - w.Y _, height := text.Measure(w.text, w.font, w.font.Size*w.lineSpacing) i := y / (int(height) / w.Poc.Size()) if i >= len(cards) { return nil } return cards[i] } type NumberInput struct { TextInput n int } func NewNumberInput(x, y int, height, width int, number int) *NumberInput { ni := &NumberInput{ n: number, } ni.InitTextInput(x, y, width, height, strconv.Itoa(number), false) return ni } func (ni *NumberInput) Add(x int) { ni.n = ni.n + x ni.SetInput(strconv.Itoa(ni.n)) } func (ni *NumberInput) Number() int { return ni.n }