diff options
Diffstat (limited to 'go/ui')
| -rw-r--r-- | go/ui/buffer.go | 4 | ||||
| -rw-r--r-- | go/ui/button.go | 14 | ||||
| -rw-r--r-- | go/ui/cardView.go | 46 | ||||
| -rw-r--r-- | go/ui/hand.go | 53 | ||||
| -rw-r--r-- | go/ui/mapView.go | 57 | ||||
| -rw-r--r-- | go/ui/stateBar.go | 40 | ||||
| -rw-r--r-- | go/ui/textBox.go | 133 | ||||
| -rw-r--r-- | go/ui/widget.go | 1 |
8 files changed, 289 insertions, 59 deletions
diff --git a/go/ui/buffer.go b/go/ui/buffer.go index e0e6b001..c451d7fc 100644 --- a/go/ui/buffer.go +++ b/go/ui/buffer.go @@ -31,12 +31,12 @@ func (b *Buffer) render() *ebiten.Image { img.Fill(BUFFER_BACKGROUND) y := 0 for _, line := range b.lines[b.pos:] { - bounds := text.BoundString(font.Font, line) + bounds := text.BoundString(font.Font18, line) y += bounds.Dy() if y > b.Height { break } - text.Draw(img, line, font.Font, 0, y, BUFFER_FOREGROUND) + text.Draw(img, line, font.Font18, 0, y, BUFFER_FOREGROUND) } return img } diff --git a/go/ui/button.go b/go/ui/button.go index 97dbad22..6454ccac 100644 --- a/go/ui/button.go +++ b/go/ui/button.go @@ -32,9 +32,19 @@ func NewSimpleButton(x, y, w, h int, label string, onClick func(b *SimpleButton) func (b *SimpleButton) render() *ebiten.Image { img := ebiten.NewImage(b.Width, b.Height) - // b := text.BoundString(f, label) ebitenutil.DrawRect(img, float64(0), float64(0), float64(b.Width), float64(b.Height), b.BG) - text.Draw(img, b.label, font.Font, 20, 25, b.FG) + + bounds := text.BoundString(font.Font24, b.label) + x := (b.Width - bounds.Dx()) / 2 + if x < 0 { + x = 0 + } + + y := b.Height - bounds.Dy()/2 + if y < 0 { + y = 0 + } + text.Draw(img, b.label, font.Font24, x, y, b.FG) return img } diff --git a/go/ui/cardView.go b/go/ui/cardView.go new file mode 100644 index 00000000..a1c004c5 --- /dev/null +++ b/go/ui/cardView.go @@ -0,0 +1,46 @@ +package ui + +import ( + "github.com/hajimehoshi/ebiten/v2" + "muhq.space/muhqs-game/go/assets" +) + +type CardView struct { + WidgetBase + cardPath string +} + +func NewScaledCardView(x, y int, width, height int, cardPath string) *CardView { + cv := &CardView{NewWidgetBase(x, y, width, height), cardPath} + cv.renderImpl = func() *ebiten.Image { return cv.render() } + return cv +} + +func NewAutoCardView(x, y int, cardPath string) *CardView { + cv := &CardView{NewWidgetBase(x, y, -1, -1), cardPath} + cv.renderImpl = func() *ebiten.Image { return cv.render() } + return cv +} + +func (cv *CardView) render() *ebiten.Image { + card := assets.GetCard(cv.cardPath, "en") + op := &ebiten.DrawImageOptions{} + + b := card.Bounds() + if cv.Width == -1 && cv.Height == -1 { + cv.Width = b.Dx() + cv.Height = b.Dy() + } else { + x_scale := float64(cv.Width) / float64(b.Dx()) + y_scale := float64(cv.Height) / float64(b.Dy()) + scale := x_scale + if y_scale < x_scale { + scale = y_scale + } + op.GeoM.Scale(scale, scale) + } + + img := ebiten.NewImage(cv.Width, cv.Height) + img.DrawImage(card, op) + return img +} diff --git a/go/ui/hand.go b/go/ui/hand.go index b07d177b..5421f6fb 100644 --- a/go/ui/hand.go +++ b/go/ui/hand.go @@ -6,6 +6,8 @@ import ( "github.com/hajimehoshi/ebiten/v2" "muhq.space/muhqs-game/go/assets" "muhq.space/muhqs-game/go/game" + + "golang.org/x/exp/slices" ) type HandCard struct { @@ -14,13 +16,24 @@ type HandCard struct { type HandView struct { WidgetBase - hand *game.Hand - cardWidth float64 - scale float64 + hand *game.Hand + cardWidth float64 + cardHeight float64 + scale float64 + bottomAligned bool + highlights []*game.Card } -func NewHandView(x, y, width, height int, h *game.Hand) *HandView { - hv := &HandView{NewWidgetBase(x, y, width, height), h, 0, 0} +func NewHandView(x, y, width, height int, h *game.Hand, bottomAligned bool) *HandView { + hv := &HandView{ + WidgetBase: NewWidgetBase(x, y, width, height), + hand: h, + cardWidth: 0, + cardHeight: 0, + scale: 0, + bottomAligned: bottomAligned, + highlights: []*game.Card{}, + } hv.renderImpl = func() *ebiten.Image { return hv.render() } return hv } @@ -44,11 +57,22 @@ func (hv *HandView) render() *ebiten.Image { hv.scale = y_scale } hv.cardWidth = float64(b.Dx()) * hv.scale + hv.cardHeight = float64(b.Dy()) * hv.scale + } + + yOffset := 0.0 + if hv.bottomAligned { + yOffset = float64(hv.Height) - hv.cardHeight } op := &ebiten.DrawImageOptions{} op.GeoM.Scale(hv.scale, hv.scale) - op.GeoM.Translate(float64(i)*hv.cardWidth, 0) + op.GeoM.Translate(float64(i)*hv.cardWidth, yOffset) + + if slices.Contains(hv.highlights, card) { + op.ColorM.Scale(1, 0, 0, 0.5) + } + img.DrawImage(cardImg, op) } return img @@ -59,6 +83,10 @@ func (hv *HandView) FindObjectAt(x, y int) interface{} { return nil } + if hv.bottomAligned && float64(y-hv.Y) < float64(hv.Height)-hv.cardHeight { + return nil + } + i := int(float64(x) / hv.cardWidth) if i >= hv.hand.Size() { return nil @@ -66,3 +94,16 @@ func (hv *HandView) FindObjectAt(x, y int) interface{} { return HandCard{hv.hand.GetCards()[i]} } + +func (hv *HandView) setHighlights(cards []*game.Card) { + hv.highlights = cards + hv.ForceRedraw() +} + +func (hv *HandView) HighlightCard(card *game.Card) { + hv.setHighlights([]*game.Card{card}) +} + +func (hv *HandView) ClearHighlights() { + hv.setHighlights([]*game.Card{}) +} diff --git a/go/ui/mapView.go b/go/ui/mapView.go index 6c887920..c966f1a8 100644 --- a/go/ui/mapView.go +++ b/go/ui/mapView.go @@ -132,20 +132,25 @@ func (vw *MapView) drawMapLayer(screen *ebiten.Image) { screen.DrawImage(vw.mapLayer, &ebiten.DrawImageOptions{}) } +func getPermanentSymbol(p game.Permanent, i int) *ebiten.Image { + permanentSymbol := assets.GetSymbol(strings.ToLower(p.GetCard().Name)) + if permanentSymbol == nil { + symbol := fmt.Sprintf("%c%d", unicode.ToUpper(rune(p.GetCard().Type.String()[0])), i) + permanentSymbol = assets.GetGenericSymbol(symbol) + if permanentSymbol == nil { + log.Fatalf("Failed to generate generic symbol %s", symbol) + } + } + + return permanentSymbol +} + func (vw *MapView) drawPermanentsLayer(screen *ebiten.Image) { if vw.permanentsLayer == nil { vw.permanentsLayer = vw.newLayerImage() for i, p := range vw.gameState.Permanents { - permanentSymbol := assets.GetSymbol(strings.ToLower(p.GetCard().Name)) - if permanentSymbol == nil { - symbol := fmt.Sprintf("%c%d", unicode.ToUpper(rune(p.GetCard().Type.String()[0])), i) - permanentSymbol = assets.GetGenericSymbol(symbol) - if permanentSymbol == nil { - log.Fatalf("Failed to generate generic symbol %s", symbol) - } - } - + permanentSymbol := getPermanentSymbol(p, i) 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 @@ -178,19 +183,37 @@ func (vw *MapView) ForceRedraw() { vw.permanentsLayer = nil } -func (vw *MapView) FindObjectAt(x_px, y_px int) interface{} { - x := x_px / TILE_WIDTH - y := y_px / TILE_HEIGHT +func isSymbolTransparentAt(p game.Permanent, relativeX, relativeY int) bool { + permanentSymbol := getPermanentSymbol(p, 0) + _, _, _, alpha := permanentSymbol.At(relativeX, relativeY).RGBA() + return alpha == 0 +} + +func (vw *MapView) FindObjectAt(screenX, screenY int) interface{} { + x := screenX / TILE_WIDTH + y := screenY / TILE_HEIGHT if x < 0 || y < 0 { return nil } - // TODO: detect if a permanent or the containing tile was selected - for _, u := range vw.gameState.Permanents { - pos := u.GetTile().Position - if pos.X == x && pos.Y == y { - return u + relativeX := screenX % TILE_WIDTH + relativeY := screenY % TILE_HEIGHT + xMargin := (TILE_WIDTH - PERMANENT_WIDTH) / 2 + yMargin := (TILE_HEIGHT - PERMANENT_HEIGHT) / 2 + // detect if a permanent or the containing tile was selected + if relativeX >= xMargin && relativeX <= TILE_WIDTH-xMargin && + relativeY >= yMargin && relativeY <= TILE_HEIGHT-yMargin { + + for _, p := range vw.gameState.Permanents { + pos := p.GetTile().Position + if pos.X == x && pos.Y == y { + if isSymbolTransparentAt(p, relativeX, relativeY) { + break + } + + return p + } } } diff --git a/go/ui/stateBar.go b/go/ui/stateBar.go index d7d86ea9..6914de6e 100644 --- a/go/ui/stateBar.go +++ b/go/ui/stateBar.go @@ -7,16 +7,12 @@ import ( "muhq.space/muhqs-game/go/game" ) -const ( - PHASE_VIEW_WIDTH = 250 -) - type StateBar struct { X, Y int Width, Height int gameState *game.State activePlayer *game.Player - ressourceView *TextBox + resourceView *TextBox phaseView *TextBox } @@ -25,25 +21,25 @@ func NewStateBar(x, y int, width, height int, s *game.State, p *game.Player) *St return sb } -func (sb *StateBar) ressourceLabel() string { - return fmt.Sprintf("%d", sb.activePlayer.Ressource) +func (sb *StateBar) resourceLabel() string { + return fmt.Sprintf("%d", sb.activePlayer.Resource) } func (sb *StateBar) phaseLabel() string { - return fmt.Sprintf("%s: %s", sb.gameState.Players[sb.gameState.ActivePlayerId].Name, + return fmt.Sprintf("%s: %s", sb.gameState.ActivePlayer.Name, sb.gameState.ActivePhase.String()) } func (sb *StateBar) Draw(screen *ebiten.Image) { - if sb.ressourceView == nil { - sb.ressourceView = NewFixedTextBox(sb.X, sb.Y, sb.Height, sb.Height, - sb.ressourceLabel()) + if sb.resourceView == nil { + sb.resourceView = NewCenteringFixedTextBox(sb.X, sb.Y, sb.Height, sb.Height, + sb.resourceLabel()) } - sb.ressourceView.Draw(screen) + sb.resourceView.Draw(screen) if sb.phaseView == nil { - sb.phaseView = NewFixedTextBox(sb.X+sb.ressourceView.Width, sb.Y, - PHASE_VIEW_WIDTH, sb.Height, sb.phaseLabel()) + sb.phaseView = NewFixedTextBox(sb.X+sb.resourceView.Width, sb.Y, + sb.Width-sb.resourceView.Width, sb.Height, sb.phaseLabel(), false) } sb.phaseView.Draw(screen) } @@ -53,9 +49,17 @@ func (sb *StateBar) FindObjectAt(x, y int) interface{} { } func (sb *StateBar) ForceRedraw() { - sb.ressourceView.Text = sb.ressourceLabel() - sb.ressourceView = nil + if sb.resourceView != nil { + sb.resourceView.Text = sb.resourceLabel() + sb.resourceView.ForceRedraw() + } + + if sb.phaseView != nil { + sb.phaseView.Text = sb.phaseLabel() + sb.phaseView.ForceRedraw() + } +} - sb.phaseView.Text = sb.phaseLabel() - sb.phaseView = nil +func (sb *StateBar) Contains(x, y int) bool { + return sb.resourceView.Contains(x, y) || sb.phaseView.Contains(x, y) } diff --git a/go/ui/textBox.go b/go/ui/textBox.go index 0cd09294..dd10e8e2 100644 --- a/go/ui/textBox.go +++ b/go/ui/textBox.go @@ -11,30 +11,60 @@ import ( ) var ( - TEXTBOX_BACKGROUND = gray - TEXTBOX_FOREGROUND = color.White + TEXTBOX_DEFAULT_BACKGROUND = gray + TEXTBOX_DEFAULT_FOREGROUND = color.White - TEXTBOX_AUTO_BORDER = 3 + TEXTBOX_DEFAULT_MARGIN = 3 ) type TextBox struct { WidgetBase - Text string + Text string + Fg color.Color + Bg color.Color + XMargin int + YMargin int + Center bool } -func NewFixedTextBox(x, y int, width, height int, t string) *TextBox { - tb := &TextBox{NewWidgetBase(x, y, width, height), t} +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) + 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\nMovement: %s\nAttack %s\nUpkeep: %d", - u.String(), u.GetDamage(), u.Health, u.Movement.String(), u.Attack.String(), u.Upkeep) + info := fmt.Sprintf("%s\nDamage: %d\nHealth: %d\nUpkeep: %d", + u.String(), u.GetDamage(), u.Health, u.Upkeep) + + if u.Movement != game.INVALID_MOVEMENT() { + info = fmt.Sprintf("%s\nMovement: %s", info, u.Movement.String()) + } + + if u.Attack != game.INVALID_ATTACK() { + info = fmt.Sprintf("%s\nAttack: %s", info, u.Attack.String()) + } + return NewAutoTextBox(x, y, info) } @@ -45,13 +75,88 @@ func NewPermInfo(x, y int, p game.Permanent) *TextBox { } func (tb *TextBox) render() *ebiten.Image { - b := text.BoundString(font.Font, tb.Text) + var x, y int + b := text.BoundString(font.Font24, tb.Text) if tb.Width == -1 || tb.Height == -1 { - tb.Width = b.Dx() + 2*TEXTBOX_AUTO_BORDER - tb.Height = b.Dy() + 2*TEXTBOX_AUTO_BORDER + 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 - b.Dx()) / 2 + if x < 0 { + x = 0 + } + } + + y = tb.Height - b.Dy()/2 + if y < 0 { + y = 0 + } } + img := ebiten.NewImage(tb.Width, tb.Height) - img.Fill(TEXTBOX_BACKGROUND) - text.Draw(img, tb.Text, font.Font, TEXTBOX_AUTO_BORDER+b.Min.X, TEXTBOX_AUTO_BORDER-b.Min.Y, TEXTBOX_FOREGROUND) + 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.GetCards() { + 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.GetCards() + if len(cards) == 0 { + return nil + } + + // b := text.BoundString(font.Font, w.Text) + // i := b.Dy() / w.poc.Size() + return cards[0] +} diff --git a/go/ui/widget.go b/go/ui/widget.go index d783eb36..cfcdd766 100644 --- a/go/ui/widget.go +++ b/go/ui/widget.go @@ -7,6 +7,7 @@ import ( type Widget interface { Draw(*ebiten.Image) FindObjectAt(int, int) interface{} + Contains(int, int) bool } type WidgetBase struct { |
