diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2025-08-06 18:02:44 +0200 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-08-20 15:57:35 +0200 |
| commit | 778487d25a79721539cea1e47331e3c2f985c8cc (patch) | |
| tree | 1762762f69895465e7d1a0c9844ed9dbb28b2a71 /go/ui | |
| parent | 121016509135952eb1144abdaa8aeabe5bebe682 (diff) | |
| download | muhqs-game-778487d25a79721539cea1e47331e3c2f985c8cc.tar.gz muhqs-game-778487d25a79721539cea1e47331e3c2f985c8cc.zip | |
rework hover detection
Diffstat (limited to 'go/ui')
| -rw-r--r-- | go/ui/collection.go | 20 | ||||
| -rw-r--r-- | go/ui/eventHandlers.go | 20 | ||||
| -rw-r--r-- | go/ui/hoverDetector.go | 45 | ||||
| -rw-r--r-- | go/ui/hoverable.go | 48 | ||||
| -rw-r--r-- | go/ui/mapView.go | 5 | ||||
| -rw-r--r-- | go/ui/textBox.go | 4 | ||||
| -rw-r--r-- | go/ui/touchManager.go | 30 | ||||
| -rw-r--r-- | go/ui/update.go | 24 | ||||
| -rw-r--r-- | go/ui/widget.go | 4 |
9 files changed, 122 insertions, 78 deletions
diff --git a/go/ui/collection.go b/go/ui/collection.go index 78944814..c5f59540 100644 --- a/go/ui/collection.go +++ b/go/ui/collection.go @@ -12,9 +12,9 @@ import ( // another Collection. type Collection struct { Width, Height int - hoverDetector hoverDetector widgets []Widget focused Widget + hovering Widget } type CollectionInterface interface { @@ -39,10 +39,6 @@ func (c *Collection) Clear() { c.widgets = []Widget{} } -func (c *Collection) ResetHover() { - c.hoverDetector.reset(0, 0) -} - func (c *Collection) AddWidget(w Widget) { if w == nil { log.Panicf("Adding nil widget to collection") @@ -191,6 +187,16 @@ func (c *Collection) Update() error { passInput(ev, txt) ConsumeInput(i) } + case HoverEnd: + if c.hovering != nil { + w.Hover(false, ev.X, ev.Y) + c.hovering = nil + } + case HoverStart: + if w.IsHoverable() { + w.Hover(true, ev.X, ev.Y) + c.hovering = w + } } // handle focused text input } else if ev.Kind == Text { @@ -201,10 +207,6 @@ func (c *Collection) Update() error { } } - x, y := ebiten.CursorPosition() - activeWidget := c.FindWidgetAt(x, y) - c.hoverDetector.update(c, x, y, activeWidget) - return nil } diff --git a/go/ui/eventHandlers.go b/go/ui/eventHandlers.go index b81606f8..701190cf 100644 --- a/go/ui/eventHandlers.go +++ b/go/ui/eventHandlers.go @@ -1,6 +1,7 @@ package ui type ClickHandler = func(int, int) +type HoverHandler = func(bool, int, int) type ScrollHandler = func(int, int) type UpdateHandler = func() error type FocusHandler = func(bool) @@ -8,8 +9,8 @@ type FocusHandler = func(bool) type EventHandlers interface { IsClickable() bool Click(int, int) - // IsHoverable() bool - // Hover(int, int) + IsHoverable() bool + Hover(bool, int, int) IsScrollable() bool Scroll(int, int) IsUpdatable() bool @@ -40,6 +41,16 @@ func (m EventHandlersMap) Click(x, y int) { onclick.(ClickHandler)(x, y) } +func (m EventHandlersMap) IsHoverable() bool { + _, found := m["hover"] + return found +} + +func (m EventHandlersMap) Hover(start bool, x, y int) { + onhover := m["hover"] + onhover.(HoverHandler)(start, x, y) +} + func (m EventHandlersMap) IsScrollable() bool { _, found := m["update"] return found @@ -60,11 +71,6 @@ func (m EventHandlersMap) Update() error { return onupdate.(UpdateHandler)() } -func (m EventHandlersMap) IsHoverable() bool { - _, found := m["hover"] - return found -} - func (m EventHandlersMap) IsFocusable() bool { _, found := m["focus"] return found diff --git a/go/ui/hoverDetector.go b/go/ui/hoverDetector.go index 49161f4f..385b2ffd 100644 --- a/go/ui/hoverDetector.go +++ b/go/ui/hoverDetector.go @@ -3,39 +3,32 @@ package ui // A simple structure to detect if the cursor has not moved or started moving again. // hoverDetector.update must be called during each Update invocation to track the // frame and the current cursor position. -type hoverDetector struct { +type _hoverDetector struct { last_x, last_y int ticks int - hovering Hoverable } -func (h *hoverDetector) update(collection *Collection, x, y int, w Widget) { +const HOVER_THRESHOLD = 60 + +var ( + hoverDetector _hoverDetector + longtapHoverDetector _hoverDetector +) + +func (h *_hoverDetector) update(x, y int) (ev InputEvent, ch bool) { if x == h.last_x && y == h.last_y { - h.hover(collection, x, y, w) + h.ticks = h.ticks + 1 + if h.ticks == HOVER_THRESHOLD { + return InputEvent{HoverStart, x, y, nil}, true + } } else { - h.reset(x, y) + if h.ticks > HOVER_THRESHOLD { + ev = InputEvent{HoverEnd, h.last_x, h.last_y, nil} + ch = true + } + h.ticks = 0 h.last_x, h.last_y = x, y } -} - -func (h *hoverDetector) hover(collection *Collection, x, y int, w Widget) { - h.ticks++ - if w == nil || h.hovering != nil { - return - } - - if hoverable, ok := w.(Hoverable); ok && hoverable.onHover(h.ticks, x, y, collection) { - h.hovering = hoverable - } -} - -func (h *hoverDetector) reset(x, y int) { - if h.hovering != nil { - if !h.hovering.stopHover(h.ticks, x, y) { - return - } - h.hovering = nil - } - h.ticks = 0 + return } diff --git a/go/ui/hoverable.go b/go/ui/hoverable.go index 30415c8d..3f7e8def 100644 --- a/go/ui/hoverable.go +++ b/go/ui/hoverable.go @@ -5,63 +5,58 @@ import ( ) const ( - HOVER_THRESHOLD = 60 - HOVER_CARD_WIDTH = 500 HOVER_CARD_HEIGHT = 700 ) -type Hoverable interface { - onHover(ticks int, x, y int, c *Collection) bool - stopHover(ticks int, x, y int) bool -} - type hoverWidget struct { + c *Collection createHint func(x, y int) Widget reset func() xMax, yMax int } -func (h *hoverWidget) showHint(x, y int, c *Collection) { +func (h *hoverWidget) Hover(started bool, x, y int) { + if started { + h.onHover(x, y) + } else { + h.stopHover(x, y) + } +} + +func (h *hoverWidget) showHint(x, y int) { hint := h.createHint(x, y) if hint == nil { return } - c.AddWidget(hint) + h.c.AddWidget(hint) h.reset = func() { - c.RemoveWidget(hint) + h.c.RemoveWidget(hint) } } -func (h *hoverWidget) onHover(ticks int, x, y int, c *Collection) bool { +func (h *hoverWidget) onHover(x, y int) { if h.xMax == 0 || h.yMax == 0 { - width, height := c.Layout() + width, height := h.c.Layout() h.xMax = width h.yMax = height } - - if ticks == HOVER_THRESHOLD { - h.showHint(x, y, c) - return true - } - - return false + h.showHint(x, y) } -func (h *hoverWidget) stopHover(ticks int, x, y int) bool { - if h.reset != nil { - h.reset() - } - return true +func (h *hoverWidget) stopHover(x, y int) { + if h.reset != nil { h.reset() } } type hoverCardView struct { hoverWidget } -func (h *hoverCardView) init(w Widget) { +func (h *hoverCardView) init(w Widget, c *Collection) { + h.c = c h.createHint = func(x, y int) Widget { + obj := w.FindObjectAt(x, y) if obj == nil { return nil @@ -89,7 +84,8 @@ type hoverPermInfo struct { hoverWidget } -func (h *hoverPermInfo) init(w Widget) { +func (h *hoverPermInfo) init(w Widget, c *Collection) { + h.c = c h.createHint = func(x, y int) Widget { obj := w.FindObjectAt(x, y) if obj == nil { diff --git a/go/ui/mapView.go b/go/ui/mapView.go index d73f3573..36455d8c 100644 --- a/go/ui/mapView.go +++ b/go/ui/mapView.go @@ -48,7 +48,7 @@ type MapView struct { permanentsHighlights map[game.Permanent][]color.Color } -func NewMapView(g game.State) *MapView { +func NewMapView(g game.State, c *Collection) *MapView { vw := &MapView{ EventHandlersMap: NewEventHandlersMap(), gameState: g, @@ -56,7 +56,8 @@ func NewMapView(g game.State) *MapView { tileHighlights: make(map[game.Position][]color.Color), permanentsHighlights: make(map[game.Permanent][]color.Color), } - vw.hoverPermInfo.init(vw) + vw.hoverPermInfo.init(vw, c) + vw.RegisterHandler("hover", vw.hoverPermInfo.Hover) return vw } diff --git a/go/ui/textBox.go b/go/ui/textBox.go index 287c2a89..9f5ff0f4 100644 --- a/go/ui/textBox.go +++ b/go/ui/textBox.go @@ -144,7 +144,7 @@ type PocList struct { poc game.PileOfCards } -func NewPocList(centerX, bottomY int, poc game.PileOfCards) *PocList { +func NewPocList(centerX, bottomY int, poc game.PileOfCards, c *Collection) *PocList { w := &PocList{ TextBox: *(NewAutoTextBox(-1, -1, "").Centering(true)), centerX: centerX, @@ -152,7 +152,7 @@ func NewPocList(centerX, bottomY int, poc game.PileOfCards) *PocList { poc: poc, } - w.hoverCardView.init(w) + w.hoverCardView.init(w, c) w.renderImpl = func() *ebiten.Image { w.setText() diff --git a/go/ui/touchManager.go b/go/ui/touchManager.go index 462b9934..b675ff0e 100644 --- a/go/ui/touchManager.go +++ b/go/ui/touchManager.go @@ -22,20 +22,25 @@ import ( "github.com/hajimehoshi/ebiten/v2/inpututil" ) +const LONGTAP_THRESHOLD = 75 + type TouchType int const ( _tap TouchType = iota + 1 + _longtap _pan _pinch ) var touchTypes = struct { tap TouchType + longtap TouchType pan TouchType pinch TouchType }{ tap: _tap, + longtap: _longtap, pan: _pan, pinch: _pinch, } @@ -48,6 +53,7 @@ type TouchInput struct { pinch *pinch pan *pan + longtap *longtap taps []tap } @@ -99,9 +105,14 @@ func (in *TouchInput) Update() error { in.pan = nil } + if in.longtap != nil && id == in.longtap.id { + // FIXME: what about this frame's movement? + in.longtap = nil + } + // If this one has not been touched long, or moved far, then it's a tap. diff := hypotenuse(t.originX, t.originY, t.currX, t.currY) - if !t.wasPinch && !t.isPan && (t.duration <= 75 || diff < 5) { + if !t.wasPinch && !t.isPan && (t.duration <= LONGTAP_THRESHOLD || diff < 5) { in.taps = append(in.taps, tap{ x: t.currX, y: t.currY, @@ -165,6 +176,15 @@ func (in *TouchInput) Update() error { prevY: t.originY, } } + } else if t.duration > LONGTAP_THRESHOLD { + id := in.touchIDs[0] + t := in.touches[id] + t.isLongtap = true + in.longtap = &longtap{ + id: id, + tap: tap{t.originX, t.originY}, + duration: t.duration, + } } } @@ -175,7 +195,7 @@ type touch struct { originX, originY int currX, currY int duration int - wasPinch, isPan bool + wasPinch, isPan, isLongtap bool } type pinch struct { @@ -228,3 +248,9 @@ func (p *pan) Incremental() (float64, float64) { type tap struct { x, y int } + +type longtap struct { + tap + id ebiten.TouchID + duration int +} diff --git a/go/ui/update.go b/go/ui/update.go index 0bdb8428..c4ed3224 100644 --- a/go/ui/update.go +++ b/go/ui/update.go @@ -21,6 +21,12 @@ const ( Pan // Pinch represents a moving multi touch input. Pinch + // HoverStart represents the start of a hover input. + HoverStart + // HoverEnd represents the end of a hover input. + HoverEnd + // Longtap represents a longer touch input. + Longtap ) // InputEvent contains all information associated with an occurred user input. @@ -84,13 +90,27 @@ func Update() error { } + var hoverEv InputEvent + var hoverChange bool + if TouchManager.longtap != nil { + hoverEv, hoverChange = longtapHoverDetector.update(TouchManager.longtap.x, TouchManager.longtap.y) + } else { + hoverEv, hoverChange = longtapHoverDetector.update(-1, -1) + } + if hoverChange { + AppendInput(hoverEv) + } + // TODO: handle pinching if TouchManager.pinch != nil { } - // TODO: make hover detector a singleton - x, y := ebiten.CursorPosition() + hoverEv, hoverChange = hoverDetector.update(x, y) + if hoverChange { + AppendInput(hoverEv) + } + scrollX, scrollY := ebiten.Wheel() if scrollX != 0 || scrollY != 0 { AppendInput(InputEvent{Scroll, x, y, DistanceCtx{int(scrollX), int(scrollY)}}) diff --git a/go/ui/widget.go b/go/ui/widget.go index 4082c828..f9451a33 100644 --- a/go/ui/widget.go +++ b/go/ui/widget.go @@ -12,8 +12,8 @@ type Widget interface { IsClickable() bool Click(int, int) - // IsHoverable() bool - // Hover(int, int) + IsHoverable() bool + Hover(bool, int, int) IsScrollable() bool Scroll(int, int) IsUpdatable() bool |
