From 4829fbb5c7d0ef1b50030c2bf28d3319ceed5fbe Mon Sep 17 00:00:00 2001 From: Ben Vezzani Date: Thu, 28 Aug 2025 14:32:23 -0400 Subject: [PATCH] holy shit components are rendering whaaaaaaaaaaaaaaaat --- common/elements/v1/block.go | 28 ----- common/elements/v1/blocks/block.go | 58 +++++++++ common/elements/v1/blocks/options.go | 23 ++++ common/elements/v1/button.go | 140 ++++++++++----------- common/elements/v1/element.go | 59 ++------- common/elements/v1/mouse/mouse.go | 64 ++++++++++ common/elements/v1/stack.go | 31 ----- common/elements/v1/stacks/options.go | 26 ++++ common/elements/v1/stacks/stack.go | 51 ++++++++ common/elements/v1/table.go | 181 ++++++++++++++------------- tools/component_test/editor.go | 68 ++++++++++ tools/component_test/main.go | 15 +++ tools/spritedit/editor.go | 41 ++++-- tools/spritedit/main.go | 6 +- 14 files changed, 511 insertions(+), 280 deletions(-) delete mode 100644 common/elements/v1/block.go create mode 100644 common/elements/v1/blocks/block.go create mode 100644 common/elements/v1/blocks/options.go create mode 100644 common/elements/v1/mouse/mouse.go delete mode 100644 common/elements/v1/stack.go create mode 100644 common/elements/v1/stacks/options.go create mode 100644 common/elements/v1/stacks/stack.go create mode 100644 tools/component_test/editor.go create mode 100644 tools/component_test/main.go diff --git a/common/elements/v1/block.go b/common/elements/v1/block.go deleted file mode 100644 index 64d9a14..0000000 --- a/common/elements/v1/block.go +++ /dev/null @@ -1,28 +0,0 @@ -package elements - -import ( - "github.com/hajimehoshi/ebiten/v2" -) - -type BlockOption func(*block) - -func Block(ops ...BlockOption) ElementFunc { - return func(d Dimensions) Element { - b := block{} - for i := range ops { - ops[i](&b) - } - return &b - } -} - -type block struct { - width float64 - height float64 - xAlign xAlign - yAlign yAlign -} - -func (b block) Draw(image *ebiten.Image, anchorX, anchorY float64) (w, h float64) { - return b.width, b.height -} diff --git a/common/elements/v1/blocks/block.go b/common/elements/v1/blocks/block.go new file mode 100644 index 0000000..ade548e --- /dev/null +++ b/common/elements/v1/blocks/block.go @@ -0,0 +1,58 @@ +package blocks + +import ( + "image/color" + + "git.vezzani.net/ben/games/common/elements/v1" + "git.vezzani.net/ben/games/common/elements/v1/mouse" + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/vector" +) + +func New(ops ...Option) elements.ElementFunc { + return func(d elements.Bounds) elements.Element { + b := Block{ + ContainerBounds: d, + } + for i := range ops { + ops[i](&b) + } + return &b + } +} + +type Block struct { + ContainerBounds elements.Bounds + backgroundColor *color.Color + mouse.NopHandler + width float64 + height float64 + name string +} + +func (b *Block) Size() (w, h float64) { + w, h = b.ContainerBounds.Width, b.ContainerBounds.Height + if b.width != 0 { + w = b.width + } + if b.height != 0 { + h = b.height + } + return +} + +func (b *Block) Draw(image *ebiten.Image) (w, h float64) { + w, h = b.Size() + if b.backgroundColor != nil { + vector.DrawFilledRect( + image, + float32(b.ContainerBounds.Min.X), + float32(b.ContainerBounds.Min.Y), + float32(w), + float32(h), + *b.backgroundColor, + true, + ) + } + return +} diff --git a/common/elements/v1/blocks/options.go b/common/elements/v1/blocks/options.go new file mode 100644 index 0000000..352353c --- /dev/null +++ b/common/elements/v1/blocks/options.go @@ -0,0 +1,23 @@ +package blocks + +import "image/color" + +type Option func(*Block) + +func Size(w, h float64) Option { + return func(b *Block) { + b.width, b.height = w, h + } +} + +func BackgroundColor(c color.Color) Option { + return func(b *Block) { + b.backgroundColor = &c + } +} + +func Name(n string) Option { + return func(b *Block) { + b.name = n + } +} diff --git a/common/elements/v1/button.go b/common/elements/v1/button.go index 8a34c96..e59a809 100644 --- a/common/elements/v1/button.go +++ b/common/elements/v1/button.go @@ -1,74 +1,70 @@ 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.block.Font == nil { - return ux.FontFace - } - - return *b.block.block.Font -} - -func (b *Button) backgroundColor() color.Color { - var c *color.Color - if b.block.block.BackgroundColor != nil { - c = b.block.block.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 { - - vector.StrokeRect(image, xz, yz, w, h, 1, b.backgroundColor(), true) - - return nil -} +// +//import ( +// "image/color" +//) +// +//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.Font == nil { +// return ux.FontFace +// } +// +// return *b.block.block.Font +//} +// +//func (b *Button) backgroundColor() color.Color { +// var c *color.Color +// if b.block.block.BackgroundColor != nil { +// c = b.block.block.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 { +// +// 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 index 8e28b6f..278fd90 100644 --- a/common/elements/v1/element.go +++ b/common/elements/v1/element.go @@ -1,68 +1,29 @@ package elements import ( + "git.vezzani.net/ben/games/common/elements/v1/mouse" "github.com/hajimehoshi/ebiten/v2" ) -type ElementFunc func(Dimensions) Element - -type MouseState struct { - X, Y int32 - LeftDown, RightDown bool - LeftChanged, RightChanged bool -} +type ElementFunc func(Bounds) Element type Clickable interface { - HandleClick(s MouseState) error } type Mouseable interface { - HandleMouseEnter(s MouseState) error - HandleMouseLeave(s MouseState) error - HandleMouseMove(s MouseState) error - HandleMouseDown(s MouseState) error - HandleMouseUp(s MouseState) error } type Element interface { - Draw(image *ebiten.Image, anchorX, anchorY float64) (w, h float64) + HandleMouseEvent(s mouse.State) bool + + Draw(image *ebiten.Image) (w, h float64) } -type Dimensions struct { - zx, zy float64 - wx, wy float64 +type Point struct { + X, Y float64 } -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 +type Bounds struct { + Min Point + Width, Height float64 } diff --git a/common/elements/v1/mouse/mouse.go b/common/elements/v1/mouse/mouse.go new file mode 100644 index 0000000..4a61d1d --- /dev/null +++ b/common/elements/v1/mouse/mouse.go @@ -0,0 +1,64 @@ +package mouse + +import ( + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/inpututil" +) + +type NopHandler struct{} + +func (n NopHandler) HandleMouseEvent(s State) bool { + return false +} + +type State struct { + X, Y int + LeftDown, RightDown bool + LeftChanged, RightChanged bool + LeftClicked, RightClicked bool + Clicked bool +} + +//func ClickHandler(e elements.Element) func() bool { +// newState := StateBuilder() +// return func() bool { +// _ = newState() +// if +// } +//} + +func StateBuilder() func() State { + prevState := State{} + state := State{} + return func() State { + state = prevState + state.X, state.Y = ebiten.CursorPosition() + + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { + state.LeftDown = true + } + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { + state.RightDown = true + } + if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) { + state.LeftDown = false + if !state.RightDown { + state.Clicked = true + } + } + if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonRight) { + state.RightDown = false + if !state.LeftDown { + state.Clicked = true + } + } + + state.Clicked = state.Clicked && !prevState.Clicked + + state.LeftChanged = state.LeftDown != prevState.LeftDown + state.RightChanged = state.RightDown != prevState.RightDown + + prevState = state + return state + } +} diff --git a/common/elements/v1/stack.go b/common/elements/v1/stack.go deleted file mode 100644 index c7450eb..0000000 --- a/common/elements/v1/stack.go +++ /dev/null @@ -1,31 +0,0 @@ -package elements - -import "github.com/hajimehoshi/ebiten/v2" - -type StackOption func(*stack) - -func Stack(ops ...StackOption) ElementFunc { - return func(parentDimensions Dimensions) Element { - - } -} - -type stack struct { - block - horizontal bool - children []Element -} - -func (s *stack) Draw(image *ebiten.Image, anchorX, anchorY float64) (w, h float64) { - var offsetX, offsetY float64 - originalX, originalY := anchorX, anchorY - for i := range s.children { - offsetX, offsetY = s.children[i].Draw(image, anchorX, anchorY) - if s.horizontal { - anchorX += offsetX - } else { - anchorY += offsetY - } - } - return anchorX - originalX, anchorY - originalY -} diff --git a/common/elements/v1/stacks/options.go b/common/elements/v1/stacks/options.go new file mode 100644 index 0000000..8ed006b --- /dev/null +++ b/common/elements/v1/stacks/options.go @@ -0,0 +1,26 @@ +package stacks + +import ( + "git.vezzani.net/ben/games/common/elements/v1" + "git.vezzani.net/ben/games/common/elements/v1/blocks" +) + +type Option func(*Stack) + +func BlockOpt(o blocks.Option) Option { + return func(s *Stack) { + o(&s.Block) + } +} + +func Children(children ...elements.ElementFunc) Option { + return func(s *Stack) { + s.children = children + } +} + +func Horizontal() Option { + return func(s *Stack) { + s.horizontal = true + } +} diff --git a/common/elements/v1/stacks/stack.go b/common/elements/v1/stacks/stack.go new file mode 100644 index 0000000..012ffe4 --- /dev/null +++ b/common/elements/v1/stacks/stack.go @@ -0,0 +1,51 @@ +package stacks + +import ( + "git.vezzani.net/ben/games/common/elements/v1" + "git.vezzani.net/ben/games/common/elements/v1/blocks" + "git.vezzani.net/ben/games/common/elements/v1/mouse" + + "github.com/hajimehoshi/ebiten/v2" +) + +func New(ops ...Option) elements.ElementFunc { + return func(d elements.Bounds) elements.Element { + s := Stack{ + Block: blocks.Block{ContainerBounds: d}, + } + for op := range ops { + ops[op](&s) + } + return &s + } +} + +type Stack struct { + blocks.Block + mouse.NopHandler + horizontal bool + children []elements.ElementFunc +} + +func (s *Stack) Draw(image *ebiten.Image) (w, h float64) { + s.Block.Draw(image) + + d := s.ContainerBounds + d.Width, d.Height = s.Block.Size() + if s.horizontal { + d.Width /= float64(len(s.children)) + } else { + d.Height /= float64(len(s.children)) + } + + var offsetX, offsetY float64 + for i := range s.children { + offsetX, offsetY = s.children[i](d).Draw(image) + if s.horizontal { + d.Min.X += offsetX + } else { + d.Min.Y += offsetY + } + } + return s.Block.Size() +} diff --git a/common/elements/v1/table.go b/common/elements/v1/table.go index e546916..4250cef 100644 --- a/common/elements/v1/table.go +++ b/common/elements/v1/table.go @@ -1,92 +1,93 @@ package elements -import ( - "context" - "fmt" - - "github.com/hajimehoshi/ebiten/v2" -) - -type Table struct { - mouseHandler - block - - ColumnCount int - RowCount int - - Cells []Dimensions - - 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() (w, h float32) { - w, h = t. - 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(screen *ebiten.Image, zx, zy float64) error { - return nil -} - -func (t *Table) getAddressUnderMouse() int { - return int(t.mouseState.Y)*t.RowCount + int(t.mouseState.X) -} +// +//import ( +// "context" +// "fmt" +// +// "github.com/hajimehoshi/ebiten/v2" +//) +// +//type Table struct { +// mouseHandler +// block +// +// ColumnCount int +// RowCount int +// +// Cells []Bounds +// +// 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() (w, h float32) { +// w, h = t. +// 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(screen *ebiten.Image, zx, zy float64) error { +// return nil +//} +// +//func (t *Table) getAddressUnderMouse() int { +// return int(t.mouseState.Y)*t.RowCount + int(t.mouseState.X) +//} diff --git a/tools/component_test/editor.go b/tools/component_test/editor.go new file mode 100644 index 0000000..eea5bb5 --- /dev/null +++ b/tools/component_test/editor.go @@ -0,0 +1,68 @@ +package main + +import ( + "image/color" + + "git.vezzani.net/ben/games/common/elements/v1" + "git.vezzani.net/ben/games/common/elements/v1/blocks" + "git.vezzani.net/ben/games/common/elements/v1/stacks" + "git.vezzani.net/ben/games/common/sprites/v1" + "github.com/hajimehoshi/ebiten/v2" + "golang.org/x/image/colornames" +) + +var root = stacks.New( + stacks.Horizontal(), + stacks.BlockOpt(blocks.BackgroundColor(color.White)), + stacks.BlockOpt(blocks.Size(200, 200)), + stacks.BlockOpt(blocks.Name("parent")), + stacks.Children( + stacks.New( + stacks.BlockOpt(blocks.Name("left")), + stacks.Children( + blocks.New(blocks.BackgroundColor(colornames.Green)), + blocks.New(blocks.BackgroundColor(colornames.Yellow)), + )), + stacks.New( + stacks.BlockOpt(blocks.Name("right")), + stacks.Children( + blocks.New(blocks.BackgroundColor(colornames.Blue)), + blocks.New(blocks.BackgroundColor(colornames.Red)), + ), + ), + ), +) + +func newEditor() *editor { + return &editor{} +} + +type editor struct { + root elements.Element + bounds elements.Bounds +} + +//var handleClick = mouse.ClickHandler(root) + +func (e *editor) Update() error { + sprites.Update() + //handleClick() + return nil +} + +func (e *editor) Draw(screen *ebiten.Image) { + e.root.Draw(screen) +} + +func (e *editor) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { + if e.bounds.Width != float64(outsideWidth) || e.bounds.Height != float64(outsideHeight) { + e.bounds = elements.Bounds{ + Min: elements.Point{}, + Width: float64(outsideWidth), + Height: float64(outsideHeight), + } + e.root = root(e.bounds) + } + + return outsideWidth, outsideHeight +} diff --git a/tools/component_test/main.go b/tools/component_test/main.go new file mode 100644 index 0000000..a7d70bb --- /dev/null +++ b/tools/component_test/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/hajimehoshi/ebiten/v2" +) + +func main() { + ebiten.SetWindowSize(480, 320) + ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled) + ebiten.SetWindowTitle("Component/Layout Test") + e := newEditor() + if err := ebiten.RunGame(e); err != nil { + panic(err) + } +} diff --git a/tools/spritedit/editor.go b/tools/spritedit/editor.go index 7f88726..2dd65e1 100644 --- a/tools/spritedit/editor.go +++ b/tools/spritedit/editor.go @@ -1,21 +1,48 @@ package main -import "github.com/hajimehoshi/ebiten/v2" +import ( + "image/color" + + "git.vezzani.net/ben/games/common/elements/v1" + "git.vezzani.net/ben/games/common/elements/v1/blocks" + "git.vezzani.net/ben/games/common/elements/v1/stacks" + "git.vezzani.net/ben/games/common/sprites/v1" + "github.com/hajimehoshi/ebiten/v2" + "golang.org/x/image/colornames" +) + +var menu = stacks.New( + stacks.BlockOpt(blocks.BackgroundColor(color.White)), + stacks.BlockOpt(blocks.Size(100, 200)), + stacks.Children( + blocks.New(blocks.BackgroundColor(colornames.Green)), + blocks.New(), + ), +) + +func newEditor() *editor { + return &editor{} +} type editor struct { } func (e *editor) Update() error { - //TODO implement me - panic("implement me") + sprites.Update() + + return nil } func (e *editor) Draw(screen *ebiten.Image) { - //TODO implement me - panic("implement me") + b := screen.Bounds() + menu(elements.Bounds{ + ZX: float64(b.Min.X), + ZY: float64(b.Min.Y), + WX: float64(b.Max.X), + WY: float64(b.Max.Y), + }).Draw(screen) } func (e *editor) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { - //TODO implement me - panic("implement me") + return outsideWidth, outsideHeight } diff --git a/tools/spritedit/main.go b/tools/spritedit/main.go index 16c432c..530162d 100644 --- a/tools/spritedit/main.go +++ b/tools/spritedit/main.go @@ -1,15 +1,15 @@ package main import ( - "bytes" - "github.com/hajimehoshi/ebiten/v2" ) func main() { ebiten.SetWindowSize(480, 320) ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled) - if err := ebiten.RunGame(); err != nil { + ebiten.SetWindowTitle("Sprite Editor") + e := newEditor() + if err := ebiten.RunGame(e); err != nil { panic(err) } }