From 40856a244ec184a95fd0e4973d30b0191ff34c05 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Mon, 3 Jun 2019 13:09:07 +0200 Subject: [PATCH] ui/pointer: introduce OpArea for pointer hit testing Split out OpArea from OpHandler to allow stacked areas in a followup. Replace hit closures with static shapes (rectangles and ellipses) to avoid allocations. If needed, generic hit functions can be introduced again later. Signed-off-by: Elias Naur --- ui/gesture/gestures.go | 40 ++----------------- ui/internal/ops/ops.go | 7 ++-- ui/layout/list.go | 10 +++-- ui/pointer/pointer.go | 89 ++++++++++++++++++++++++++++++++++++++---- ui/pointer/queue.go | 14 ++++--- ui/text/editor.go | 6 +-- 6 files changed, 108 insertions(+), 58 deletions(-) diff --git a/ui/gesture/gestures.go b/ui/gesture/gestures.go index b74d052e..edc57266 100644 --- a/ui/gesture/gestures.go +++ b/ui/gesture/gestures.go @@ -3,7 +3,6 @@ package gesture import ( - "image" "math" "runtime" "time" @@ -38,14 +37,6 @@ type Scroll struct { scroll float32 } -type Rect struct { - Size image.Point -} - -type Ellipse struct { - Size image.Point -} - type flinger struct { // Current offset in pixels. x float32 @@ -84,8 +75,8 @@ const ( thresholdVelocity = 1 ) -func (c *Click) Op(ops *ui.Ops, a pointer.Area) { - op := pointer.OpHandler{Area: a, Key: c} +func (c *Click) Add(ops *ui.Ops) { + op := pointer.OpHandler{Key: c} op.Add(ops) } @@ -117,8 +108,8 @@ func (c *Click) Update(q pointer.Events) []ClickEvent { return events } -func (s *Scroll) Op(ops *ui.Ops, a pointer.Area) { - oph := pointer.OpHandler{Area: a, Key: s, Grab: s.grab} +func (s *Scroll) Add(ops *ui.Ops) { + oph := pointer.OpHandler{Key: s, Grab: s.grab} oph.Add(ops) if s.flinger.Active() { ui.OpRedraw{}.Add(ops) @@ -264,29 +255,6 @@ func (f *flinger) Tick(now time.Time) int { return idist } -func (r *Rect) Hit(pos f32.Point) pointer.HitResult { - if 0 <= pos.X && pos.X < float32(r.Size.X) && - 0 <= pos.Y && pos.Y < float32(r.Size.Y) { - return pointer.HitOpaque - } else { - return pointer.HitNone - } -} - -func (e *Ellipse) Hit(pos f32.Point) pointer.HitResult { - rx := float32(e.Size.X) / 2 - ry := float32(e.Size.Y) / 2 - rx2 := rx * rx - ry2 := ry * ry - xh := pos.X - rx - yk := pos.Y - ry - if xh*xh*ry2+yk*yk*rx2 <= rx2*ry2 { - return pointer.HitOpaque - } else { - return pointer.HitNone - } -} - func (a Axis) String() string { switch a { case Horizontal: diff --git a/ui/internal/ops/ops.go b/ui/internal/ops/ops.go index e48de5d5..0aeef074 100644 --- a/ui/internal/ops/ops.go +++ b/ui/internal/ops/ops.go @@ -14,6 +14,7 @@ const ( TypeImage TypeDraw TypeColor + TypeArea TypePointerHandler TypeKeyHandler TypeHideInput @@ -32,6 +33,7 @@ const ( TypeImageLen = 1 + 4*4 TypeDrawLen = 1 + 4*4 TypeColorLen = 1 + 4 + TypeAreaLen = 1 + 1 + 2*4 TypePointerHandlerLen = 1 + 1 TypeKeyHandlerLen = 1 + 1 TypeHideInputLen = 1 @@ -51,6 +53,7 @@ func (t OpType) Size() int { TypeImageLen, TypeDrawLen, TypeColorLen, + TypeAreaLen, TypePointerHandlerLen, TypeKeyHandlerLen, TypeHideInputLen, @@ -63,10 +66,8 @@ func (t OpType) Size() int { func (t OpType) NumRefs() int { switch t { - case TypeBlock, TypeImage, TypeKeyHandler: + case TypeBlock, TypeImage, TypeKeyHandler, TypePointerHandler: return 1 - case TypePointerHandler: - return 2 default: return 0 } diff --git a/ui/layout/list.go b/ui/layout/list.go index e81b7f1f..c3395d02 100644 --- a/ui/layout/list.go +++ b/ui/layout/list.go @@ -24,7 +24,6 @@ type List struct { // The distance scrolled since last call to Init. Distance int - area gesture.Rect scroll gesture.Scroll scrollDir int @@ -78,7 +77,7 @@ func (l *List) Next(ops *ui.Ops) (int, Constraints, bool) { var cs Constraints if ok { if len(l.children) == 0 { - l.scroll.Op(ops, &l.area) + ops.Begin() } cs = axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs)) ops.Begin() @@ -193,7 +192,12 @@ func (l *List) Layout(ops *ui.Ops) Dimens { l.scroll.Stop() } dims := axisPoint(l.Axis, mainc.Constrain(pos), maxCross) - l.area.Size = dims + if len(l.children) > 0 { + block := ops.End() + pointer.AreaRect(dims).Add(ops) + l.scroll.Add(ops) + block.Add(ops) + } return Dimens{Size: dims} } diff --git a/ui/pointer/pointer.go b/ui/pointer/pointer.go index 9ff9062a..56bde3da 100644 --- a/ui/pointer/pointer.go +++ b/ui/pointer/pointer.go @@ -3,6 +3,8 @@ package pointer import ( + "encoding/binary" + "image" "time" "gioui.org/ui" @@ -21,14 +23,14 @@ type Event struct { Scroll f32.Point } -type OpHandler struct { - Key Key - Area Area - Grab bool +type OpArea struct { + kind areaKind + size image.Point } -type Area interface { - Hit(pos f32.Point) HitResult +type OpHandler struct { + Key Key + Grab bool } type Key interface{} @@ -50,6 +52,8 @@ type Type uint8 type Priority uint8 type Source uint8 +type areaKind uint8 + const ( Cancel Type = iota Press @@ -68,13 +72,83 @@ const ( Grabbed ) +const ( + areaRect areaKind = iota + areaEllipse +) + +func AreaRect(size image.Point) OpArea { + return OpArea{ + kind: areaRect, + size: size, + } +} + +func AreaEllipse(size image.Point) OpArea { + return OpArea{ + kind: areaEllipse, + size: size, + } +} + +func (op OpArea) Add(o *ui.Ops) { + data := make([]byte, ops.TypeAreaLen) + data[0] = byte(ops.TypeArea) + data[1] = byte(op.kind) + bo := binary.LittleEndian + bo.PutUint32(data[2:], uint32(op.size.X)) + bo.PutUint32(data[6:], uint32(op.size.Y)) + o.Write(data, nil) +} + +func (op *OpArea) decode(d []byte) { + if ops.OpType(d[0]) != ops.TypeArea { + panic("invalid op") + } + bo := binary.LittleEndian + size := image.Point{ + X: int(bo.Uint32(d[2:])), + Y: int(bo.Uint32(d[6:])), + } + *op = OpArea{ + kind: areaKind(d[1]), + size: size, + } +} + +func (op *OpArea) Hit(pos f32.Point) HitResult { + 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 HitOpaque + } else { + return HitNone + } + case areaEllipse: + rx := float32(op.size.X) / 2 + ry := float32(op.size.Y) / 2 + rx2 := rx * rx + ry2 := ry * ry + xh := pos.X - rx + yk := pos.Y - ry + if xh*xh*ry2+yk*yk*rx2 <= rx2*ry2 { + return HitOpaque + } else { + return HitNone + } + default: + panic("invalid area kind") + } +} + func (h OpHandler) Add(o *ui.Ops) { data := make([]byte, ops.TypePointerHandlerLen) data[0] = byte(ops.TypePointerHandler) if h.Grab { data[1] = 1 } - o.Write(data, []interface{}{h.Key, h.Area}) + o.Write(data, []interface{}{h.Key}) } func (h *OpHandler) Decode(d []byte, refs []interface{}) { @@ -84,7 +158,6 @@ func (h *OpHandler) Decode(d []byte, refs []interface{}) { *h = OpHandler{ Grab: d[1] != 0, Key: refs[0].(Key), - Area: refs[1].(Area), } } diff --git a/ui/pointer/queue.go b/ui/pointer/queue.go index d24ee7c7..9c33873c 100644 --- a/ui/pointer/queue.go +++ b/ui/pointer/queue.go @@ -30,14 +30,14 @@ type pointerInfo struct { } type handler struct { - area Area + area OpArea active bool transform ui.Transform events []Event wantsGrab bool } -func (q *Queue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer int) { +func (q *Queue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer int, area OpArea) { for { encOp, ok := r.Decode() if !ok { @@ -45,12 +45,16 @@ func (q *Queue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer int) { } switch ops.OpType(encOp.Data[0]) { case ops.TypePush: - q.collectHandlers(r, t, layer) + q.collectHandlers(r, t, layer, area) case ops.TypePop: return case ops.TypeLayer: layer++ q.hitTree = append(q.hitTree, hitNode{level: layer}) + case ops.TypeArea: + var op OpArea + op.decode(encOp.Data) + area = op case ops.TypeTransform: var op ui.OpTransform op.Decode(encOp.Data) @@ -64,7 +68,7 @@ func (q *Queue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer int) { h = new(handler) q.handlers[op.Key] = h } - h.area = op.Area + h.area = area h.transform = t h.wantsGrab = h.wantsGrab || op.Grab } @@ -117,7 +121,7 @@ func (q *Queue) Frame(root *ui.Ops) { } q.hitTree = q.hitTree[:0] q.reader.Reset(root) - q.collectHandlers(&q.reader, ui.Transform{}, 0) + q.collectHandlers(&q.reader, ui.Transform{}, 0, OpArea{}) } func (q *Queue) For(k Key) []Event { diff --git a/ui/text/editor.go b/ui/text/editor.go index d89c003e..a2ddbc7d 100644 --- a/ui/text/editor.go +++ b/ui/text/editor.go @@ -201,9 +201,9 @@ func (e *Editor) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens { } baseline := e.padTop + e.dims.Baseline - area := &gesture.Rect{e.viewSize} - e.scroller.Op(ops, area) - e.clicker.Op(ops, area) + pointer.AreaRect(e.viewSize).Add(ops) + e.scroller.Add(ops) + e.clicker.Add(ops) return layout.Dimens{Size: e.viewSize, Baseline: baseline} }