From dda0a832024a77829a9fb7ad26c8eaec6f528d6d Mon Sep 17 00:00:00 2001 From: Ben Vezzani Date: Sun, 31 Aug 2025 13:29:52 -0400 Subject: [PATCH] okay giant ux refactor to hopefully make it cleaner --- common/elements/v1/blocks/block.go | 57 ++++++++++------ common/elements/v1/blocks/options.go | 8 +-- common/elements/v1/buttons/button.go | 48 ++++++++++++-- common/elements/v1/buttons/options.go | 28 ++++++-- common/elements/v1/element.go | 28 ++++++-- common/elements/v1/menu.go | 1 - common/elements/v1/mouse/mouse.go | 19 +++--- common/elements/v1/stacks/options.go | 13 +--- common/elements/v1/stacks/stack.go | 76 ++++++++++++++-------- common/elements/v1/table.go | 93 --------------------------- common/ux/v1/ux.go | 1 + tools/component_test/editor.go | 40 +++++++----- 12 files changed, 214 insertions(+), 198 deletions(-) delete mode 100644 common/elements/v1/menu.go delete mode 100644 common/elements/v1/table.go diff --git a/common/elements/v1/blocks/block.go b/common/elements/v1/blocks/block.go index 4ff9380..ed092c8 100644 --- a/common/elements/v1/blocks/block.go +++ b/common/elements/v1/blocks/block.go @@ -5,15 +5,14 @@ import ( "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.InitFunc { - return func(d elements.Bounds) elements.Element { - b := Block{ - ContainerBounds: d, - } +func New(ops ...OptFunc) elements.InitFunc { + return func() elements.Element { + b := Block{} for i := range ops { ops[i](&b) } @@ -22,37 +21,53 @@ func New(ops ...Option) elements.InitFunc { } type Block struct { - ContainerBounds elements.Bounds + anchor elements.Point backgroundColor *color.Color mouse.NopHandler - width float64 - height float64 - name string + width, height int + name string } -func (b *Block) Size() (w, h float64) { - w, h = b.ContainerBounds.Width, b.ContainerBounds.Height - if b.width != 0 { - w = b.width +func (b *Block) SetAnchor(a elements.Point) { + d := b.anchor.Delta(a) + for i := range b.Children() { + b.Children()[i].SetAnchor(b.Children()[i].Anchor().Add(d)) } - if b.height != 0 { - h = b.height + b.anchor = a +} + +func (b *Block) Bounds() elements.Bounds { + return elements.Bounds{ + Min: b.anchor, + Width: b.width, + Height: b.height, } - return +} + +func (b *Block) Anchor() elements.Point { + return b.anchor +} + +func (b *Block) Size() (w, h int) { + return b.width, b.height } func (b *Block) Draw(image *ebiten.Image) { - w, h := b.Size() + bnd := b.Bounds() if b.backgroundColor != nil { vector.DrawFilledRect( image, - float32(b.ContainerBounds.Min.X), - float32(b.ContainerBounds.Min.Y), - float32(w), - float32(h), + float32(b.anchor.X), + float32(b.anchor.Y), + float32(bnd.Width), + float32(bnd.Height), *b.backgroundColor, true, ) } return } + +func (b *Block) Children() []elements.Element { + return nil +} diff --git a/common/elements/v1/blocks/options.go b/common/elements/v1/blocks/options.go index 352353c..2816e8a 100644 --- a/common/elements/v1/blocks/options.go +++ b/common/elements/v1/blocks/options.go @@ -2,21 +2,21 @@ package blocks import "image/color" -type Option func(*Block) +type OptFunc func(*Block) -func Size(w, h float64) Option { +func Size(w, h int) OptFunc { return func(b *Block) { b.width, b.height = w, h } } -func BackgroundColor(c color.Color) Option { +func BackgroundColor(c color.Color) OptFunc { return func(b *Block) { b.backgroundColor = &c } } -func Name(n string) Option { +func Name(n string) OptFunc { return func(b *Block) { b.name = n } diff --git a/common/elements/v1/buttons/button.go b/common/elements/v1/buttons/button.go index 7023d49..ba5a2c5 100644 --- a/common/elements/v1/buttons/button.go +++ b/common/elements/v1/buttons/button.go @@ -1,15 +1,24 @@ package buttons 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/mouse" + "git.vezzani.net/ben/games/common/ux/v1" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/text" + "golang.org/x/image/font" ) -func New(ops ...Option) elements.InitFunc { - return func(bounds elements.Bounds) elements.Element { +func New(ops ...OptFunc) elements.InitFunc { + return func() elements.Element { b := Button{ - Block: blocks.Block{ContainerBounds: bounds}, + Block: blocks.Block{}, + font: ux.FontFace, + color: ux.FontColor, } for op := range ops { ops[op](&b) @@ -22,18 +31,43 @@ func New(ops ...Option) elements.InitFunc { type Button struct { blocks.Block + label string + font font.Face + color color.Color + onClick func(ms mouse.State) onMouseDown func(ms mouse.State) onMouseUp func(ms mouse.State) } +func (b *Button) Draw(screen *ebiten.Image) { + b.Block.Draw(screen) + b.drawLabel(screen) +} + +func (b *Button) drawLabel(screen *ebiten.Image) { + if b.label != "" { + strBounds, _ := font.BoundString(b.font, b.label) + + textWidth := strBounds.Max.X - strBounds.Min.X + textHeight := strBounds.Max.Y - strBounds.Min.Y + + bnd := b.Bounds() + tx := bnd.Min.X + (bnd.Width / 2) - (textWidth / 2).Round() + ty := bnd.Min.Y + (bnd.Height / 2) - (textHeight / 2).Round() + + text.Draw(screen, b.label, b.font, tx, ty, b.color) + + } +} + func (b *Button) HandleMouseEvent(ms mouse.State) bool { - if !b.Block.ContainerBounds.Contains(elements.Point{ - X: float64(ms.X), - Y: float64(ms.Y), - }) { + bnd := b.Bounds() + + if !bnd.Contains(ms.Point()) { return false } + switch { case b.onClick != nil && (ms.RightClicked || ms.LeftClicked): b.onClick(ms) diff --git a/common/elements/v1/buttons/options.go b/common/elements/v1/buttons/options.go index 7d4a76b..ccf81ae 100644 --- a/common/elements/v1/buttons/options.go +++ b/common/elements/v1/buttons/options.go @@ -1,32 +1,50 @@ package buttons import ( + "image/color" + "git.vezzani.net/ben/games/common/elements/v1/blocks" "git.vezzani.net/ben/games/common/elements/v1/mouse" ) -type Option func(button *Button) +type OptFunc func(button *Button) -func OnClick(f func(ms mouse.State)) Option { +type Option interface { + OptFunc | blocks.OptFunc +} + +func OnClick(f func(ms mouse.State)) OptFunc { return func(button *Button) { button.onClick = f } } -func OnMouseDown(f func(ms mouse.State)) Option { +func OnMouseDown(f func(ms mouse.State)) OptFunc { return func(button *Button) { button.onMouseDown = f } } -func OnMouseUp(f func(ms mouse.State)) Option { +func OnMouseUp(f func(ms mouse.State)) OptFunc { return func(button *Button) { button.onMouseUp = f } } -func BlockOpt(o blocks.Option) Option { +func BlockOpt(o blocks.OptFunc) OptFunc { return func(s *Button) { o(&s.Block) } } + +func Label(s string) OptFunc { + return func(button *Button) { + button.label = s + } +} + +func LabelColor(color color.Color) OptFunc { + return func(button *Button) { + button.color = color + } +} diff --git a/common/elements/v1/element.go b/common/elements/v1/element.go index 6ef79a7..234c7b7 100644 --- a/common/elements/v1/element.go +++ b/common/elements/v1/element.go @@ -4,21 +4,37 @@ import ( "github.com/hajimehoshi/ebiten/v2" ) -type InitFunc func(Bounds) Element +type InitFunc func() Element type Element interface { - Size() (w, h float64) - - Draw(image *ebiten.Image) + Draw(*ebiten.Image) + SetAnchor(Point) + Anchor() Point + Size() (w, h int) + Children() []Element } type Point struct { - X, Y float64 + X, Y int +} + +func (p Point) Delta(p2 Point) Point { + return Point{ + X: p2.X - p.X, + Y: p2.Y - p.Y, + } +} + +func (p Point) Add(p2 Point) Point { + return Point{ + X: p.X + p2.X, + Y: p.Y + p2.Y, + } } type Bounds struct { Min Point - Width, Height float64 + Width, Height int } func (b *Bounds) Contains(p Point) bool { diff --git a/common/elements/v1/menu.go b/common/elements/v1/menu.go deleted file mode 100644 index 8a64ab6..0000000 --- a/common/elements/v1/menu.go +++ /dev/null @@ -1 +0,0 @@ -package elements diff --git a/common/elements/v1/mouse/mouse.go b/common/elements/v1/mouse/mouse.go index f64a4d9..cd4443d 100644 --- a/common/elements/v1/mouse/mouse.go +++ b/common/elements/v1/mouse/mouse.go @@ -12,10 +12,6 @@ func (n NopHandler) HandleMouseEvent(s State) bool { return false } -type ChildrenProvider interface { - GetChildren() []elements.Element -} - type EventHandler interface { HandleMouseEvent(s State) bool } @@ -28,6 +24,13 @@ type State struct { Clicked bool } +func (s *State) Point() elements.Point { + return elements.Point{ + X: s.X, + Y: s.Y, + } +} + func Handler(e elements.Element) func() bool { newState := StateBuilder() ms := newState() @@ -38,11 +41,9 @@ func Handler(e elements.Element) func() bool { } func propagateMouse(e elements.Element, ms State) bool { - if p, ok := e.(ChildrenProvider); ok { - for _, c := range p.GetChildren() { - if propagateMouse(c, ms) { - return true - } + for _, c := range e.Children() { + if propagateMouse(c, ms) { + return true } } diff --git a/common/elements/v1/stacks/options.go b/common/elements/v1/stacks/options.go index 2432aa8..af50647 100644 --- a/common/elements/v1/stacks/options.go +++ b/common/elements/v1/stacks/options.go @@ -2,24 +2,17 @@ package stacks import ( "git.vezzani.net/ben/games/common/elements/v1" - "git.vezzani.net/ben/games/common/elements/v1/blocks" ) -type Option func(*Stack) +type OptFunc func(*Stack) -func BlockOpt(o blocks.Option) Option { - return func(s *Stack) { - o(&s.Block) - } -} - -func Children(children ...elements.InitFunc) Option { +func Children(children ...elements.InitFunc) OptFunc { return func(s *Stack) { s.childrenInit = children } } -func Horizontal() Option { +func Horizontal() OptFunc { return func(s *Stack) { s.horizontal = true } diff --git a/common/elements/v1/stacks/stack.go b/common/elements/v1/stacks/stack.go index 8e67eae..9bf2562 100644 --- a/common/elements/v1/stacks/stack.go +++ b/common/elements/v1/stacks/stack.go @@ -2,37 +2,29 @@ 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.InitFunc { - return func(d elements.Bounds) elements.Element { - s := Stack{ - Block: blocks.Block{ContainerBounds: d}, - } +func New(ops ...OptFunc) elements.InitFunc { + return func() elements.Element { + s := Stack{} for op := range ops { ops[op](&s) } - d.Width, d.Height = s.Block.Size() - if s.horizontal { - d.Width /= float64(len(s.childrenInit)) - } else { - d.Height /= float64(len(s.childrenInit)) - } - - s.Children = make([]elements.Element, len(s.childrenInit)) - var offsetX, offsetY float64 + s.children = make([]elements.Element, len(s.childrenInit)) + var cw, ch int + anchor := s.anchor for i := range s.childrenInit { - s.Children[i] = s.childrenInit[i](d) - offsetX, offsetY = s.Children[i].Size() + s.children[i] = s.childrenInit[i]() + s.children[i].SetAnchor(anchor) + cw, ch = s.children[i].Size() if s.horizontal { - d.Min.X += offsetX + anchor.X += cw } else { - d.Min.Y += offsetY + anchor.Y += ch } } @@ -41,21 +33,51 @@ func New(ops ...Option) elements.InitFunc { } type Stack struct { - blocks.Block mouse.NopHandler + anchor elements.Point horizontal bool childrenInit []elements.InitFunc - Children []elements.Element + children []elements.Element +} + +func (s *Stack) SetAnchor(a elements.Point) { + d := s.anchor.Delta(a) + for i := range s.Children() { + s.Children()[i].SetAnchor(s.Children()[i].Anchor().Add(d)) + } + s.anchor = a +} + +func (s *Stack) Anchor() elements.Point { + return s.anchor +} + +func (s *Stack) Size() (w, h int) { + var cw, ch int + for i := range s.children { + cw, ch = s.children[i].Size() + if s.horizontal { + w += cw + if h < ch { + h = ch + } + } else { + h += ch + if w < cw { + w = cw + } + } + } + + return } func (s *Stack) Draw(image *ebiten.Image) { - s.Block.Draw(image) - - for i := range s.Children { - s.Children[i].Draw(image) + for i := range s.children { + s.children[i].Draw(image) } } -func (s *Stack) GetChildren() []elements.Element { - return s.Children +func (s *Stack) Children() []elements.Element { + return s.children } diff --git a/common/elements/v1/table.go b/common/elements/v1/table.go deleted file mode 100644 index 4250cef..0000000 --- a/common/elements/v1/table.go +++ /dev/null @@ -1,93 +0,0 @@ -package elements - -// -//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/common/ux/v1/ux.go b/common/ux/v1/ux.go index af6688a..0bacbc5 100644 --- a/common/ux/v1/ux.go +++ b/common/ux/v1/ux.go @@ -9,6 +9,7 @@ import ( var ( FontFace font.Face = basicfont.Face7x13 + FontColor color.Color = color.Black BackgroundColor color.Color = color.White Scale float64 = 1.0 ) diff --git a/tools/component_test/editor.go b/tools/component_test/editor.go index d009a65..daeee5c 100644 --- a/tools/component_test/editor.go +++ b/tools/component_test/editor.go @@ -1,40 +1,50 @@ 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/buttons" "git.vezzani.net/ben/games/common/elements/v1/mouse" "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.BlockOpt(blocks.BackgroundColor(color.White)), + //stacks.BlockOpt(blocks.Name("parent")), stacks.Children( stacks.New( - stacks.BlockOpt(blocks.Name("left")), + //stacks.BlockOpt(blocks.Name("left")), stacks.Children( buttons.New( + buttons.BlockOpt(blocks.Size(100, 100)), + buttons.Label("hello"), buttons.BlockOpt(blocks.BackgroundColor(colornames.Green)), buttons.OnClick(func(ms mouse.State) { println("green") }), ), - blocks.New(blocks.BackgroundColor(colornames.Yellow)), - )), + blocks.New( + blocks.Size(100, 100), + blocks.BackgroundColor(colornames.Yellow), + ), + ), + ), stacks.New( - stacks.BlockOpt(blocks.Name("right")), + //stacks.BlockOpt(blocks.Name("right")), stacks.Children( - blocks.New(blocks.BackgroundColor(colornames.Blue)), - blocks.New(blocks.BackgroundColor(colornames.Red)), + blocks.New( + blocks.Size(100, 100), + blocks.BackgroundColor(colornames.Blue), + ), + blocks.New( + blocks.Size(100, 100), + blocks.BackgroundColor(colornames.Red), + ), ), ), ), @@ -61,13 +71,13 @@ func (e *editor) Draw(screen *ebiten.Image) { } func (e *editor) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { - if e.bounds.Width != float64(outsideWidth) || e.bounds.Height != float64(outsideHeight) { + if e.bounds.Width != outsideWidth || e.bounds.Height != outsideHeight { e.bounds = elements.Bounds{ Min: elements.Point{}, - Width: float64(outsideWidth), - Height: float64(outsideHeight), + Width: outsideWidth, + Height: outsideHeight, } - e.root = root(e.bounds) + e.root = root() e.handleMouse = mouse.Handler(e.root) }