Files
gio/io/router/pointer.go
T
pierre 1100e03c1e io/router: support cursor changes on Frame events
Add support to Router so that the cursor can be changed with CursorNameOp without any mouse movement.
Enter and Leave events are also delivered as areas change.

Signed-off-by: pierre <pierre.curto@gmail.com>
2021-01-27 22:21:27 +01:00

457 lines
9.8 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
cursors []cursorNode
cursor pointer.CursorName
handlers map[event.Tag]*pointerHandler
pointers []pointerInfo
reader ops.Reader
// states holds the storage for save/restore ops.
states []collectState
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 cursorNode struct {
name pointer.CursorName
area int
}
type pointerInfo struct {
id pointer.ID
pressed bool
handlers []event.Tag
// last tracks the last pointer event received,
// used while processing frame events.
last pointer.Event
// 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
// collectState represents the state for collectHandlers
type collectState struct {
t f32.Affine2D
area int
node int
pass bool
}
const (
areaRect areaKind = iota
areaEllipse
)
func (q *pointerQueue) save(id int, state collectState) {
if extra := id - len(q.states) + 1; extra > 0 {
q.states = append(q.states, make([]collectState, extra)...)
}
q.states[id] = state
}
func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) {
state := collectState{
area: -1,
node: -1,
}
q.save(opconst.InitialStateID, state)
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
switch opconst.OpType(encOp.Data[0]) {
case opconst.TypeSave:
id := ops.DecodeSave(encOp.Data)
q.save(id, state)
case opconst.TypeLoad:
id, mask := ops.DecodeLoad(encOp.Data)
s := q.states[id]
if mask&opconst.TransformState != 0 {
state.t = s.t
}
if mask&^opconst.TransformState != 0 {
state = s
}
case opconst.TypePass:
state.pass = encOp.Data[1] != 0
case opconst.TypeArea:
var op areaOp
op.Decode(encOp.Data)
q.areas = append(q.areas, areaNode{trans: state.t, next: state.area, area: op})
state.area = len(q.areas) - 1
q.hitTree = append(q.hitTree, hitNode{
next: state.node,
area: state.area,
pass: state.pass,
})
state.node = len(q.hitTree) - 1
case opconst.TypeTransform:
dop := ops.DecodeTransform(encOp.Data)
state.t = state.t.Mul(dop)
case opconst.TypePointerInput:
op := pointer.InputOp{
Tag: encOp.Refs[0].(event.Tag),
Grab: encOp.Data[1] != 0,
Types: pointer.Type(encOp.Data[2]),
}
q.hitTree = append(q.hitTree, hitNode{
next: state.node,
area: state.area,
pass: state.pass,
tag: op.Tag,
})
state.node = len(q.hitTree) - 1
h, ok := q.handlers[op.Tag]
if !ok {
h = new(pointerHandler)
q.handlers[op.Tag] = h
// Cancel handlers on (each) first appearance, but don't
// trigger redraw.
events.AddNoRedraw(op.Tag, pointer.Event{Type: pointer.Cancel})
}
h.active = true
h.area = state.area
h.wantsGrab = h.wantsGrab || op.Grab
h.types = h.types | op.Types
case opconst.TypeCursor:
q.cursors = append(q.cursors, cursorNode{
name: encOp.Refs[0].(pointer.CursorName),
area: len(q.areas) - 1,
})
}
}
}
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) reset() {
if q.handlers == nil {
q.handlers = make(map[event.Tag]*pointerHandler)
}
}
func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) {
q.reset()
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.cursors = q.cursors[:0]
q.reader.Reset(root)
q.collectHandlers(&q.reader, events)
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.
cancelHandlers(events, p.handlers[i+1:]...)
cancelHandlers(events, p.handlers[:i]...)
q.dropHandlers(events, p.handlers[i+1:]...)
q.dropHandlers(events, p.handlers[:i]...)
break
}
}
}
}
}
for i := range q.pointers {
p := &q.pointers[i]
q.deliverEnterLeaveEvents(p, events, p.last)
}
}
func cancelHandlers(events *handlerEvents, tags ...event.Tag) {
for _, k := range tags {
events.Add(k, pointer.Event{Type: pointer.Cancel})
}
}
func (q *pointerQueue) dropHandlers(events *handlerEvents, tags ...event.Tag) {
for _, k := range tags {
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.reset()
if e.Type == pointer.Cancel {
q.pointers = q.pointers[:0]
for k := range q.handlers {
cancelHandlers(events, k)
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]
p.last = e
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.deliverEnterLeaveEvents(p, 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, events *handlerEvents, e pointer.Event) {
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:]...)
}
}
}
hits := q.scratch
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 and update cursor.
q.cursor = pointer.CursorDefault
for _, k := range hits {
h := q.handlers[k]
for i := len(q.cursors) - 1; i >= 0; i-- {
if c := q.cursors[i]; c.area == h.area {
q.cursor = c.name
break
}
}
if _, found := searchTag(p.entered, k); found {
continue
}
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")
}
}