From f44ccec04361f090a930ac7350c4641c3d0d1729 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Wed, 10 Jul 2019 22:34:24 +0200 Subject: [PATCH] ui/pointer: simplify pointer pass through Get rid of the confused LayerOp and the transparent property from AreaOp. Add an explicit PassOp to specify whether pointer events pass-through the current area. Let AreaOp swallow events even when no handlers are active for the area. That behaviour is less surprising and allow clients to disable a widget by keeping its areas but leave out its handlers. Simplify the pointer.HitResult enum to just a bool: hit or no hit. Finally, simplify the pointer queue by tracking parent areas and node with indices. --- ui/input/pointer.go | 155 ++++++++++++++++++----------------------- ui/internal/ops/ops.go | 3 + ui/layout/flex.go | 1 - ui/layout/list.go | 1 - ui/layout/stack.go | 1 - ui/pointer/pointer.go | 48 ++++++++----- ui/ui.go | 23 +----- 7 files changed, 103 insertions(+), 129 deletions(-) diff --git a/ui/input/pointer.go b/ui/input/pointer.go index 9d699cd0..4bd79457 100644 --- a/ui/input/pointer.go +++ b/ui/input/pointer.go @@ -11,17 +11,20 @@ import ( type pointerQueue struct { hitTree []hitNode + areas []areaNode handlers map[Key]*pointerHandler pointers []pointerInfo reader ui.OpsReader scratch []Key - areas areaStack } type hitNode struct { - // The layer depth. - level int - // The handler, or nil for a layer. + next int + area int + // Pass tracks the most recent PassOp mode. + pass bool + + // For handler nodes. key Key } @@ -32,26 +35,19 @@ type pointerInfo struct { } type pointerHandler struct { - area areaIntersection + area int active bool transform ui.Transform wantsGrab bool } -type area struct { +type areaNode struct { trans ui.Transform + next int area pointer.AreaOp } -type areaIntersection []area - -type areaStack struct { - stack []int - areas []area - backing []area -} - -func (q *pointerQueue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer int, events handlerEvents) { +func (q *pointerQueue) collectHandlers(r *ui.OpsReader, events handlerEvents, t ui.Transform, area, node int, pass bool) { for { encOp, ok := r.Decode() if !ok { @@ -59,18 +55,24 @@ func (q *pointerQueue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer in } switch ops.OpType(encOp.Data[0]) { case ops.TypePush: - q.areas.push() - q.collectHandlers(r, t, layer, events) + q.collectHandlers(r, events, t, area, node, pass) case ops.TypePop: - q.areas.pop() return - case ops.TypeLayer: - layer++ - q.hitTree = append(q.hitTree, hitNode{level: layer}) + case ops.TypePass: + var op pointer.PassOp + op.Decode(encOp.Data) + pass = op.Pass case ops.TypeArea: var op pointer.AreaOp op.Decode(encOp.Data) - q.areas.add(t, op) + q.areas = append(q.areas, areaNode{trans: t, next: area, area: op}) + area = len(q.areas) - 1 + q.hitTree = append(q.hitTree, hitNode{ + next: node, + area: area, + pass: pass, + }) + node = len(q.hitTree) - 1 case ops.TypeTransform: var op ui.TransformOp op.Decode(encOp.Data) @@ -78,7 +80,13 @@ func (q *pointerQueue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer in case ops.TypePointerHandler: var op pointer.HandlerOp op.Decode(encOp.Data, encOp.Refs) - q.hitTree = append(q.hitTree, hitNode{level: layer, key: op.Key}) + q.hitTree = append(q.hitTree, hitNode{ + next: node, + area: area, + pass: pass, + key: op.Key, + }) + node = len(q.hitTree) - 1 h, ok := q.handlers[op.Key] if !ok { h = new(pointerHandler) @@ -86,7 +94,7 @@ func (q *pointerQueue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer in events[op.Key] = []Event{pointer.Event{Type: pointer.Cancel}} } h.active = true - h.area = q.areas.intersection() + h.area = area h.transform = t h.wantsGrab = h.wantsGrab || op.Grab } @@ -94,32 +102,43 @@ func (q *pointerQueue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer in } func (q *pointerQueue) 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 := h.area.hit(pos) - opaque = opaque || res == pointer.HitOpaque - if res != pointer.HitNone { - *handlers = append(*handlers, n.key) - } + // Track whether we're passing through hits. + pass := true + idx := len(q.hitTree) - 1 + for idx >= 0 { + n := &q.hitTree[idx] + if !q.hit(n.area, pos) { + idx-- + continue + } + pass = pass && n.pass + if pass { + idx-- + } else { + idx = n.next + } + if n.key != nil { + *handlers = append(*handlers, n.key) } } } +func (q *pointerQueue) hit(areaIdx int, p f32.Point) bool { + for areaIdx != -1 { + a := &q.areas[areaIdx] + if !a.hit(p) { + return false + } + areaIdx = a.next + } + return true +} + +func (a *areaNode) hit(p f32.Point) bool { + p = a.trans.InvTransform(p) + return a.area.Hit(p) +} + func (q *pointerQueue) init() { if q.handlers == nil { q.handlers = make(map[Key]*pointerHandler) @@ -133,9 +152,9 @@ func (q *pointerQueue) Frame(root *ui.Ops, events handlerEvents) { h.active = false } q.hitTree = q.hitTree[:0] - q.areas.reset() + q.areas = q.areas[:0] q.reader.Reset(root) - q.collectHandlers(&q.reader, ui.Transform{}, 0, events) + q.collectHandlers(&q.reader, events, ui.Transform{}, -1, -1, false) for k, h := range q.handlers { if !h.active { q.dropHandler(k) @@ -221,7 +240,7 @@ func (q *pointerQueue) Push(e pointer.Event, events handlerEvents) { case i == 0: e.Priority = pointer.Foremost } - e.Hit = h.area.hit(e.Position) != pointer.HitNone + e.Hit = q.hit(h.area, e.Position) e.Position = h.transform.InvTransform(e.Position) events[k] = append(events[k], e) if e.Type == pointer.Release { @@ -238,41 +257,3 @@ func (q *pointerQueue) Push(e pointer.Event, events handlerEvents) { } } } - -func (a areaIntersection) hit(p f32.Point) pointer.HitResult { - res := pointer.HitNone - for _, area := range a { - tp := area.trans.InvTransform(p) - res = area.area.Hit(tp) - if res == pointer.HitNone { - break - } - } - return res -} - -func (s *areaStack) add(t ui.Transform, a pointer.AreaOp) { - s.areas = append(s.areas, area{t, a}) -} - -func (s *areaStack) push() { - s.stack = append(s.stack, len(s.areas)) -} - -func (s *areaStack) pop() { - off := s.stack[len(s.stack)-1] - s.stack = s.stack[:len(s.stack)-1] - s.areas = s.areas[:off] -} - -func (s *areaStack) intersection() areaIntersection { - off := len(s.backing) - s.backing = append(s.backing, s.areas...) - return areaIntersection(s.backing[off:len(s.backing):len(s.backing)]) -} - -func (a *areaStack) reset() { - a.areas = a.areas[:0] - a.stack = a.stack[:0] - a.backing = a.backing[:0] -} diff --git a/ui/internal/ops/ops.go b/ui/internal/ops/ops.go index 989214b0..4366d7a6 100644 --- a/ui/internal/ops/ops.go +++ b/ui/internal/ops/ops.go @@ -16,6 +16,7 @@ const ( TypeColor TypeArea TypePointerHandler + TypePass TypeKeyHandler TypeHideInput TypePush @@ -35,6 +36,7 @@ const ( TypeColorLen = 1 + 4 TypeAreaLen = 1 + 1 + 2*4 TypePointerHandlerLen = 1 + 1 + TypePassLen = 1 + 1 TypeKeyHandlerLen = 1 + 1 TypeHideInputLen = 1 TypePushLen = 1 @@ -55,6 +57,7 @@ func (t OpType) Size() int { TypeColorLen, TypeAreaLen, TypePointerHandlerLen, + TypePassLen, TypeKeyHandlerLen, TypeHideInputLen, TypePushLen, diff --git a/ui/layout/flex.go b/ui/layout/flex.go index 3f42aad7..43abe7e8 100644 --- a/ui/layout/flex.go +++ b/ui/layout/flex.go @@ -77,7 +77,6 @@ func (f *Flex) begin() { } f.begun = true f.ops.Begin() - ui.LayerOp{}.Add(f.ops) } func (f *Flex) Rigid() Constraints { diff --git a/ui/layout/list.go b/ui/layout/list.go index 53e4c322..8a63ad37 100644 --- a/ui/layout/list.go +++ b/ui/layout/list.go @@ -91,7 +91,6 @@ func (l *List) Next() (int, Constraints, bool) { if ok { cs = axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs)) l.ops.Begin() - ui.LayerOp{}.Add(l.ops) } return i, cs, ok } diff --git a/ui/layout/stack.go b/ui/layout/stack.go index 02ffca39..f2cc2ec2 100644 --- a/ui/layout/stack.go +++ b/ui/layout/stack.go @@ -55,7 +55,6 @@ func (s *Stack) begin() { } s.begun = true s.ops.Begin() - ui.LayerOp{}.Add(s.ops) } func (s *Stack) Rigid() Constraints { diff --git a/ui/pointer/pointer.go b/ui/pointer/pointer.go index 98ca950c..325cadb5 100644 --- a/ui/pointer/pointer.go +++ b/ui/pointer/pointer.go @@ -24,8 +24,6 @@ type Event struct { } type AreaOp struct { - Transparent bool - kind areaKind size image.Point } @@ -35,16 +33,14 @@ type HandlerOp struct { Grab bool } +// PassOp change the current event pass-through +// setting. +type PassOp struct { + Pass bool +} + type Key interface{} -type HitResult uint8 - -const ( - HitNone HitResult = iota - HitTransparent - HitOpaque -) - type ID uint16 type Type uint8 type Priority uint8 @@ -114,18 +110,14 @@ func (op *AreaOp) Decode(d []byte) { } } -func (op *AreaOp) Hit(pos f32.Point) HitResult { - res := HitOpaque - if op.Transparent { - res = HitTransparent - } +func (op *AreaOp) Hit(pos f32.Point) bool { switch op.kind { case areaRect: if 0 <= pos.X && pos.X < float32(op.size.X) && 0 <= pos.Y && pos.Y < float32(op.size.Y) { - return res + return true } else { - return HitNone + return false } case areaEllipse: rx := float32(op.size.X) / 2 @@ -135,9 +127,9 @@ func (op *AreaOp) Hit(pos f32.Point) HitResult { xh := pos.X - rx yk := pos.Y - ry if xh*xh*ry2+yk*yk*rx2 <= rx2*ry2 { - return res + return true } else { - return HitNone + return false } default: panic("invalid area kind") @@ -163,6 +155,24 @@ func (h *HandlerOp) Decode(d []byte, refs []interface{}) { } } +func (op PassOp) Add(o *ui.Ops) { + data := make([]byte, ops.TypePassLen) + data[0] = byte(ops.TypePass) + if op.Pass { + data[1] = 1 + } + o.Write(data) +} + +func (op *PassOp) Decode(d []byte) { + if ops.OpType(d[0]) != ops.TypePass { + panic("invalid op") + } + *op = PassOp{ + Pass: d[1] != 0, + } +} + func (t Type) String() string { switch t { case Press: diff --git a/ui/ui.go b/ui/ui.go index ee58d877..9637db3e 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -20,10 +20,6 @@ type Config interface { Px(v Value) int } -// LayerOp represents a semantic layer of UI. -type LayerOp struct { -} - // InvalidateOp requests a redraw at the given time. Use // the zero value to request an immediate redraw. type InvalidateOp struct { @@ -40,6 +36,9 @@ type Transform struct { offset f32.Point } +// Inf is the int value that represents an unbounded maximum constraint. +const Inf = int(^uint(0) >> 1) + func (r InvalidateOp) Add(o *Ops) { data := make([]byte, ops.TypeRedrawLen) data[0] = byte(ops.TypeInvalidate) @@ -100,22 +99,6 @@ func (t *TransformOp) Decode(d []byte) { } } -func (l LayerOp) Add(o *Ops) { - data := make([]byte, ops.TypeLayerLen) - data[0] = byte(ops.TypeLayer) - o.Write(data) -} - -func (l *LayerOp) Decode(d []byte) { - if ops.OpType(d[0]) != ops.TypeLayer { - panic("invalid op") - } - *l = LayerOp{} -} - func Offset(o f32.Point) Transform { return Transform{o} } - -// Inf is the int value that represents an unbounded maximum constraint. -const Inf = int(^uint(0) >> 1)