You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
405 lines
8.5 KiB
Go
405 lines
8.5 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package router
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"image"
|
|
|
|
"gioui.org/f32"
|
|
"gioui.org/internal/opconst"
|
|
"gioui.org/internal/ops"
|
|
"gioui.org/io/event"
|
|
"gioui.org/io/pointer"
|
|
"gioui.org/op"
|
|
)
|
|
|
|
type pointerQueue struct {
|
|
hitTree []hitNode
|
|
areas []areaNode
|
|
handlers map[event.Tag]*pointerHandler
|
|
pointers []pointerInfo
|
|
reader ops.Reader
|
|
|
|
scratch []event.Tag
|
|
}
|
|
|
|
type hitNode struct {
|
|
next int
|
|
area int
|
|
// Pass tracks the most recent PassOp mode.
|
|
pass bool
|
|
|
|
// For handler nodes.
|
|
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 {
|
|
area int
|
|
active bool
|
|
wantsGrab bool
|
|
types pointer.Type
|
|
}
|
|
|
|
type areaOp struct {
|
|
kind areaKind
|
|
rect image.Rectangle
|
|
}
|
|
|
|
type areaNode struct {
|
|
trans f32.Affine2D
|
|
next int
|
|
area areaOp
|
|
}
|
|
|
|
type areaKind uint8
|
|
|
|
const (
|
|
areaRect areaKind = iota
|
|
areaEllipse
|
|
)
|
|
|
|
func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t f32.Affine2D, area, node int, pass bool) {
|
|
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
|
switch opconst.OpType(encOp.Data[0]) {
|
|
case opconst.TypePush:
|
|
q.collectHandlers(r, events, t, area, node, pass)
|
|
case opconst.TypePop:
|
|
return
|
|
case opconst.TypePass:
|
|
op := decodePassOp(encOp.Data)
|
|
pass = op.Pass
|
|
case opconst.TypeArea:
|
|
var op areaOp
|
|
op.Decode(encOp.Data)
|
|
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 opconst.TypeTransform:
|
|
dop := ops.DecodeTransform(encOp.Data)
|
|
t = t.Mul(dop)
|
|
case opconst.TypePointerInput:
|
|
op := decodePointerInputOp(encOp.Data, encOp.Refs)
|
|
q.hitTree = append(q.hitTree, hitNode{
|
|
next: node,
|
|
area: area,
|
|
pass: pass,
|
|
tag: op.Tag,
|
|
})
|
|
node = len(q.hitTree) - 1
|
|
h, ok := q.handlers[op.Tag]
|
|
if !ok {
|
|
h = new(pointerHandler)
|
|
q.handlers[op.Tag] = h
|
|
events.Add(op.Tag, pointer.Event{Type: pointer.Cancel})
|
|
}
|
|
h.active = true
|
|
h.area = area
|
|
h.wantsGrab = h.wantsGrab || op.Grab
|
|
h.types = h.types | op.Types
|
|
}
|
|
}
|
|
}
|
|
|
|
func (q *pointerQueue) opHit(handlers *[]event.Tag, pos f32.Point) {
|
|
// 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.tag != nil {
|
|
if _, exists := q.handlers[n.tag]; exists {
|
|
*handlers = append(*handlers, n.tag)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (q *pointerQueue) invTransform(areaIdx int, p f32.Point) f32.Point {
|
|
if areaIdx == -1 {
|
|
return p
|
|
}
|
|
return q.areas[areaIdx].trans.Invert().Transform(p)
|
|
}
|
|
|
|
func (q *pointerQueue) hit(areaIdx int, p f32.Point) bool {
|
|
for areaIdx != -1 {
|
|
a := &q.areas[areaIdx]
|
|
p := a.trans.Invert().Transform(p)
|
|
if !a.area.Hit(p) {
|
|
return false
|
|
}
|
|
areaIdx = a.next
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (q *pointerQueue) init() {
|
|
if q.handlers == nil {
|
|
q.handlers = make(map[event.Tag]*pointerHandler)
|
|
}
|
|
}
|
|
|
|
func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) {
|
|
q.init()
|
|
for _, h := range q.handlers {
|
|
// Reset handler.
|
|
h.active = false
|
|
h.wantsGrab = false
|
|
h.types = 0
|
|
}
|
|
q.hitTree = q.hitTree[:0]
|
|
q.areas = q.areas[:0]
|
|
q.reader.Reset(root)
|
|
q.collectHandlers(&q.reader, events, f32.Affine2D{}, -1, -1, false)
|
|
for k, h := range q.handlers {
|
|
if !h.active {
|
|
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) 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:]...)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
|
|
q.init()
|
|
if e.Type == pointer.Cancel {
|
|
q.pointers = q.pointers[:0]
|
|
for k := range q.handlers {
|
|
q.dropHandlers(events, k)
|
|
}
|
|
return
|
|
}
|
|
pidx := -1
|
|
for i, p := range q.pointers {
|
|
if p.id == e.PointerID {
|
|
pidx = i
|
|
break
|
|
}
|
|
}
|
|
if pidx == -1 {
|
|
q.pointers = append(q.pointers, pointerInfo{id: e.PointerID})
|
|
pidx = len(q.pointers) - 1
|
|
}
|
|
p := &q.pointers[pidx]
|
|
|
|
if e.Type == pointer.Move && p.pressed {
|
|
e.Type = pointer.Drag
|
|
}
|
|
|
|
if e.Type == pointer.Release {
|
|
q.deliverEvent(p, events, e)
|
|
p.pressed = false
|
|
}
|
|
q.scratch = q.scratch[:0]
|
|
q.opHit(&q.scratch, e.Position)
|
|
if p.pressed {
|
|
// Filter out non-participating handlers.
|
|
for i := len(q.scratch) - 1; i >= 0; i-- {
|
|
if _, found := searchTag(p.handlers, q.scratch[i]); !found {
|
|
q.scratch = append(q.scratch[:i], q.scratch[i+1:]...)
|
|
}
|
|
}
|
|
}
|
|
q.deliverEnterLeaveEvents(p, q.scratch, events, e)
|
|
|
|
if !p.pressed {
|
|
p.handlers = append(p.handlers[:0], q.scratch...)
|
|
}
|
|
if e.Type == pointer.Press {
|
|
p.pressed = true
|
|
}
|
|
if e.Type != pointer.Release {
|
|
q.deliverEvent(p, events, e)
|
|
}
|
|
if !p.pressed && len(p.entered) == 0 {
|
|
// No longer need to track pointer.
|
|
q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...)
|
|
}
|
|
}
|
|
|
|
func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e pointer.Event) {
|
|
foremost := true
|
|
for _, k := range p.handlers {
|
|
h := q.handlers[k]
|
|
e := e
|
|
if p.pressed && len(p.handlers) == 1 {
|
|
e.Priority = pointer.Grabbed
|
|
} else if foremost {
|
|
e.Priority = pointer.Foremost
|
|
}
|
|
|
|
e.Position = q.invTransform(h.area, e.Position)
|
|
|
|
if e.Type&h.types == e.Type {
|
|
foremost = false
|
|
events.Add(k, e)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, hits []event.Tag, events *handlerEvents, e pointer.Event) {
|
|
if e.Source != pointer.Mouse && !p.pressed && e.Type != pointer.Press {
|
|
// Consider non-mouse pointers leaving when they're released.
|
|
hits = nil
|
|
}
|
|
// Deliver Leave events.
|
|
for _, k := range p.entered {
|
|
if _, found := searchTag(hits, k); found {
|
|
continue
|
|
}
|
|
h := q.handlers[k]
|
|
e.Type = pointer.Leave
|
|
e.Position = q.invTransform(h.area, e.Position)
|
|
|
|
if e.Type&h.types == e.Type {
|
|
events.Add(k, e)
|
|
}
|
|
}
|
|
// Deliver Enter events.
|
|
for _, k := range hits {
|
|
if _, found := searchTag(p.entered, k); found {
|
|
continue
|
|
}
|
|
h := q.handlers[k]
|
|
e.Type = pointer.Enter
|
|
e.Position = q.invTransform(h.area, e.Position)
|
|
|
|
if e.Type&h.types == e.Type {
|
|
events.Add(k, e)
|
|
}
|
|
}
|
|
p.entered = append(p.entered[:0], hits...)
|
|
}
|
|
|
|
func searchTag(tags []event.Tag, tag event.Tag) (int, bool) {
|
|
for i, t := range tags {
|
|
if t == tag {
|
|
return i, true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func (op *areaOp) Decode(d []byte) {
|
|
if opconst.OpType(d[0]) != opconst.TypeArea {
|
|
panic("invalid op")
|
|
}
|
|
bo := binary.LittleEndian
|
|
rect := image.Rectangle{
|
|
Min: image.Point{
|
|
X: int(int32(bo.Uint32(d[2:]))),
|
|
Y: int(int32(bo.Uint32(d[6:]))),
|
|
},
|
|
Max: image.Point{
|
|
X: int(int32(bo.Uint32(d[10:]))),
|
|
Y: int(int32(bo.Uint32(d[14:]))),
|
|
},
|
|
}
|
|
*op = areaOp{
|
|
kind: areaKind(d[1]),
|
|
rect: rect,
|
|
}
|
|
}
|
|
|
|
func (op *areaOp) Hit(pos f32.Point) bool {
|
|
min := f32.Point{
|
|
X: float32(op.rect.Min.X),
|
|
Y: float32(op.rect.Min.Y),
|
|
}
|
|
pos = pos.Sub(min)
|
|
size := op.rect.Size()
|
|
switch op.kind {
|
|
case areaRect:
|
|
return 0 <= pos.X && pos.X < float32(size.X) &&
|
|
0 <= pos.Y && pos.Y < float32(size.Y)
|
|
case areaEllipse:
|
|
rx := float32(size.X) / 2
|
|
ry := float32(size.Y) / 2
|
|
xh := pos.X - rx
|
|
yk := pos.Y - ry
|
|
// The ellipse function works in all cases because
|
|
// 0/0 is not <= 1.
|
|
return (xh*xh)/(rx*rx)+(yk*yk)/(ry*ry) <= 1
|
|
default:
|
|
panic("invalid area kind")
|
|
}
|
|
}
|
|
|
|
func decodePointerInputOp(d []byte, refs []interface{}) pointer.InputOp {
|
|
if opconst.OpType(d[0]) != opconst.TypePointerInput {
|
|
panic("invalid op")
|
|
}
|
|
return pointer.InputOp{
|
|
Tag: refs[0].(event.Tag),
|
|
Grab: d[1] != 0,
|
|
Types: pointer.Type(d[2]),
|
|
}
|
|
}
|
|
|
|
func decodePassOp(d []byte) pointer.PassOp {
|
|
if opconst.OpType(d[0]) != opconst.TypePass {
|
|
panic("invalid op")
|
|
}
|
|
return pointer.PassOp{
|
|
Pass: d[1] != 0,
|
|
}
|
|
}
|