diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2025-06-17 11:43:34 -0500 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-08-20 15:57:21 +0200 |
| commit | 4206d7fe5cf450fbc1f9a16e8d57b85c08df3dab (patch) | |
| tree | c0dd008bcb47fe6e7af49e5b79ba928ace002cc7 | |
| parent | ab08e0b7c7ebc50549392e6de3908dfa61f1e973 (diff) | |
| download | muhqs-game-4206d7fe5cf450fbc1f9a16e8d57b85c08df3dab.tar.gz muhqs-game-4206d7fe5cf450fbc1f9a16e8d57b85c08df3dab.zip | |
make input events consumable
Having a input queue of consumable input events allow widgets to prevent
the further handling of the same event by different collections by
consuming the event.
| -rw-r--r-- | go/activities/draft.go | 2 | ||||
| -rw-r--r-- | go/activities/sealed.go | 2 | ||||
| -rw-r--r-- | go/client/keybindings.go | 1 | ||||
| -rw-r--r-- | go/client/startMenu.go | 2 | ||||
| -rw-r--r-- | go/dummy-ui/main.go | 4 | ||||
| -rw-r--r-- | go/ui/collection.go | 95 | ||||
| -rw-r--r-- | go/ui/touchManager.go | 8 | ||||
| -rw-r--r-- | go/ui/update.go | 74 |
8 files changed, 148 insertions, 40 deletions
diff --git a/go/activities/draft.go b/go/activities/draft.go index 10ce9671..119696ec 100644 --- a/go/activities/draft.go +++ b/go/activities/draft.go @@ -113,7 +113,7 @@ func (d *Draft) Update() error { d.presentPack(n) } - if err := ui.TouchManager.Update(); err != nil { + if err := ui.Update(); err != nil { return err } diff --git a/go/activities/sealed.go b/go/activities/sealed.go index 7438e89d..512bd604 100644 --- a/go/activities/sealed.go +++ b/go/activities/sealed.go @@ -59,7 +59,7 @@ func NewSealed(width, height int) *Sealed { } func (s *Sealed) Update() error { - if err := ui.TouchManager.Update(); err != nil { + if err := ui.Update(); err != nil { return err } diff --git a/go/client/keybindings.go b/go/client/keybindings.go index b39a0da4..2bfc52c7 100644 --- a/go/client/keybindings.go +++ b/go/client/keybindings.go @@ -17,6 +17,7 @@ type ( var tapToHandle ui.Tap func (g *Game) handleKeyBindings(bindings keyBindings) { + // FIXME: Use new ui.InputEvent queue for _k, a := range bindings { var remove bool diff --git a/go/client/startMenu.go b/go/client/startMenu.go index 10b9e23d..c3377a40 100644 --- a/go/client/startMenu.go +++ b/go/client/startMenu.go @@ -151,7 +151,7 @@ func (m *startMenu) Update() error { m.sealed = nil } - if err := ui.TouchManager.Update(); err != nil { + if err := ui.Update(); err != nil { return err } diff --git a/go/dummy-ui/main.go b/go/dummy-ui/main.go index cf49813e..c24d5031 100644 --- a/go/dummy-ui/main.go +++ b/go/dummy-ui/main.go @@ -21,6 +21,10 @@ func (a *app) Update() error { a.build() } + if err := ui.Update(); err != nil { + return err + } + return a.Collection.Update() } diff --git a/go/ui/collection.go b/go/ui/collection.go index bb7a4d53..9ccaa176 100644 --- a/go/ui/collection.go +++ b/go/ui/collection.go @@ -4,7 +4,6 @@ import ( "log" "github.com/hajimehoshi/ebiten/v2" - "github.com/hajimehoshi/ebiten/v2/inpututil" "golang.org/x/exp/slices" ) @@ -18,6 +17,16 @@ type Collection struct { focused Widget } +type CollectionInterface interface { + Widget + Widgets() []Widget + Clear() + AddWidget(Widget) + FindWidget(Widget) int + RemoveWidget(Widget) + RemoveWidgetAt(int) +} + func (c *Collection) Layout() (int, int) { return c.Width, c.Height } @@ -125,12 +134,18 @@ func (c *Collection) switchFocus(fcsbl Widget) { } func (c *Collection) handleTouchInputs() error { - for _, tap := range TouchManager.Taps { + for i := len(TouchManager.Taps) - 1; i >= 0; i-- { + tap := TouchManager.Taps[i] for i := len(c.widgets) - 1; i >= 0; i-- { w := c.widgets[i] if !w.Contains(tap.X, tap.Y) { continue } + // Skip collections their widgets will receive left over input + // during the following updateWidgets call, + if _, ok := w.(CollectionInterface); ok { + continue + } if w.IsClickable() { w.Click(tap.X, tap.Y) } @@ -139,6 +154,8 @@ func (c *Collection) handleTouchInputs() error { } else if c.focused != nil { c.switchFocus(nil) } + log.Printf("consume Tap for %T %v\n", w, w) + TouchManager.consumeTap(i) break } } @@ -157,12 +174,8 @@ func (c *Collection) handleTouchInputs() error { return nil } -func passInput(textInput *TextInput) { - var input []rune - input = ebiten.AppendInputChars(input) - if len(input) > 0 { - textInput.AddInput(input) - } +func passInput(ev InputEvent, textInput *TextInput) { + textInput.AddInput(ev.ctx.(TextCtx).in) textInput.HandleKey() } @@ -170,7 +183,7 @@ func (c *Collection) IsUpdatable() bool { return true } -func (c *Collection) Update() error { +func (c *Collection) updateWidgets() error { // Update all updatables for _, w := range c.widgets { if w.IsUpdatable() { @@ -179,43 +192,51 @@ func (c *Collection) Update() error { } } } + return nil +} + +func (c *Collection) Update() error { + defer c.updateWidgets() if err := c.handleTouchInputs(); err != nil { return err } - x, y := ebiten.CursorPosition() - - activeWidget := c.FindWidgetAt(x, y) - - c.hoverDetector.update(c, x, y, activeWidget) - - if activeWidget == nil { - if txt, ok := c.focused.(*TextInput); ok { - if ebiten.IsKeyPressed(ebiten.KeyA) { - txt.AddInput([]rune{'a'}) + for i := len(Input) - 1; i >= 0; i-- { + ev := Input[i] + w := c.FindWidgetAt(ev.x, ev.y) + if w != nil { + switch ev.kind { + case Click: + if w.IsClickable() && ev.ctx.(ClickCtx).btn == ebiten.MouseButtonLeft { + w.Click(ev.x, ev.y) + consumeInput(i) + } + // TODO: switch focus + case Scroll: + if w.IsScrollable() { + s := ev.ctx.(ScrollCtx) + w.Scroll(s.scrollX, s.scrollY) + consumeInput(i) + } + case Text: + if txt, ok := w.(*TextInput); ok { + passInput(ev, txt) + consumeInput(i) + } + } + // handle focused text input + } else if ev.kind == Text { + if txt, ok := c.focused.(*TextInput); ok { + passInput(ev, txt) + consumeInput(i) } - passInput(txt) - } - return nil - } - - scrollX, scrollY := ebiten.Wheel() - if scrollX != 0 || scrollY != 0 { - if activeWidget.IsScrollable() { - activeWidget.Scroll(int(scrollX), int(scrollY)) } } - if textInput, ok := activeWidget.(*TextInput); ok { - passInput(textInput) - } - - if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { - if activeWidget.IsClickable() { - activeWidget.Click(x, y) - } - } + x, y := ebiten.CursorPosition() + activeWidget := c.FindWidgetAt(x, y) + c.hoverDetector.update(c, x, y, activeWidget) return nil } diff --git a/go/ui/touchManager.go b/go/ui/touchManager.go index cedff82c..ef4448d5 100644 --- a/go/ui/touchManager.go +++ b/go/ui/touchManager.go @@ -49,6 +49,14 @@ type TouchInput struct { Taps []Tap } +func (t *TouchInput) consumeTap(i int) { + if len(t.Taps) == 1 { + t.Taps = []Tap{} + } else { + t.Taps[i] = t.Taps[len(t.Taps)-1] + } +} + var ( TouchManager *TouchInput ) diff --git a/go/ui/update.go b/go/ui/update.go new file mode 100644 index 00000000..fdb0f19e --- /dev/null +++ b/go/ui/update.go @@ -0,0 +1,74 @@ +package ui + +import ( + "log" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/inpututil" +) + +type InputType int + +const ( + Click InputType = iota + 1 + Scroll + Text +) + +type InputEvent struct { + kind InputType + x, y int + ctx any +} + +type ClickCtx struct { + btn ebiten.MouseButton +} + +type ScrollCtx struct { + scrollX, scrollY int +} + +type TextCtx struct { + in []rune +} + +// Input queue +var Input []InputEvent + +func consumeInput(i int) { + log.Println("consume input", i) + if len(Input) == 1 { + Input = []InputEvent{} + } else { + Input[i] = Input[len(Input)-1] + } +} + +// Update all ui singletons and collect all occurred input +func Update() error { + Input = []InputEvent{} + if err := TouchManager.Update(); err != nil { + return err + } + + // TODO: make hover detector a singleton + + x, y := ebiten.CursorPosition() + scrollX, scrollY := ebiten.Wheel() + if scrollX != 0 || scrollY != 0 { + Input = append(Input, InputEvent{Scroll, x, y, ScrollCtx{int(scrollX), int(scrollY)}}) + } + + var in []rune + in = ebiten.AppendInputChars(in) + if len(in) > 0 { + Input = append(Input, InputEvent{Text, x, y, TextCtx{in}}) + } + + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { + Input = append(Input, InputEvent{Click, x, y, ClickCtx{ebiten.MouseButtonLeft}}) + } + + return nil +} |
