M gesture/gesture.go => gesture/gesture.go +1 -1
@@ 145,7 145,7 @@ func (c *Click) Events(q event.Queue) []ClickEvent {
case pointer.Cancel:
c.state = StateNormal
case pointer.Press:
- if c.state == StatePressed || !e.Hit {
+ if c.state == StatePressed {
break
}
if e.Source == pointer.Mouse && e.Buttons != pointer.ButtonLeft {
M io/pointer/pointer.go => io/pointer/pointer.go +0 -5
@@ 31,11 31,6 @@ type Event struct {
Time time.Duration
// Buttons are the set of pressed mouse buttons for this event.
Buttons Buttons
- // Hit is set when the event was within the registered
- // area for the handler. Hit can be false when a pointer
- // was pressed within the hit area, and then dragged
- // outside it.
- Hit bool
// Position is the position of the event, relative to
// the current transformation, as set by op.TransformOp.
Position f32.Point
M io/router/pointer.go => io/router/pointer.go +85 -83
@@ 20,11 20,6 @@ type pointerQueue struct {
handlers map[event.Tag]*pointerHandler
pointers []pointerInfo
reader ops.Reader
- scratch []event.Tag
-
- // prev and curr are two additional scratch slices that track active
- // pointer event handlers from the previous and current frame
- prev, curr []event.Tag
}
type hitNode struct {
@@ 34,13 29,16 @@ type hitNode struct {
pass bool
// For handler nodes.
- key event.Tag
+ tag event.Tag
}
type pointerInfo struct {
id pointer.ID
pressed bool
handlers []event.Tag
+
+ // entered tracks the tags that contain the pointer.
+ entered []event.Tag
}
type pointerHandler struct {
@@ 98,7 96,7 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t o
next: node,
area: area,
pass: pass,
- key: op.Tag,
+ tag: op.Tag,
})
node = len(q.hitTree) - 1
h, ok := q.handlers[op.Tag]
@@ 110,7 108,7 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t o
h.active = true
h.area = area
h.transform = t
- h.wantsGrab = h.wantsGrab || op.Grab
+ h.wantsGrab = op.Grab
}
}
}
@@ 131,17 129,14 @@ func (q *pointerQueue) opHit(handlers *[]event.Tag, pos f32.Point) {
} else {
idx = n.next
}
- if n.key != nil {
- if _, exists := q.handlers[n.key]; exists {
- *handlers = append(*handlers, n.key)
+ if n.tag != nil {
+ if _, exists := q.handlers[n.tag]; exists {
+ *handlers = append(*handlers, n.tag)
}
}
}
}
-// TODO(whereswaldon): This method fails to handle the case in which a child
-// hit area extends outside of the boundaries of its parent. Such child hit
-// areas will not recieve some events as a result.
func (q *pointerQueue) hit(areaIdx int, p f32.Point) bool {
for areaIdx != -1 {
a := &q.areas[areaIdx]
@@ 176,20 171,41 @@ func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) {
q.collectHandlers(&q.reader, events, op.TransformOp{}, -1, -1, false)
for k, h := range q.handlers {
if !h.active {
- q.dropHandler(k, events)
+ q.dropHandlers(events, k)
delete(q.handlers, k)
}
+ if h.wantsGrab {
+ for _, p := range q.pointers {
+ if !p.pressed {
+ continue
+ }
+ for i, k2 := range p.handlers {
+ if k2 == k {
+ // Drop other handlers that lost their grab.
+ q.dropHandlers(events, p.handlers[i+1:]...)
+ q.dropHandlers(events, p.handlers[:i]...)
+ break
+ }
+ }
+ }
+ }
}
}
-func (q *pointerQueue) dropHandler(k event.Tag, events *handlerEvents) {
- events.Add(k, pointer.Event{Type: pointer.Cancel})
- q.handlers[k].wantsGrab = false
- for i := range q.pointers {
- p := &q.pointers[i]
- for i := len(p.handlers) - 1; i >= 0; i-- {
- if p.handlers[i] == k {
- p.handlers = append(p.handlers[:i], p.handlers[i+1:]...)
+func (q *pointerQueue) dropHandlers(events *handlerEvents, tags ...event.Tag) {
+ for _, k := range tags {
+ events.Add(k, pointer.Event{Type: pointer.Cancel})
+ for i := range q.pointers {
+ p := &q.pointers[i]
+ for i := len(p.handlers) - 1; i >= 0; i-- {
+ if p.handlers[i] == k {
+ p.handlers = append(p.handlers[:i], p.handlers[i+1:]...)
+ }
+ }
+ for i := len(p.entered) - 1; i >= 0; i-- {
+ if p.entered[i] == k {
+ p.entered = append(p.entered[:i], p.entered[i+1:]...)
+ }
}
}
}
@@ 200,7 216,7 @@ func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
if e.Type == pointer.Cancel {
q.pointers = q.pointers[:0]
for k := range q.handlers {
- q.dropHandler(k, events)
+ q.dropHandlers(events, k)
}
return
}
@@ 216,86 232,72 @@ func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
pidx = len(q.pointers) - 1
}
p := &q.pointers[pidx]
- if !p.pressed && (e.Type == pointer.Move || e.Type == pointer.Press) {
- p.handlers, q.scratch = q.scratch[:0], p.handlers
- q.opHit(&p.handlers, e.Position)
+
+ q.deliverEnterLeaveEvents(p, events, e)
+ if e.Type == pointer.Release {
+ q.deliverEvent(p, events, e)
+ p.pressed = false
+ }
+ if !p.pressed {
if e.Type == pointer.Press {
p.pressed = true
}
+ p.handlers = p.handlers[:0]
+ q.opHit(&p.handlers, e.Position)
+ q.deliverEnterLeaveEvents(p, events, e)
}
- if p.pressed {
- // Resolve grabs.
- q.scratch = q.scratch[:0]
- for i, k := range p.handlers {
- h := q.handlers[k]
- if h.wantsGrab {
- q.scratch = append(q.scratch, p.handlers[:i]...)
- q.scratch = append(q.scratch, p.handlers[i+1:]...)
- break
- }
- }
- // Drop handlers that lost their grab.
- for _, k := range q.scratch {
- q.dropHandler(k, events)
- }
+ if e.Type != pointer.Release {
+ q.deliverEvent(p, events, e)
}
- if e.Type == pointer.Release {
+ if !p.pressed && len(p.entered) == 0 {
+ // No longer need to track pointer.
q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...)
}
+}
- // Deliver enter and leave events for pointers that entered or left a hit area.
- q.curr, q.prev = q.prev[:0], q.curr
- q.opHit(&q.curr, e.Position)
- q.deliverEventsToMissingHandlers(q.prev, q.curr, pointer.Enter, e, events)
- q.deliverEventsToMissingHandlers(q.curr, q.prev, pointer.Leave, e, events)
-
+func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e pointer.Event) {
for _, k := range p.handlers {
h := q.handlers[k]
e := e
if p.pressed && len(p.handlers) == 1 {
e.Priority = pointer.Grabbed
}
- e.Hit = q.hit(h.area, e.Position)
e.Position = h.transform.Invert().Transform(e.Position)
+
events.Add(k, e)
- if e.Type == pointer.Release {
- // Release grab when the number of grabs reaches zero.
- grabs := 0
- for _, p := range q.pointers {
- if p.pressed && len(p.handlers) == 1 && p.handlers[0] == k {
- grabs++
- }
- }
- if grabs == 0 {
- h.wantsGrab = false
- }
- }
}
}
-// deliverEventsToMissingHandlers compares the a and b handler lists to find all
-// handlers in b that are missing from a. It then sends an event templated off of
-// evTemplate but with the type specified by evType.
-//
-// This is useful for delivering pointer.Enter and pointer.Leave events.
-func (q *pointerQueue) deliverEventsToMissingHandlers(a, b []event.Tag, evType pointer.Type, evTemplate pointer.Event, events *handlerEvents) {
- for _, newH := range b {
- found := false
- for _, oldH := range a {
- if newH == oldH {
- found = true
- }
+func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEvents, e pointer.Event) {
+ for _, k := range p.handlers {
+ h := q.handlers[k]
+ e := e
+ if p.pressed && len(p.handlers) == 1 {
+ e.Priority = pointer.Grabbed
}
- if !found {
- h, ok := q.handlers[newH]
- if !ok {
- continue
+
+ // Hit-test to deliver Enter/Leave events. Consider non-mouse
+ // events leaving when they're Released.
+ hit := (e.Source == pointer.Mouse || p.pressed) && q.hit(h.area, e.Position)
+ entered := -1
+ for i, k2 := range p.entered {
+ if k2 == k {
+ entered = i
+ break
}
- ev := evTemplate
- ev.Hit = q.hit(h.area, evTemplate.Position)
- ev.Position = h.transform.Invert().Transform(evTemplate.Position)
- ev.Type = evType
- events.Add(newH, ev)
+ }
+
+ e.Position = h.transform.Invert().Transform(e.Position)
+
+ switch {
+ case !hit && entered != -1:
+ p.entered = append(p.entered[:entered], p.entered[entered+1:]...)
+ e.Type = pointer.Leave
+ events.Add(k, e)
+ case hit && entered == -1:
+ p.entered = append(p.entered, k)
+ e.Type = pointer.Enter
+ events.Add(k, e)
}
}
}
M io/router/pointer_test.go => io/router/pointer_test.go +53 -24
@@ 5,6 5,7 @@ package router
import (
"fmt"
"image"
+ "reflect"
"testing"
"gioui.org/f32"
@@ 344,6 345,44 @@ func TestPointerActiveInputDisappears(t *testing.T) {
assertEventSequence(t, r.Events(handler1), pointer.Cancel)
}
+func TestMultitouch(t *testing.T) {
+ var ops op.Ops
+
+ // Add two separate handlers.
+ h1, h2 := new(int), new(int)
+ addPointerHandler(&ops, h1, image.Rect(0, 0, 100, 100))
+ addPointerHandler(&ops, h2, image.Rect(0, 100, 100, 200))
+
+ h1pt, h2pt := f32.Pt(0, 0), f32.Pt(0, 100)
+ var p1, p2 pointer.ID = 0, 1
+
+ var r Router
+ r.Frame(&ops)
+ r.Add(
+ pointer.Event{
+ Type: pointer.Press,
+ Position: h1pt,
+ PointerID: p1,
+ },
+ )
+ r.Add(
+ pointer.Event{
+ Type: pointer.Press,
+ Position: h2pt,
+ PointerID: p2,
+ },
+ )
+ r.Add(
+ pointer.Event{
+ Type: pointer.Release,
+ Position: h2pt,
+ PointerID: p2,
+ },
+ )
+ assertEventSequence(t, r.Events(h1), pointer.Cancel, pointer.Enter, pointer.Press)
+ assertEventSequence(t, r.Events(h2), pointer.Cancel, pointer.Enter, pointer.Press, pointer.Release)
+}
+
// addPointerHandler adds a pointer.InputOp for the tag in a
// rectangular area.
func addPointerHandler(ops *op.Ops, tag event.Tag, area image.Rectangle) {
@@ 354,36 393,26 @@ func addPointerHandler(ops *op.Ops, tag event.Tag, area image.Rectangle) {
pointer.InputOp{Tag: tag}.Add(ops)
}
-// toTypes converts a sequence of event.Event to their pointer.Types. It assumes
+// pointerTypes converts a sequence of event.Event to their pointer.Types. It assumes
// that all input events are of underlying type pointer.Event, and thus will
// panic if some are not.
-func toTypes(events []event.Event) []pointer.Type {
- out := make([]pointer.Type, len(events))
- for i, event := range events {
- out[i] = event.(pointer.Event).Type
+func pointerTypes(events []event.Event) []pointer.Type {
+ var types []pointer.Type
+ for _, e := range events {
+ if e, ok := e.(pointer.Event); ok {
+ types = append(types, e.Type)
+ }
}
- return out
+ return types
}
// assertEventSequence ensures that the provided actualEvents match the expected event types
-// in the provided order
-func assertEventSequence(t *testing.T, actualEvents []event.Event, expected ...pointer.Type) {
- if len(actualEvents) != len(expected) {
- t.Errorf("expected %v events, got %v", expected, toTypes(actualEvents))
- }
- for i, event := range actualEvents {
- pointerEvent, ok := event.(pointer.Event)
- if !ok {
- t.Errorf("actualEvents[%d] is not a pointer event, type %T", i, event)
- continue
- }
- if len(expected) <= i {
- continue
- }
- if pointerEvent.Type != expected[i] {
- t.Errorf("actualEvents[%d] has type %s, expected %s", i, pointerEvent.Type.String(), expected[i].String())
- continue
- }
+// in the provided order.
+func assertEventSequence(t *testing.T, events []event.Event, expected ...pointer.Type) {
+ t.Helper()
+ got := pointerTypes(events)
+ if !reflect.DeepEqual(got, expected) {
+ t.Errorf("expected %v events, got %v", expected, got)
}
}