Files
gio/ui/pointer/queue.go
T
Elias Naur a8bb3c2f14 all: make pointer.Area an interface
With an interface instead of anonymous functions, amending an
area's parameters can be done even after adding it to an OpHandler.

This will be useful when we switch to serialized op lists.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2019-04-25 10:02:14 +02:00

248 lines
4.8 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package pointer
import (
"gioui.org/ui"
"gioui.org/ui/f32"
)
type Queue struct {
// The root of the tree of ops relevant to pointer handling.
root ui.Op
handlers map[Key]*handler
pointers []pointerInfo
scratch []Key
}
type pointerInfo struct {
id ID
pressed bool
handlers []Key
}
type handler struct {
area Area
active bool
transform ui.Transform
events []Event
wantsGrab bool
}
type childOp interface {
ChildOp() ui.Op
}
func (q *Queue) collectHandlers(op ui.Op, t ui.Transform) ui.Op {
switch op := op.(type) {
case ui.Ops:
var all ui.Ops
for _, op := range op {
if op := q.collectHandlers(op, t); op != nil {
if ops, ok := op.(ui.Ops); ok {
all = append(all, ops...)
} else {
all = append(all, op)
}
}
}
return all
case ui.OpLayer:
child := q.collectHandlers(op.ChildOp(), t)
if child == nil {
return nil
}
return ui.OpLayer{Op: child}
case ui.OpTransform:
return q.collectHandlers(op.ChildOp(), t.Mul(op.Transform))
case OpHandler:
h, ok := q.handlers[op.Key]
if !ok {
h = new(handler)
q.handlers[op.Key] = h
}
h.area = op.Area
h.transform = t
h.wantsGrab = h.wantsGrab || op.Grab
return op
case childOp:
return q.collectHandlers(op.ChildOp(), t)
default:
return nil
}
}
func (q *Queue) opHit(handlers *[]Key, op ui.Op, pos f32.Point) (HitResult, bool) {
if op == nil {
return HitNone, false
}
switch op := op.(type) {
case ui.Ops:
hitRes := HitNone
var layer bool
for i := len(op) - 1; i >= 0; i-- {
op := op[i]
if _, ok := op.(ui.OpLayer); layer && ok {
continue
}
res, l := q.opHit(handlers, op, pos)
if res > hitRes {
hitRes = res
}
layer = layer || l
}
return hitRes, layer
case ui.OpLayer:
res, layer := q.opHit(handlers, op.Op, pos)
return res, layer || res == HitOpaque
case OpHandler:
h, ok := q.handlers[op.Key]
if !ok {
return HitNone, false
}
tpos := h.transform.InvTransform(pos)
res := h.area.Hit(tpos)
if res != HitNone {
*handlers = append(*handlers, op.Key)
}
return res, false
default:
panic("unexpected op")
}
}
func (q *Queue) init() {
if q.handlers == nil {
q.handlers = make(map[Key]*handler)
}
}
func (q *Queue) Frame(op ui.Op) {
q.init()
for k, h := range q.handlers {
if !h.active {
q.dropHandler(k)
} else {
// Reset handler.
h.events = h.events[:0]
}
}
q.root = q.collectHandlers(op, ui.Transform{})
}
func (q *Queue) For(k Key) []Event {
if k == nil {
panic("nil handler")
}
q.init()
h, ok := q.handlers[k]
if !ok {
h = new(handler)
q.handlers[k] = h
}
if !h.active {
h.active = true
// Prepend a Cancel.
h.events = append(h.events, Event{})
copy(h.events[1:], h.events)
h.events[0] = Event{Type: Cancel}
}
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, q.root, 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.Position = h.transform.InvTransform(e.Position)
e.Hit = h.area.Hit(e.Position) != HitNone
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
}
}
}
}