aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2025-04-29 10:24:39 -0500
committerFlorian Fischer <florian.fischer@muhq.space>2025-05-01 09:46:15 -0500
commit39a35e9f498b39926d4e4a4cfbe154bb8395e9eb (patch)
tree995d2450bae27a33e4de9cbd7cd17dff1cb18e6d
parent644d848e9e67f408166141027a098234beb8f442 (diff)
downloadmuhqs-game-39a35e9f498b39926d4e4a4cfbe154bb8395e9eb.tar.gz
muhqs-game-39a35e9f498b39926d4e4a4cfbe154bb8395e9eb.zip
add a singleton to handle touch inputs
-rw-r--r--go/TODO1
-rw-r--r--go/ui/touchManager.go220
2 files changed, 221 insertions, 0 deletions
diff --git a/go/TODO b/go/TODO
index ede55fc0..577121ca 100644
--- a/go/TODO
+++ b/go/TODO
@@ -7,5 +7,6 @@
* implement target selection for triggers
* implement draft
* implement game log
+* implement LongTap as hover equivalent
* finish AIs
* tyrant
diff --git a/go/ui/touchManager.go b/go/ui/touchManager.go
new file mode 100644
index 00000000..cedff82c
--- /dev/null
+++ b/go/ui/touchManager.go
@@ -0,0 +1,220 @@
+// Copyright 2021 Huw Griffiths
+// Copyright 2025 Florian Fischer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ui
+
+import (
+ "math"
+
+ "github.com/hajimehoshi/ebiten/v2"
+ "github.com/hajimehoshi/ebiten/v2/inpututil"
+)
+
+type TouchType int
+
+const (
+ _tap TouchType = iota + 1
+ _pan
+ _pinch
+)
+
+var TouchTypes = struct {
+ Tap TouchType
+ Pan TouchType
+ Pinch TouchType
+}{Tap: _tap,
+ Pan: _pan,
+ Pinch: _pinch}
+
+// TouchInput is a "manager" for touch input and provides logical encapsulations of
+// touch interactions like panning, pinching, and tapping.
+type TouchInput struct {
+ touchIDs []ebiten.TouchID
+ touches map[ebiten.TouchID]*touch
+
+ Pinch *pinch
+ Pan *pan
+ Taps []Tap
+}
+
+var (
+ TouchManager *TouchInput
+)
+
+func init() {
+ TouchManager = NewTouchInput()
+}
+
+func NewTouchInput() *TouchInput {
+ return &TouchInput{
+ touches: map[ebiten.TouchID]*touch{},
+ }
+}
+
+// hypotenuse of a right triangle.
+func hypotenuse(xa, ya, xb, yb int) float64 {
+ x := math.Abs(float64(xa - xb))
+ y := math.Abs(float64(ya - yb))
+ return math.Sqrt(x*x + y*y)
+}
+
+// Update the touch input manager.
+// After a call to Update, if we have two touches, then we have a pinch, if we
+// have one touch, held longer than (N)ms, then we have a pan.
+// If we have quickly released presses, then they are represented in taps.
+func (in *TouchInput) Update() error {
+ in.Taps = []Tap{}
+
+ // What's gone?
+ for id, t := range in.touches {
+ if inpututil.IsTouchJustReleased(id) {
+ if in.Pinch != nil && (id == in.Pinch.id1 || id == in.Pinch.id2) {
+ // FIXME: what about this frame's movement?
+ in.Pinch = nil
+ }
+ if in.Pan != nil && id == in.Pan.id {
+ // FIXME: what about this frame's movement?
+ in.Pan = 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) {
+ in.Taps = append(in.Taps, Tap{
+ X: t.currX,
+ Y: t.currY,
+ })
+ }
+
+ delete(in.touches, id)
+ }
+ }
+
+ // What's new?
+ in.touchIDs = inpututil.AppendJustPressedTouchIDs(in.touchIDs[:0])
+ for _, id := range in.touchIDs {
+ x, y := ebiten.TouchPosition(id)
+ in.touches[ebiten.TouchID(id)] = &touch{
+ originX: x, originY: y,
+ currX: x, currY: y,
+ }
+ }
+
+ // What's going on?
+ in.touchIDs = ebiten.AppendTouchIDs(in.touchIDs[:0])
+ for _, id := range in.touchIDs {
+ t := in.touches[id]
+ t.duration = inpututil.TouchPressDuration(id)
+ t.currX, t.currY = ebiten.TouchPosition(id)
+ }
+
+ if len(in.touches) == 2 {
+ // Potential pinch?
+ // If the diff between their origins is different to the diff between
+ // their currents and if these two are not already a pinch, then this is
+ // a new pinch!
+ id1, id2 := in.touchIDs[0], in.touchIDs[1]
+ t1, t2 := in.touches[id1], in.touches[id2]
+ originDiff := hypotenuse(t1.originX, t1.originY, t2.originX, t2.originY)
+ currDiff := hypotenuse(t1.currX, t1.currY, t2.currX, t2.currY)
+ if in.Pinch == nil && in.Pan == nil && math.Abs(originDiff-currDiff) > 3 {
+ t1.wasPinch = true
+ t2.wasPinch = true
+ in.Pinch = &pinch{
+ id1: id1,
+ id2: id2,
+ originH: originDiff,
+ prevH: originDiff,
+ }
+ }
+ } else if len(in.touches) == 1 {
+ // Potential pan.
+ id := in.touchIDs[0]
+ t := in.touches[id]
+ if !t.wasPinch && in.Pan == nil && in.Pinch == nil {
+ diff := math.Abs(hypotenuse(t.originX, t.originY, t.currX, t.currY))
+ if diff > 0.25 {
+ t.isPan = true
+ in.Pan = &pan{
+ id: id,
+ originX: t.originX,
+ originY: t.originY,
+ prevX: t.originX,
+ prevY: t.originY,
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+type touch struct {
+ originX, originY int
+ currX, currY int
+ duration int
+ wasPinch, isPan bool
+}
+
+type pinch struct {
+ id1, id2 ebiten.TouchID
+ originH float64
+ prevH float64
+}
+
+func (p *pinch) currentH() float64 {
+ x1, y1 := ebiten.TouchPosition(p.id1)
+ x2, y2 := ebiten.TouchPosition(p.id2)
+ return hypotenuse(x1, y1, x2, y2)
+}
+
+func (p *pinch) Total() float64 {
+ return -(p.currentH() - p.originH)
+}
+
+// Incremental zoom that has occurred in this frame.
+func (p *pinch) Incremental() float64 {
+ curr := p.currentH()
+ delta := curr - p.prevH
+ p.prevH = curr
+ return -delta
+}
+
+type pan struct {
+ id ebiten.TouchID
+
+ prevX, prevY int
+ originX, originY int
+}
+
+// Total panning that has occurred since this pan started.
+func (p *pan) Total() (float64, float64) {
+ currX, currY := ebiten.TouchPosition(p.id)
+ return float64(currX - p.originX), float64(currY - p.originY)
+}
+
+// Incremental panning that has occurred in this frame.
+func (p *pan) Incremental() (float64, float64) {
+ currX, currY := ebiten.TouchPosition(p.id)
+ deltaX, deltaY := currX-p.prevX, currY-p.prevY
+
+ p.prevX, p.prevY = currX, currY
+
+ return float64(deltaX), float64(deltaY)
+}
+
+type Tap struct {
+ X, Y int
+}