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.
NoiseTorch/vendor/gioui.org/io/router/pointer.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,
}
}