mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
a78f9c6eaa
We're about to merge the pointer and key event streams in a single input queue. To do that, we need to simplify Queue.For to just returning events for the given handler. First step is the handler active flag. Dropping handlers that haven't had their events read doesn't seem worth it. Drop that special case and only determine a handler's activeness from its presence in the ops list. Signed-off-by: Elias Naur <mail@eliasnaur.com>
293 lines
5.7 KiB
Go
293 lines
5.7 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package pointer
|
|
|
|
import (
|
|
"gioui.org/ui"
|
|
"gioui.org/ui/f32"
|
|
"gioui.org/ui/internal/ops"
|
|
)
|
|
|
|
type Queue struct {
|
|
hitTree []hitNode
|
|
handlers map[Key]*handler
|
|
pointers []pointerInfo
|
|
reader ui.OpsReader
|
|
scratch []Key
|
|
areas areaStack
|
|
}
|
|
|
|
type hitNode struct {
|
|
// The layer depth.
|
|
level int
|
|
// The handler, or nil for a layer.
|
|
key Key
|
|
}
|
|
|
|
type pointerInfo struct {
|
|
id ID
|
|
pressed bool
|
|
handlers []Key
|
|
}
|
|
|
|
type handler struct {
|
|
area areaIntersection
|
|
active bool
|
|
transform ui.Transform
|
|
events []Event
|
|
wantsGrab bool
|
|
}
|
|
|
|
type area struct {
|
|
trans ui.Transform
|
|
area OpArea
|
|
}
|
|
|
|
type areaIntersection []area
|
|
|
|
type areaStack struct {
|
|
stack []int
|
|
areas []area
|
|
backing []area
|
|
}
|
|
|
|
func (q *Queue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer int) {
|
|
for {
|
|
encOp, ok := r.Decode()
|
|
if !ok {
|
|
return
|
|
}
|
|
switch ops.OpType(encOp.Data[0]) {
|
|
case ops.TypePush:
|
|
q.areas.push()
|
|
q.collectHandlers(r, t, layer)
|
|
case ops.TypePop:
|
|
q.areas.pop()
|
|
return
|
|
case ops.TypeLayer:
|
|
layer++
|
|
q.hitTree = append(q.hitTree, hitNode{level: layer})
|
|
case ops.TypeArea:
|
|
var op OpArea
|
|
op.decode(encOp.Data)
|
|
q.areas.add(t, op)
|
|
case ops.TypeTransform:
|
|
var op ui.OpTransform
|
|
op.Decode(encOp.Data)
|
|
t = t.Mul(op.Transform)
|
|
case ops.TypePointerHandler:
|
|
var op OpHandler
|
|
op.Decode(encOp.Data, encOp.Refs)
|
|
q.hitTree = append(q.hitTree, hitNode{level: layer, key: op.Key})
|
|
h, ok := q.handlers[op.Key]
|
|
if !ok {
|
|
h = &handler{
|
|
// Reset the handler on (each) first appearance.
|
|
events: []Event{Event{Type: Cancel}},
|
|
}
|
|
q.handlers[op.Key] = h
|
|
}
|
|
h.active = true
|
|
h.area = q.areas.intersection()
|
|
h.transform = t
|
|
h.wantsGrab = h.wantsGrab || op.Grab
|
|
}
|
|
}
|
|
}
|
|
|
|
func (q *Queue) 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 == hitOpaque
|
|
if res != hitNone {
|
|
*handlers = append(*handlers, n.key)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (q *Queue) init() {
|
|
if q.handlers == nil {
|
|
q.handlers = make(map[Key]*handler)
|
|
}
|
|
}
|
|
|
|
func (q *Queue) Frame(root *ui.Ops) {
|
|
q.init()
|
|
for _, h := range q.handlers {
|
|
// Reset handler.
|
|
h.active = false
|
|
h.events = h.events[:0]
|
|
}
|
|
q.hitTree = q.hitTree[:0]
|
|
q.areas.reset()
|
|
q.reader.Reset(root)
|
|
q.collectHandlers(&q.reader, ui.Transform{}, 0)
|
|
for k, h := range q.handlers {
|
|
if !h.active {
|
|
q.dropHandler(k)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (q *Queue) For(k Key) []Event {
|
|
if k == nil {
|
|
panic("nil handler")
|
|
}
|
|
h := q.handlers[k]
|
|
if h == nil {
|
|
return nil
|
|
}
|
|
return h.events
|
|
}
|
|
|
|
func (q *Queue) dropHandler(k Key) {
|
|
delete(q.handlers, k)
|
|
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:]...)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (q *Queue) Push(e Event) {
|
|
q.init()
|
|
if e.Type == Cancel {
|
|
q.pointers = q.pointers[:0]
|
|
for k := range q.handlers {
|
|
q.dropHandler(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 !p.pressed && (e.Type == Move || e.Type == Press) {
|
|
p.handlers, q.scratch = q.scratch[:0], p.handlers
|
|
q.opHit(&p.handlers, e.Position)
|
|
// Drop handlers no longer hit.
|
|
loop:
|
|
for _, h := range q.scratch {
|
|
for _, h2 := range p.handlers {
|
|
if h == h2 {
|
|
continue loop
|
|
}
|
|
}
|
|
q.dropHandler(h)
|
|
}
|
|
if e.Type == Press {
|
|
p.pressed = true
|
|
}
|
|
}
|
|
if p.pressed {
|
|
// Resolve grabs.
|
|
q.scratch = q.scratch[:0]
|
|
for i, k := range p.handlers {
|
|
h := q.handlers[k]
|
|
if h.wantsGrab {
|
|
q.scratch = append(q.scratch, p.handlers[:i]...)
|
|
q.scratch = append(q.scratch, p.handlers[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
// Drop handlers that lost their grab.
|
|
for _, k := range q.scratch {
|
|
q.dropHandler(k)
|
|
}
|
|
}
|
|
if e.Type == Release {
|
|
q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...)
|
|
}
|
|
for i, k := range p.handlers {
|
|
h := q.handlers[k]
|
|
e := e
|
|
switch {
|
|
case p.pressed && len(p.handlers) == 1:
|
|
e.Priority = Grabbed
|
|
case i == 0:
|
|
e.Priority = Foremost
|
|
}
|
|
e.Hit = h.area.hit(e.Position) != hitNone
|
|
e.Position = h.transform.InvTransform(e.Position)
|
|
h.events = append(h.events, e)
|
|
if e.Type == Release {
|
|
// Release grab when the number of grabs reaches zero.
|
|
grabs := 0
|
|
for _, p := range q.pointers {
|
|
if p.pressed && len(p.handlers) == 1 && p.handlers[0] == k {
|
|
grabs++
|
|
}
|
|
}
|
|
if grabs == 0 {
|
|
h.wantsGrab = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a areaIntersection) hit(p f32.Point) hitResult {
|
|
res := hitNone
|
|
for _, area := range a {
|
|
tp := area.trans.InvTransform(p)
|
|
res = area.area.hit(tp)
|
|
if res == hitNone {
|
|
break
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (s *areaStack) add(t ui.Transform, a OpArea) {
|
|
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]
|
|
}
|