From 7b6e1ce35aebd14872cbbd1989f5e44e56dbb8e0 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Fri, 26 Apr 2019 19:22:59 +0200 Subject: [PATCH] ui/pointer: simplify hit testing Serialize the implied tree of layers and handlers into a list that can be traversed from the end to collect handlers. This change will make it easier to switch op representation in a follow-up change. Signed-off-by: Elias Naur --- ui/pointer/queue.go | 81 ++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/ui/pointer/queue.go b/ui/pointer/queue.go index d2d44071..c7723c37 100644 --- a/ui/pointer/queue.go +++ b/ui/pointer/queue.go @@ -8,13 +8,19 @@ import ( ) type Queue struct { - // The root of the tree of ops relevant to pointer handling. - root ui.Op + hitTree []hitNode handlers map[Key]*handler pointers []pointerInfo scratch []Key } +type hitNode struct { + // The layer depth. + level int + // The handler, or nil for a layer. + key Key +} + type pointerInfo struct { id ID pressed bool @@ -33,12 +39,12 @@ type childOp interface { ChildOp() ui.Op } -func (q *Queue) collectHandlers(op ui.Op, t ui.Transform) ui.Op { +func (q *Queue) collectHandlers(op ui.Op, t ui.Transform, layer int) ui.Op { switch op := op.(type) { case ui.Ops: var all ui.Ops for _, op := range op { - if op := q.collectHandlers(op, t); op != nil { + if op := q.collectHandlers(op, t, layer); op != nil { if ops, ok := op.(ui.Ops); ok { all = append(all, ops...) } else { @@ -48,14 +54,17 @@ func (q *Queue) collectHandlers(op ui.Op, t ui.Transform) ui.Op { } return all case ui.OpLayer: - child := q.collectHandlers(op.ChildOp(), t) + layer++ + q.hitTree = append(q.hitTree, hitNode{level: layer}) + child := q.collectHandlers(op.ChildOp(), t, layer) if child == nil { return nil } return ui.OpLayer{Op: child} case ui.OpTransform: - return q.collectHandlers(op.ChildOp(), t.Mul(op.Transform)) + return q.collectHandlers(op.ChildOp(), t.Mul(op.Transform), layer) case OpHandler: + q.hitTree = append(q.hitTree, hitNode{level: layer, key: op.Key}) h, ok := q.handlers[op.Key] if !ok { h = new(handler) @@ -66,48 +75,37 @@ func (q *Queue) collectHandlers(op ui.Op, t ui.Transform) ui.Op { h.wantsGrab = h.wantsGrab || op.Grab return op case childOp: - return q.collectHandlers(op.ChildOp(), t) + return q.collectHandlers(op.ChildOp(), t, layer) default: return nil } } -func (q *Queue) opHit(handlers *[]Key, op ui.Op, pos f32.Point) (HitResult, bool) { - if op == nil { - return HitNone, false - } - switch op := op.(type) { - case ui.Ops: - hitRes := HitNone - var layer bool - for i := len(op) - 1; i >= 0; i-- { - op := op[i] - if _, ok := op.(ui.OpLayer); layer && ok { +func (q *Queue) opHit(handlers *[]Key, pos f32.Point) { + level := 1 << 30 + opaque := false + for i := len(q.hitTree) - 1; i >= 0; i-- { + n := q.hitTree[i] + if n.key == nil { + // Layer + if opaque { + opaque = false + // Skip sibling handlers. + level = n.level - 1 + } + } else if n.level <= level { + // Handler + h, ok := q.handlers[n.key] + if !ok { continue } - res, l := q.opHit(handlers, op, pos) - if res > hitRes { - hitRes = res + tpos := h.transform.InvTransform(pos) + res := h.area.Hit(tpos) + opaque = opaque || res == HitOpaque + if res != HitNone { + *handlers = append(*handlers, n.key) } - layer = layer || l } - return hitRes, layer - case ui.OpLayer: - res, layer := q.opHit(handlers, op.Op, pos) - return res, layer || res == HitOpaque - case OpHandler: - h, ok := q.handlers[op.Key] - if !ok { - return HitNone, false - } - tpos := h.transform.InvTransform(pos) - res := h.area.Hit(tpos) - if res != HitNone { - *handlers = append(*handlers, op.Key) - } - return res, false - default: - panic("unexpected op") } } @@ -127,7 +125,8 @@ func (q *Queue) Frame(op ui.Op) { h.events = h.events[:0] } } - q.root = q.collectHandlers(op, ui.Transform{}) + q.hitTree = q.hitTree[:0] + q.collectHandlers(op, ui.Transform{}, 0) } func (q *Queue) For(k Key) []Event { @@ -185,7 +184,7 @@ func (q *Queue) Push(e Event) { p := &q.pointers[pidx] if !p.pressed && (e.Type == Move || e.Type == Press) { p.handlers, q.scratch = q.scratch[:0], p.handlers - q.opHit(&p.handlers, q.root, e.Position) + q.opHit(&p.handlers, e.Position) // Drop handlers no longer hit. loop: for _, h := range q.scratch {