diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2023-01-20 03:18:06 +0100 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-01-27 16:43:44 +0100 |
| commit | c4cb9637f5c85d03aa162968f0dfec3c193fc9dc (patch) | |
| tree | 3ad32e5dfd7c4ae72f66b86e77edef7b700a8b7a /go/ui | |
| parent | 9df5cc7b55d8ac9ecd775a14a14f80a6c36c4d74 (diff) | |
| download | muhqs-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.go | 81 | ||||
| -rw-r--r-- | go/ui/button.go | 53 | ||||
| -rw-r--r-- | go/ui/choice.go | 68 | ||||
| -rw-r--r-- | go/ui/colors.go | 7 | ||||
| -rw-r--r-- | go/ui/font.go | 24 | ||||
| -rw-r--r-- | go/ui/mapView.go | 43 | ||||
| -rw-r--r-- | go/ui/textBox.go | 67 | ||||
| -rw-r--r-- | go/ui/widget.go | 10 |
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{} +} |
