package ui import ( "fmt" "image/color" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/text" "muhq.space/muhqs-game/go/font" "muhq.space/muhqs-game/go/game" ) var ( TEXTBOX_DEFAULT_BACKGROUND = gray TEXTBOX_DEFAULT_FOREGROUND = color.White TEXTBOX_DEFAULT_MARGIN = 3 ) type TextBox struct { WidgetBase text string Fg color.Color Bg color.Color XMargin int YMargin int Center bool } func NewFixedTextBox(x, y int, width, height int, t string, center bool) *TextBox { tb := &TextBox{ WidgetBase: NewWidgetBase(x, y, width, height), text: t, Fg: TEXTBOX_DEFAULT_FOREGROUND, Bg: TEXTBOX_DEFAULT_BACKGROUND, XMargin: TEXTBOX_DEFAULT_MARGIN, YMargin: TEXTBOX_DEFAULT_MARGIN, Center: center, } tb.renderImpl = func() *ebiten.Image { return tb.render() } return tb } func NewCenteringFixedTextBox(x, y int, width, height int, t string) *TextBox { return NewFixedTextBox(x, y, width, height, t, true) } func NewAutoTextBox(x, y int, t string) *TextBox { return NewFixedTextBox(x, y, -1, -1, t, false) } func NewCenteringAutoTextBox(x, y int, t string) *TextBox { return NewFixedTextBox(x, y, -1, -1, t, true) } func NewUnitInfo(x, y int, u *game.Unit) *TextBox { info := fmt.Sprintf("%s\nDamage: %d\nHealth: %d\nUpkeep: %d", 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 !game.INVALID_ATTACK(u.Attack) { 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 NewPermInfo(x, y int, p game.Permanent) *TextBox { info := fmt.Sprintf("%s\nDamage: %d", p.String(), p.Damage()) return NewAutoTextBox(x, y, info) } func (tb *TextBox) render() *ebiten.Image { var x, y int b := text.BoundString(font.Font24, tb.text) if tb.Width == -1 || tb.Height == -1 { tb.Width = b.Dx() + 2*tb.XMargin tb.Height = b.Dy() + 2*tb.YMargin x = tb.XMargin + b.Min.X y = tb.YMargin - b.Min.Y } else { if !tb.Center { x = tb.XMargin + b.Min.X } else { x = tb.Width/2 - centerTextXOffset(b) } y = tb.Height/2 - centerTextYOffset(b) } img := ebiten.NewImage(tb.Width, tb.Height) img.Fill(tb.Bg) text.Draw(img, tb.text, font.Font24, x, y, tb.Fg) return img } type PocList struct { TextBox centerX int bottomY int poc game.PileOfCards } func NewPocList(centerX, bottomY int, poc game.PileOfCards) *PocList { w := &PocList{ TextBox: TextBox{ WidgetBase: NewWidgetBase(-1, -1, -1, -1), text: "", Fg: TEXTBOX_DEFAULT_FOREGROUND, Bg: TEXTBOX_DEFAULT_BACKGROUND, XMargin: TEXTBOX_DEFAULT_MARGIN, YMargin: TEXTBOX_DEFAULT_MARGIN, Center: true, }, centerX: centerX, bottomY: bottomY, poc: poc, } w.renderImpl = func() *ebiten.Image { w.setText() return w.render() } return w } func (w *PocList) setText() { 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" } b := text.BoundString(font.Font24, w.text) w.X = w.centerX - b.Dx()/2 - w.XMargin w.Y = w.bottomY - b.Dy() - w.YMargin } func (w *PocList) FindObjectAt(x, y int) interface{} { cards := w.poc.Cards() if len(cards) == 0 { return nil } // b := text.BoundString(font.Font, w.Text) // i := b.Dy() / w.poc.Size() return cards[0] } type TextInput struct { TextBox input string label string } func NewTextInput(x, y int, width, height int, label string) *TextInput { w := &TextInput{*NewCenteringFixedTextBox(x, y, width, height, ""), "", label} w.renderImpl = func() *ebiten.Image { if w.input == "" { w.text = w.label } else { w.text = w.input } return w.render() } return w } func (ti *TextInput) AddInput(input []rune) { ti.input += string(input) ti.ForceRedraw() } func (ti *TextInput) HandleKey() { if inpututil.IsKeyJustPressed(ebiten.KeyBackspace) { if len(ti.input) > 0 { ti.input = ti.input[:len(ti.input)-1] ti.ForceRedraw() } } } func (ti *TextInput) Text() string { return ti.input }