mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
a8bb3c2f14
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>
248 lines
4.8 KiB
Go
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
|
|
}
|
|
}
|
|
}
|
|
}
|