~eliasnaur/gio

f44ccec04361f090a930ac7350c4641c3d0d1729 — Elias Naur 1 year, 4 months ago f90ef91
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.
M ui/input/pointer.go => ui/input/pointer.go +68 -87
@@ 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,30 102,41 @@ 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() {


@@ 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]
}

M ui/internal/ops/ops.go => ui/internal/ops/ops.go +3 -0
@@ 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,

M ui/layout/flex.go => ui/layout/flex.go +0 -1
@@ 77,7 77,6 @@ func (f *Flex) begin() {
	}
	f.begun = true
	f.ops.Begin()
	ui.LayerOp{}.Add(f.ops)
}

func (f *Flex) Rigid() Constraints {

M ui/layout/list.go => ui/layout/list.go +0 -1
@@ 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
}

M ui/layout/stack.go => ui/layout/stack.go +0 -1
@@ 55,7 55,6 @@ func (s *Stack) begin() {
	}
	s.begun = true
	s.ops.Begin()
	ui.LayerOp{}.Add(s.ops)
}

func (s *Stack) Rigid() Constraints {

M ui/pointer/pointer.go => ui/pointer/pointer.go +29 -19
@@ 24,8 24,6 @@ type Event struct {
}

type AreaOp struct {
	Transparent bool

	kind areaKind
	size image.Point
}


@@ 35,15 33,13 @@ type HandlerOp struct {
	Grab bool
}

type Key interface{}

type HitResult uint8
// PassOp change the current event pass-through
// setting.
type PassOp struct {
	Pass bool
}

const (
	HitNone HitResult = iota
	HitTransparent
	HitOpaque
)
type Key interface{}

type ID uint16
type Type 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:

M ui/ui.go => ui/ui.go +3 -20
@@ 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)