aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2025-06-17 11:43:34 -0500
committerFlorian Fischer <florian.fischer@muhq.space>2025-08-20 15:57:21 +0200
commit4206d7fe5cf450fbc1f9a16e8d57b85c08df3dab (patch)
treec0dd008bcb47fe6e7af49e5b79ba928ace002cc7
parentab08e0b7c7ebc50549392e6de3908dfa61f1e973 (diff)
downloadmuhqs-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.go2
-rw-r--r--go/activities/sealed.go2
-rw-r--r--go/client/keybindings.go1
-rw-r--r--go/client/startMenu.go2
-rw-r--r--go/dummy-ui/main.go4
-rw-r--r--go/ui/collection.go95
-rw-r--r--go/ui/touchManager.go8
-rw-r--r--go/ui/update.go74
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
+}