aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2025-08-06 18:02:44 +0200
committerFlorian Fischer <florian.fischer@muhq.space>2025-08-20 15:57:35 +0200
commit778487d25a79721539cea1e47331e3c2f985c8cc (patch)
tree1762762f69895465e7d1a0c9844ed9dbb28b2a71
parent121016509135952eb1144abdaa8aeabe5bebe682 (diff)
downloadmuhqs-game-778487d25a79721539cea1e47331e3c2f985c8cc.tar.gz
muhqs-game-778487d25a79721539cea1e47331e3c2f985c8cc.zip
rework hover detection
-rw-r--r--go/activities/draft.go3
-rw-r--r--go/activities/sealed.go6
-rw-r--r--go/client/game.go12
-rw-r--r--go/ui/collection.go20
-rw-r--r--go/ui/eventHandlers.go20
-rw-r--r--go/ui/hoverDetector.go45
-rw-r--r--go/ui/hoverable.go48
-rw-r--r--go/ui/mapView.go5
-rw-r--r--go/ui/textBox.go4
-rw-r--r--go/ui/touchManager.go30
-rw-r--r--go/ui/update.go24
-rw-r--r--go/ui/widget.go4
12 files changed, 137 insertions, 84 deletions
diff --git a/go/activities/draft.go b/go/activities/draft.go
index 742855c0..53e762a6 100644
--- a/go/activities/draft.go
+++ b/go/activities/draft.go
@@ -248,7 +248,8 @@ func (d *Draft) startDraft(desc string, setList string, aiN int) error {
d.Width-SEALED_DECK_STRIP_WIDTH/2,
d.Height-DRAFT_BUTTON_HEIGHT-20,
d.player.Deck,
- )
+ &d.Collection)
+
d.deckList.Bg(ui.Gray)
{
cardsInDeck := 0
diff --git a/go/activities/sealed.go b/go/activities/sealed.go
index 512bd604..1399e144 100644
--- a/go/activities/sealed.go
+++ b/go/activities/sealed.go
@@ -101,7 +101,11 @@ func (s *Sealed) startSealed(setList string) {
setDeck.MoveInto(s.pool)
}
- deckList := ui.NewPocList(s.Width-SEALED_DECK_STRIP_WIDTH/2, s.Height-SEALED_BUTTON_HEIGHT-20, s.Deck)
+ deckList := ui.NewPocList(
+ s.Width-SEALED_DECK_STRIP_WIDTH/2,
+ s.Height-SEALED_BUTTON_HEIGHT-20,
+ s.Deck,
+ &s.Collection)
deckList.Bg(ui.Gray)
s.AddWidget(deckList)
diff --git a/go/client/game.go b/go/client/game.go
index d525a49c..f726c95d 100644
--- a/go/client/game.go
+++ b/go/client/game.go
@@ -122,7 +122,7 @@ func (g *Game) loadMap(mapName string) *Game {
}
func (g *Game) initMapUi() {
- g.mapView = ui.NewMapView(g.gameState)
+ g.mapView = ui.NewMapView(g.gameState, &g.Collection)
g.AddWidget(g.mapView)
storeTiles := g.gameState.Map().FilterTiles(func(t *game.Tile) bool { return t.Type == game.TileTypes.Store })
@@ -179,7 +179,7 @@ func (g *Game) initPlayerUi(player *game.Player) *Game {
x = g.discardPileButton.X + g.discardPileButton.Width/2
y = g.Height - DEFAULT_BUTTON_HEIGHT
- g.discardPileView = ui.NewPocList(x, y, g.activePlayer().DiscardPile)
+ g.discardPileView = ui.NewPocList(x, y, g.activePlayer().DiscardPile, &g.Collection)
if !g.storesOnMap {
x = g.stateBar.Width + g.discardPileButton.Width
@@ -199,7 +199,7 @@ func (g *Game) initPlayerUi(player *game.Player) *Game {
x = g.storeButton.X + g.storeButton.Width/2
y = g.Height - DEFAULT_BUTTON_HEIGHT
- g.storeView = ui.NewPocList(x, y, g.activePlayer().Store)
+ g.storeView = ui.NewPocList(x, y, g.activePlayer().Store, &g.Collection)
}
return g
@@ -461,6 +461,10 @@ func (g *Game) updateButtons() {
g.passButton.UpdateLabel(passButtonLabel)
}
+func (g *Game) ResetHover() {
+ ui.AppendInput(ui.InputEvent{ui.HoverEnd, -1, -1, nil})
+}
+
func (g *Game) reset() {
g.removeChoice()
g.selectedObject = nil
@@ -582,7 +586,7 @@ func (g *Game) handleSelection(obj any, x, y int) {
g.hideStore()
}
- g.storeView = ui.NewPocList(x, y, g.gameState.Map().StoreOn(obj.Position))
+ g.storeView = ui.NewPocList(x, y, g.gameState.Map().StoreOn(obj.Position), &g.Collection)
g.showStore()
}
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