diff --git a/common/control/v1/mouse.go b/common/control/v1/mouse.go new file mode 100644 index 0000000..73e5121 --- /dev/null +++ b/common/control/v1/mouse.go @@ -0,0 +1,8 @@ +package control + +const ( + MouseLeft MouseButton = iota + MouseRight +) + +type MouseButton int diff --git a/common/elements/v1/block.go b/common/elements/v1/block.go new file mode 100644 index 0000000..4c71109 --- /dev/null +++ b/common/elements/v1/block.go @@ -0,0 +1,61 @@ +package elements + +import ( + "context" + "image/color" + + "git.vezzani.net/ben/games/common/ux/v1" + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/vector" + "golang.org/x/image/font" +) + +type Block struct { + Label string + Style struct { + Width float32 + Height float32 + XAlign XAlign + YAlign YAlign + Offset int + Font *font.Face + BackgroundColor *color.Color + } +} + +func (b *Block) backgroundColor() color.Color { + var c *color.Color + if b.Style.BackgroundColor != nil { + c = b.Style.BackgroundColor + } else { + c = &ux.BackgroundColor + } + + return *c +} + +func (b *Block) size(ctx context.Context) (x, y float32) { + x = b.Style.Width + y = b.Style.Height + + if x == 0 || y == 0 { + px, py := GetContainerSize(ctx) + if x == 0 { + x = px + } + if y == 0 { + y = py + } + } + + return +} + +func (b *Block) Draw(ctx context.Context, image *ebiten.Image) error { + xz, yz := GetZero(ctx) + w, h := b.size(ctx) + + vector.StrokeRect(image, xz, yz, w, h, 1, b.backgroundColor(), true) + + return nil +} diff --git a/common/elements/v1/button.go b/common/elements/v1/button.go new file mode 100644 index 0000000..8d21ce3 --- /dev/null +++ b/common/elements/v1/button.go @@ -0,0 +1,76 @@ +package elements + +import ( + "context" + "image/color" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/vector" + "golang.org/x/image/font" +) +import "git.vezzani.net/ben/games/common/ux/v1" + +type XAlign int +type YAlign int + +const ( + AlignCente XAlign = iota + AlignLeft + AlignRight +) + +const ( + AlignCenter YAlign = iota + AlignTop + AlignBottom +) + +type Button struct { + mouseHandler + Block + Label string + OnClick func() error + OnRightClick func() error + Style struct { + MouseDownColor *color.Color + } +} + +func (b *Button) HandleClick(_ MouseState) error { + if b.OnClick == nil { + return nil + } + return b.OnClick() +} + +func (b *Button) getFont() font.Face { + if b.Block.Style.Font == nil { + return ux.FontFace + } + + return *b.Block.Style.Font +} + +func (b *Button) backgroundColor() color.Color { + var c *color.Color + if b.Block.Style.BackgroundColor != nil { + c = b.Block.Style.BackgroundColor + } else { + c = &ux.BackgroundColor + } + + if (b.mouseState.RightDown || b.mouseState.LeftDown) && b.Style.MouseDownColor != nil { + c = b.Style.MouseDownColor + } + + return *c +} + +func (b *Button) Draw(ctx context.Context, image *ebiten.Image) error { + xz, yz := GetZero(ctx) + w, h := b.size(ctx) + + vector.StrokeRect(image, xz, yz, w, h, 1, b.backgroundColor(), true) + + return nil +} diff --git a/common/elements/v1/element.go b/common/elements/v1/element.go new file mode 100644 index 0000000..411c511 --- /dev/null +++ b/common/elements/v1/element.go @@ -0,0 +1,92 @@ +package elements + +import ( + "context" + + "github.com/hajimehoshi/ebiten/v2" +) + +type MouseState struct { + X, Y int32 + LeftDown, RightDown bool + LeftChanged, RightChanged bool +} + +const ( + ContainerSizeKey = "container_size" + ZeroKey = "zero" +) + +type Clickable interface { + Element + HandleClick(ctx context.Context, s MouseState) error +} + +type Mouseable interface { + Element + HandleMouseEnter(ctx context.Context, s MouseState) error + HandleMouseLeave(ctx context.Context, s MouseState) error + HandleMouseMove(ctx context.Context, s MouseState) error + HandleMouseDown(ctx context.Context, s MouseState) error + HandleMouseUp(ctx context.Context, s MouseState) error +} + +type Element interface { + Draw(ctx context.Context, image *ebiten.Image) error +} + +func SetZero(ctx context.Context, x, y float32) context.Context { + return context.WithValue(ctx, ZeroKey, [2]float32{x, y}) +} + +func SetContainerSize(ctx context.Context, w, h float32) context.Context { + return context.WithValue(ctx, ContainerSizeKey, [2]float32{w, h}) +} + +func GetZero(ctx context.Context) (x, y float32) { + if v, ok := ctx.Value(ZeroKey).([2]float32); ok { + return v[0], v[1] + } + return 0, 0 +} + +func GetContainerSize(ctx context.Context) (w, h float32) { + if s, ok := ctx.Value(ContainerSizeKey).([2]float32); ok { + return s[0], s[1] + } + return 0, 0 +} + +type mouseHandler struct { + mouseState MouseState + prevMouseState MouseState +} + +func (b *mouseHandler) HandleMouseEnter(s MouseState) error { + b.mouseState = s + return nil +} + +func (b *mouseHandler) HandleMouseLeave(s MouseState) error { + b.prevMouseState = b.mouseState + b.mouseState = MouseState{} + return nil +} + +func (b *mouseHandler) HandleMouseMove(s MouseState) error { + b.prevMouseState = b.mouseState + b.mouseState = s + return nil +} + +func (b *mouseHandler) HandleMouseDown(s MouseState) error { + b.prevMouseState = b.mouseState + b.mouseState = s + return nil +} + +func (b *mouseHandler) HandleMouseUp(s MouseState) error { + b.prevMouseState = b.mouseState + b.mouseState = s + return nil +} diff --git a/common/elements/v1/menu.go b/common/elements/v1/menu.go new file mode 100644 index 0000000..8a64ab6 --- /dev/null +++ b/common/elements/v1/menu.go @@ -0,0 +1 @@ +package elements diff --git a/common/elements/v1/table.go b/common/elements/v1/table.go new file mode 100644 index 0000000..34af0f5 --- /dev/null +++ b/common/elements/v1/table.go @@ -0,0 +1,100 @@ +package elements + +import ( + "context" + "fmt" + + "github.com/hajimehoshi/ebiten/v2" +) + +type Table struct { + mouseHandler + Block + + ColumnCount int + RowCount int + + Cells []Element + + Style struct { + } + + mouseOverCell int +} + +func (t *Table) HandleClick(ctx context.Context, s MouseState) error { + address := t.getAddressUnderMouse() + if address < 0 || address >= len(t.Cells) { + return fmt.Errorf("cell address under mouse is out of bounds") + } + + if clickable, ok := t.Cells[address].(Clickable); ok { + return clickable.HandleClick(s) + } + + return nil +} + +func (t *Table) HandleMouseEnter(ctx context.Context, s MouseState) error { + _ = t.mouseHandler.HandleMouseEnter(s) + address := t.getAddressUnderMouse() + if address < 0 || address >= len(t.Cells) { + return fmt.Errorf("cell address under mouse is out of bounds") + } + + if mouseable, ok := t.Cells[address].(Mouseable); ok { + s.X -= + return mouseable.HandleMouseEnter(s) + } + + return nil +} + +func (t *Table) HandleMouseLeave(ctx context.Context, s MouseState) error { + _ = t.mouseHandler.HandleMouseLeave(s) + return nil +} + +func (t *Table) HandleMouseMove(ctx context.Context, s MouseState) error { + _ = t.mouseHandler.HandleMouseMove(s) + return nil +} + +func (t *Table) HandleMouseDown(ctx context.Context, s MouseState) error { + _ = t.mouseHandler.HandleMouseDown(s) + return nil +} + +func (t *Table) HandleMouseUp(ctx context.Context, s MouseState) error { + _ = t.mouseHandler.HandleMouseUp(s) + return nil +} + +func (t *Table) CellSize(ctx context.Context) (w, h float32) { + w, h = GetContainerSize(ctx) + return w / float32(t.ColumnCount), h / float32(t.RowCount) +} + +func (t *Table) CellZero(ctx context.Context, addr int) (x, y float32) { + x, y = GetZero(ctx) + w, h := t.CellSize(ctx) + + col, row := addr%t.ColumnCount, addr/t.ColumnCount + return x + float32(col)*w, y + float32(row) + h +} + +func (t *Table) Draw(ctx context.Context, screen *ebiten.Image) error { + +} + +func (t *Table) contextForCell(ctx context.Context, addr int) context.Context { + zx, zy := t.CellZero(ctx, addr) + w, h := t.CellSize(ctx) + ctx = SetZero(ctx, zx, zy) + ctx = SetContainerSize(ctx, w, h) + return ctx +} + +func (t *Table) getAddressUnderMouse() int { + return int(t.mouseState.Y)*t.RowCount + int(t.mouseState.X) +} diff --git a/common/ux/v1/ux.go b/common/ux/v1/ux.go new file mode 100644 index 0000000..03490e5 --- /dev/null +++ b/common/ux/v1/ux.go @@ -0,0 +1,13 @@ +package ux + +import ( + "image/color" + + "golang.org/x/image/font" + "golang.org/x/image/font/basicfont" +) + +var ( + FontFace font.Face = basicfont.Face7x13 + BackgroundColor color.Color = color.White +) diff --git a/games/minesweeper/game/game.go b/games/minesweeper/v1/game/game.go similarity index 100% rename from games/minesweeper/game/game.go rename to games/minesweeper/v1/game/game.go diff --git a/games/minesweeper/main.go b/games/minesweeper/v1/main.go similarity index 74% rename from games/minesweeper/main.go rename to games/minesweeper/v1/main.go index b23f639..8a6b190 100644 --- a/games/minesweeper/main.go +++ b/games/minesweeper/v1/main.go @@ -3,12 +3,12 @@ package main import ( "log" - "git.vezzani.net/ben/games/games/minesweeper/game" + "git.vezzani.net/ben/games/games/minesweeper/v1/game" "github.com/hajimehoshi/ebiten/v2" ) func main() { - g := game.New(10, 10, 50) + g := game.New(10, 10, 15) w, h := g.Layout(0, 0) ebiten.SetWindowSize(w, h) ebiten.SetWindowTitle("Minesweeper")