diff --git a/gesture/gesture.go b/gesture/gesture.go index 0ae744a8..075884ca 100644 --- a/gesture/gesture.go +++ b/gesture/gesture.go @@ -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 { diff --git a/io/pointer/pointer.go b/io/pointer/pointer.go index 9b6dac9d..c0c87bae 100644 --- a/io/pointer/pointer.go +++ b/io/pointer/pointer.go @@ -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 diff --git a/io/router/pointer.go b/io/router/pointer.go index 5221ed6a..e8e3c54e 100644 --- a/io/router/pointer.go +++ b/io/router/pointer.go @@ -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 + } + + // 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 } } - if !found { - h, ok := q.handlers[newH] - if !ok { - continue - } - 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) } } } diff --git a/io/router/pointer_test.go b/io/router/pointer_test.go index e9ffa98a..6ea80d7a 100644 --- a/io/router/pointer_test.go +++ b/io/router/pointer_test.go @@ -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) } }