mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-05 17:35:36 +00:00
gesture: support multiple click actions
Double click or tap actions are common in user interfaces and this commit adds support for it in the gesture package. ClickEvent has now a new field NumClicks that contains the number of successive clicks that occurred within 200ms. Fixes gio#101 Signed-off-by: Pierre.Curto <pierre.curto@gmail.com>
This commit is contained in:
+19
-1
@@ -22,11 +22,20 @@ import (
|
|||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The duration is somewhat arbitrary.
|
||||||
|
const doubleClickDuration = 200 * time.Millisecond
|
||||||
|
|
||||||
// Click detects click gestures in the form
|
// Click detects click gestures in the form
|
||||||
// of ClickEvents.
|
// of ClickEvents.
|
||||||
type Click struct {
|
type Click struct {
|
||||||
// state tracks the gesture state.
|
// state tracks the gesture state.
|
||||||
state ClickState
|
state ClickState
|
||||||
|
// clickedAt is the timestamp at which
|
||||||
|
// the last click occurred.
|
||||||
|
clickedAt time.Duration
|
||||||
|
// clicks is incremented if successive clicks
|
||||||
|
// are performed within a fixed duration.
|
||||||
|
clicks int
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClickState uint8
|
type ClickState uint8
|
||||||
@@ -39,6 +48,9 @@ type ClickEvent struct {
|
|||||||
Position f32.Point
|
Position f32.Point
|
||||||
Source pointer.Source
|
Source pointer.Source
|
||||||
Modifiers key.Modifiers
|
Modifiers key.Modifiers
|
||||||
|
// NumClicks records successive clicks occurring
|
||||||
|
// within a short duration of each other.
|
||||||
|
NumClicks int
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClickType uint8
|
type ClickType uint8
|
||||||
@@ -122,7 +134,13 @@ func (c *Click) Events(q event.Queue) []ClickEvent {
|
|||||||
wasPressed := c.state == StatePressed
|
wasPressed := c.state == StatePressed
|
||||||
c.state = StateNormal
|
c.state = StateNormal
|
||||||
if wasPressed {
|
if wasPressed {
|
||||||
events = append(events, ClickEvent{Type: TypeClick, Position: e.Position, Source: e.Source, Modifiers: e.Modifiers})
|
if e.Time-c.clickedAt < doubleClickDuration {
|
||||||
|
c.clicks++
|
||||||
|
} else {
|
||||||
|
c.clicks = 1
|
||||||
|
}
|
||||||
|
c.clickedAt = e.Time
|
||||||
|
events = append(events, ClickEvent{Type: TypeClick, Position: e.Position, Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks})
|
||||||
}
|
}
|
||||||
case pointer.Cancel:
|
case pointer.Cancel:
|
||||||
c.state = StateNormal
|
c.state = StateNormal
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package gesture
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gioui.org/io/event"
|
||||||
|
"gioui.org/io/pointer"
|
||||||
|
"gioui.org/io/router"
|
||||||
|
"gioui.org/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMouseClicks(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
label string
|
||||||
|
events []event.Event
|
||||||
|
clicks []int // number of combined clicks per click (single, double...)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
label: "single click",
|
||||||
|
events: mouseClickEvents(200 * time.Millisecond),
|
||||||
|
clicks: []int{1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "double click",
|
||||||
|
events: mouseClickEvents(
|
||||||
|
100*time.Millisecond,
|
||||||
|
100*time.Millisecond+doubleClickDuration-1),
|
||||||
|
clicks: []int{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "two single clicks",
|
||||||
|
events: mouseClickEvents(
|
||||||
|
100*time.Millisecond,
|
||||||
|
100*time.Millisecond+doubleClickDuration+1),
|
||||||
|
clicks: []int{1, 1},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.label, func(t *testing.T) {
|
||||||
|
var click Click
|
||||||
|
var ops op.Ops
|
||||||
|
click.Add(&ops)
|
||||||
|
|
||||||
|
var r router.Router
|
||||||
|
r.Frame(&ops)
|
||||||
|
r.Add(tc.events...)
|
||||||
|
|
||||||
|
events := click.Events(&r)
|
||||||
|
clicks := filterMouseClicks(events)
|
||||||
|
if got, want := len(clicks), len(tc.clicks); got != want {
|
||||||
|
t.Fatalf("got %d mouse clicks, expected %d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, click := range clicks {
|
||||||
|
if got, want := click.NumClicks, tc.clicks[i]; got != want {
|
||||||
|
t.Errorf("got %d combined mouse clicks, expected %d", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mouseClickEvents(times ...time.Duration) []event.Event {
|
||||||
|
press := pointer.Event{
|
||||||
|
Type: pointer.Press,
|
||||||
|
Source: pointer.Mouse,
|
||||||
|
Buttons: pointer.ButtonLeft,
|
||||||
|
}
|
||||||
|
events := make([]event.Event, 0, 2*len(times))
|
||||||
|
for _, t := range times {
|
||||||
|
release := press
|
||||||
|
release.Type = pointer.Release
|
||||||
|
release.Time = t
|
||||||
|
events = append(events, press, release)
|
||||||
|
}
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterMouseClicks(events []ClickEvent) []ClickEvent {
|
||||||
|
var clicks []ClickEvent
|
||||||
|
for _, ev := range events {
|
||||||
|
if ev.Type == TypeClick {
|
||||||
|
clicks = append(clicks, ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clicks
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user