mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
b247395c62
Encode TransformOp as an Affince2D matrix instead and use that in gpu and io transform handling. There are no changes to user facing API and so far only the offset part of the matrix is used. This patch is a step towards full affine transformations. Signed-off-by: Viktor <viktor.ogeman@gmail.com>
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,
|
|
}
|
|
}
|