aboutsummaryrefslogtreecommitdiff
path: root/go/ui
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2023-01-20 03:18:06 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2025-01-27 16:43:44 +0100
commitc4cb9637f5c85d03aa162968f0dfec3c193fc9dc (patch)
tree3ad32e5dfd7c4ae72f66b86e77edef7b700a8b7a /go/ui
parent9df5cc7b55d8ac9ecd775a14a14f80a6c36c4d74 (diff)
downloadmuhqs-game-c4cb9637f5c85d03aa162968f0dfec3c193fc9dc.tar.gz
muhqs-game-c4cb9637f5c85d03aa162968f0dfec3c193fc9dc.zip
intermediate commit
Implement actions and multiple ui widgets
Diffstat (limited to 'go/ui')
-rw-r--r--go/ui/buffer.go81
-rw-r--r--go/ui/button.go53
-rw-r--r--go/ui/choice.go68
-rw-r--r--go/ui/colors.go7
-rw-r--r--go/ui/font.go24
-rw-r--r--go/ui/mapView.go43
-rw-r--r--go/ui/textBox.go67
-rw-r--r--go/ui/widget.go10
8 files changed, 337 insertions, 16 deletions
diff --git a/go/ui/buffer.go b/go/ui/buffer.go
new file mode 100644
index 00000000..cc9f2377
--- /dev/null
+++ b/go/ui/buffer.go
@@ -0,0 +1,81 @@
+package ui
+
+import (
+ "image/color"
+
+ "github.com/hajimehoshi/ebiten/v2"
+ "github.com/hajimehoshi/ebiten/v2/text"
+)
+
+var (
+ BUFFER_BACKGROUND = gray
+ BUFFER_FOREGROUND = color.White
+)
+
+type Buffer struct {
+ X, Y int
+ Width, Height int
+ img *ebiten.Image
+ lines []string
+ pos int
+}
+
+func NewBuffer(x, y int, width, height int) *Buffer {
+ return &Buffer{x, y, width, height, nil, []string{}, 0}
+}
+
+func (b *Buffer) render() {
+ img := ebiten.NewImage(b.Width, b.Height)
+ img.Fill(BUFFER_BACKGROUND)
+ y := 0
+ for _, line := range b.lines[b.pos:] {
+ bounds := text.BoundString(Font, line)
+ y += bounds.Dy()
+ if y > b.Height {
+ break
+ }
+ text.Draw(img, line, Font, 0, y, BUFFER_FOREGROUND)
+ }
+ b.img = img
+}
+
+func (b *Buffer) Draw(screen *ebiten.Image) {
+ if b.img == nil {
+ b.render()
+ }
+ op := &ebiten.DrawImageOptions{}
+ op.GeoM.Translate(float64(b.X), float64(b.Y))
+ screen.DrawImage(b.img, op)
+}
+
+func (b *Buffer) FindObjectAt(x, y int) interface{} {
+ return nil
+}
+
+func (b *Buffer) ForceRedraw() {
+ b.img = nil
+}
+
+func (b *Buffer) AddLines(lines []string) {
+ b.lines = append(b.lines, lines...)
+ b.ForceRedraw()
+}
+
+func (b *Buffer) AddLine(line string) {
+ b.AddLines([]string{line})
+}
+
+func (b *Buffer) ClearLines() {
+ b.lines = []string{}
+ b.ForceRedraw()
+}
+
+func (b *Buffer) RemoveLast() {
+ b.lines = b.lines[:len(b.lines)-1]
+ b.ForceRedraw()
+}
+
+func (b *Buffer) Remove(idx int) {
+ b.lines = append(b.lines[:idx], b.lines[idx+1:]...)
+ b.ForceRedraw()
+}
diff --git a/go/ui/button.go b/go/ui/button.go
new file mode 100644
index 00000000..93180204
--- /dev/null
+++ b/go/ui/button.go
@@ -0,0 +1,53 @@
+package ui
+
+import (
+ "image/color"
+
+ "github.com/hajimehoshi/ebiten/v2"
+ "github.com/hajimehoshi/ebiten/v2/ebitenutil"
+ "github.com/hajimehoshi/ebiten/v2/text"
+)
+
+type Button interface {
+ Widget
+ Contains(x, y int) bool
+ Click(x, y int)
+}
+
+type SimpleButton struct {
+ X, Y int
+ Width, Height int
+ img *ebiten.Image
+ OnClick func(b *SimpleButton)
+}
+
+func NewSimpleButton(x, y, w, h int, onClick func(b *SimpleButton), label string) *SimpleButton {
+ img := ebiten.NewImage(w, h)
+
+ // b := text.BoundString(f, label)
+ ebitenutil.DrawRect(img, float64(0), float64(0), float64(w), float64(h), gray)
+ text.Draw(img, label, Font, 20, 25, color.White)
+
+ return &SimpleButton{x, y, w, h, img, onClick}
+}
+
+func (b *SimpleButton) Draw(screen *ebiten.Image) {
+ op := &ebiten.DrawImageOptions{}
+ op.GeoM.Translate(float64(b.X), float64(b.Y))
+ screen.DrawImage(b.img, op)
+}
+
+func (b *SimpleButton) Click(x, y int) {
+ b.OnClick(b)
+}
+
+func (b *SimpleButton) Contains(x, y int) bool {
+ return x >= b.X && x <= b.X+b.Width && y >= b.Y && y <= b.Y+b.Height
+}
+
+func (b *SimpleButton) FindObjectAt(x, y int) interface{} {
+ if b.Contains(x, y) {
+ return b
+ }
+ return nil
+}
diff --git a/go/ui/choice.go b/go/ui/choice.go
new file mode 100644
index 00000000..9e9b7b2f
--- /dev/null
+++ b/go/ui/choice.go
@@ -0,0 +1,68 @@
+package ui
+
+import (
+ "github.com/hajimehoshi/ebiten/v2"
+ "muhq.space/muhqs-game/go/game"
+)
+
+type Choice struct {
+ X, Y int
+ Width, choiceHeight int
+ choices []string
+ onClick func(*Choice, int, int)
+ buttons []Button
+}
+
+func choiceButtonOnClickDummy(b *SimpleButton) {}
+
+func NewChoice(x, y int, width, choiceHeight int, choices []string,
+ onClick func(*Choice, int, int)) *Choice {
+
+ c := &Choice{x, y, width, choiceHeight, choices, onClick, []Button{}}
+
+ for i, choice := range c.choices {
+ b := NewSimpleButton(c.X, c.Y+i*c.choiceHeight, c.Width, c.choiceHeight,
+ choiceButtonOnClickDummy, choice)
+
+ c.buttons = append(c.buttons, b)
+ }
+ return c
+}
+
+func (c *Choice) Draw(screen *ebiten.Image) {
+ for _, b := range c.buttons {
+ b.Draw(screen)
+ }
+}
+
+func (c *Choice) GetChoosen(x, y int) int {
+ return (y - c.Y) / c.choiceHeight
+}
+
+func (c *Choice) Click(x, y int) {
+ c.onClick(c, x, y)
+}
+
+func (c *Choice) Contains(x, y int) bool {
+ height := len(c.choices) * c.choiceHeight
+ return x >= c.X && x <= c.X+c.Width && y >= c.Y && y <= c.Y+height
+}
+
+func (c *Choice) FindObjectAt(x, y int) interface{} {
+ if c.Contains(x, y) {
+ return c
+ }
+ return nil
+}
+
+func NewActionChoice(x, y int, actions []game.ActionPrototype, onClick func(*Choice, int, int)) *Choice {
+ // TODO: use dynamic with width and height
+ width := 200
+ choiceHeight := 40
+ labels := make([]string, 0, len(actions))
+ for _, action := range actions {
+ labels = append(labels, action.String())
+ }
+
+ return NewChoice(x, y, width, choiceHeight, labels, onClick)
+}
diff --git a/go/ui/colors.go b/go/ui/colors.go
new file mode 100644
index 00000000..a45aa16b
--- /dev/null
+++ b/go/ui/colors.go
@@ -0,0 +1,7 @@
+package ui
+
+import (
+ "image/color"
+)
+
+var gray = color.RGBA{0x80, 0x80, 0x80, 0xff}
diff --git a/go/ui/font.go b/go/ui/font.go
new file mode 100644
index 00000000..065ac0d8
--- /dev/null
+++ b/go/ui/font.go
@@ -0,0 +1,24 @@
+package ui
+
+import (
+ "log"
+
+ "golang.org/x/image/font"
+ "golang.org/x/image/font/gofont/goregular"
+ "golang.org/x/image/font/opentype"
+)
+
+var Font font.Face
+
+func InitFont() {
+ tt, err := opentype.Parse(goregular.TTF)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ opts := &opentype.FaceOptions{Size: 24, DPI: 72}
+ Font, err = opentype.NewFace(tt, opts)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/go/ui/mapView.go b/go/ui/mapView.go
index 10929f68..c292d526 100644
--- a/go/ui/mapView.go
+++ b/go/ui/mapView.go
@@ -13,7 +13,7 @@ import (
)
const (
- PERMANENT_WIDTH int = 40
+ PERMANENT_WIDTH int = 40
PERMANENT_HEIGHT int = 40
TILE_WIDTH int = 50
@@ -21,15 +21,15 @@ const (
)
type MapView struct {
- gameState *game.State
- mapLayer *ebiten.Image
- permanentsLayer *ebiten.Image
- tileHighlights []game.Position
- permanentsHighlights []*game.Permanent
+ gameState *game.State
+ mapLayer *ebiten.Image
+ permanentsLayer *ebiten.Image
+ tileHighlights []game.Position
+ permanentsHighlights []game.Permanent
}
func NewMapView(g *game.State) *MapView {
- vw := MapView{g, nil, nil, []game.Position{}, []*game.Permanent{}}
+ vw := MapView{g, nil, nil, []game.Position{}, []game.Permanent{}}
return &vw
}
@@ -131,16 +131,27 @@ func (vw *MapView) drawPermanentsLayer(screen *ebiten.Image) {
vw.permanentsLayer = vw.newLayerImage()
for _, p := range vw.gameState.Permanents {
- permanentSymbol := assets.GetSymbol(strings.ToLower(p.Card.Name))
+ permanentSymbol := assets.GetSymbol(strings.ToLower(p.GetCard().Name))
// TODO: Implement generic symbols
if permanentSymbol == nil {
continue
}
+ t := p.GetTile()
+ x_px := t.Position.X*TILE_WIDTH + (TILE_WIDTH-PERMANENT_WIDTH)/2
+ y_px := t.Position.Y*TILE_HEIGHT + (TILE_HEIGHT-PERMANENT_HEIGHT)/2
+
op := &ebiten.DrawImageOptions{}
- x_px := p.Tile.Position.X * TILE_WIDTH + (TILE_WIDTH - PERMANENT_WIDTH) / 2
- y_px := p.Tile.Position.Y * TILE_HEIGHT + (TILE_HEIGHT - PERMANENT_HEIGHT) / 2
op.GeoM.Translate(float64(x_px), float64(y_px))
+ op.ColorM.ScaleWithColor(p.GetController().Color)
+
+ for _, h := range vw.permanentsHighlights {
+ if p == h {
+ op.ColorM.Scale(255, 0.5, 0.5, 1)
+ break
+ }
+ }
+
vw.permanentsLayer.DrawImage(permanentSymbol, op)
}
}
@@ -163,8 +174,8 @@ func (vw *MapView) FindObjectAt(x_px, y_px int) interface{} {
y := y_px / TILE_HEIGHT
// TODO: detect if a permanent or the containing tile was selected
- for _, u := range vw.gameState.Units {
- pos := u.Tile.Position
+ for _, u := range vw.gameState.Permanents {
+ pos := u.GetTile().Position
if pos.X == x && pos.Y == y {
return u
}
@@ -198,15 +209,15 @@ func (vw *MapView) ClearTileHighlights() {
vw.HighlightTiles([]*game.Tile{})
}
-func (vw *MapView) HighlightPermanents(permanents []*game.Permanent) {
+func (vw *MapView) HighlightPermanents(permanents []game.Permanent) {
vw.ForceRedraw()
vw.permanentsHighlights = permanents
}
-func (vw *MapView) HighlightPermanent(p *game.Permanent) {
- vw.HighlightPermanents([]*game.Permanent{p})
+func (vw *MapView) HighlightPermanent(p game.Permanent) {
+ vw.HighlightPermanents([]game.Permanent{p})
}
func (vw *MapView) ClearPermanentsHighlights() {
- vw.HighlightPermanents([]*game.Permanent{})
+ vw.HighlightPermanents([]game.Permanent{})
}
diff --git a/go/ui/textBox.go b/go/ui/textBox.go
new file mode 100644
index 00000000..e07440b5
--- /dev/null
+++ b/go/ui/textBox.go
@@ -0,0 +1,67 @@
+package ui
+
+import (
+ "fmt"
+ "image/color"
+
+ "github.com/hajimehoshi/ebiten/v2"
+ "github.com/hajimehoshi/ebiten/v2/text"
+ "muhq.space/muhqs-game/go/game"
+)
+
+var (
+ TEXTBOX_BACKGROUND = gray
+ TEXTBOX_FOREGROUND = color.White
+)
+
+type TextBox struct {
+ X, Y int
+ Width, Height int
+ img *ebiten.Image
+ text string
+}
+
+func NewFixedTextBox(x, y int, width, height int, t string) *TextBox {
+ tb := &TextBox{x, y, width, height, nil, t}
+ tb.render()
+ return tb
+}
+
+func NewAutoTextBox(x, y int, t string) *TextBox {
+ tb := &TextBox{x, y, -1, -1, nil, t}
+ tb.render()
+ return tb
+}
+
+func NewUnitInfo(x, y int, u *game.Unit) *TextBox {
+ info := fmt.Sprintf("%s\nDamage: %d\nHealth: %d\nMovement: %d\nAttack %s\nUpkeep: %d",
+ u.String(), u.GetDamage(), u.Health, u.Movement, u.Attack.String(), u.Upkeep)
+ return NewAutoTextBox(x, y, info)
+}
+
+func NewPermInfo(x, y int, p game.Permanent) *TextBox {
+ info := fmt.Sprintf("%s\nDamage: %d",
+ p.String(), p.GetDamage())
+ return NewAutoTextBox(x, y, info)
+}
+
+func (tb *TextBox) render() {
+ b := text.BoundString(Font, tb.text)
+ if tb.Width == -1 || tb.Height == -1 {
+ tb.Width = b.Dx()
+ tb.Height = b.Dy()
+ }
+ tb.img = ebiten.NewImage(tb.Width, tb.Height)
+ tb.img.Fill(TEXTBOX_BACKGROUND)
+ text.Draw(tb.img, tb.text, Font, b.Min.X, -b.Min.Y, TEXTBOX_FOREGROUND)
+}
+
+func (tb *TextBox) Draw(screen *ebiten.Image) {
+ op := &ebiten.DrawImageOptions{}
+ op.GeoM.Translate(float64(tb.X), float64(tb.Y))
+ screen.DrawImage(tb.img, op)
+}
+
+func (tb TextBox) FindObjectAt(x, y int) interface{} {
+ return nil
+}
diff --git a/go/ui/widget.go b/go/ui/widget.go
new file mode 100644
index 00000000..c8fb0816
--- /dev/null
+++ b/go/ui/widget.go
@@ -0,0 +1,10 @@
+package ui
+
+import (
+ "github.com/hajimehoshi/ebiten/v2"
+)
+
+type Widget interface {
+ Draw(screen *ebiten.Image)
+ FindObjectAt(x, y int) interface{}
+}