Files
gio-patched/ui/pointer/queue.go
T
Elias Naur a78f9c6eaa ui/pointer: don't drop handlers that whose events haven't been read
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>
2019-06-07 20:25:32 +02:00

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]
}