~eliasnaur/gio

3b28c5d06738183e360e841d5d3df2c50a02ec58 — Elias Naur 3 years ago 23c2d44
io/router: simplify pointer event routing

- Drop pointer.Event.Hit in favour of Enter/Leave events.
- Track enter/leaves for each pointer.ID (updates #122). Add test.
- Resolve grabs once.
- Get rid of scratch slice.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
4 files changed, 139 insertions(+), 113 deletions(-)

M gesture/gesture.go
M io/pointer/pointer.go
M io/router/pointer.go
M io/router/pointer_test.go
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)
	}
}