pointer.go raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 package router
4
5 import (
6 "encoding/binary"
7 "image"
8
9 "github.com/p9c/p9/pkg/gel/gio/f32"
10 "github.com/p9c/p9/pkg/gel/gio/internal/opconst"
11 "github.com/p9c/p9/pkg/gel/gio/internal/ops"
12 "github.com/p9c/p9/pkg/gel/gio/io/event"
13 "github.com/p9c/p9/pkg/gel/gio/io/pointer"
14 "github.com/p9c/p9/pkg/gel/gio/op"
15 )
16
17 type pointerQueue struct {
18 hitTree []hitNode
19 areas []areaNode
20 cursors []cursorNode
21 cursor pointer.CursorName
22 handlers map[event.Tag]*pointerHandler
23 pointers []pointerInfo
24 reader ops.Reader
25
26 // states holds the storage for save/restore ops.
27 states []collectState
28 scratch []event.Tag
29 }
30
31 type hitNode struct {
32 next int
33 area int
34 // Pass tracks the most recent PassOp mode.
35 pass bool
36
37 // For handler nodes.
38 tag event.Tag
39 }
40
41 type cursorNode struct {
42 name pointer.CursorName
43 area int
44 }
45
46 type pointerInfo struct {
47 id pointer.ID
48 pressed bool
49 handlers []event.Tag
50 // last tracks the last pointer event received,
51 // used while processing frame events.
52 last pointer.Event
53
54 // entered tracks the tags that contain the pointer.
55 entered []event.Tag
56 }
57
58 type pointerHandler struct {
59 area int
60 active bool
61 wantsGrab bool
62 types pointer.Type
63 // min and max horizontal/vertical scroll
64 scrollRange image.Rectangle
65 }
66
67 type areaOp struct {
68 kind areaKind
69 rect f32.Rectangle
70 }
71
72 type areaNode struct {
73 trans f32.Affine2D
74 next int
75 area areaOp
76 }
77
78 type areaKind uint8
79
80 // collectState represents the state for collectHandlers
81 type collectState struct {
82 t f32.Affine2D
83 area int
84 node int
85 pass bool
86 }
87
88 const (
89 areaRect areaKind = iota
90 areaEllipse
91 )
92
93 func (q *pointerQueue) save(id int, state collectState) {
94 if extra := id - len(q.states) + 1; extra > 0 {
95 q.states = append(q.states, make([]collectState, extra)...)
96 }
97 q.states[id] = state
98 }
99
100 func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) {
101 state := collectState{
102 area: -1,
103 node: -1,
104 }
105 q.save(opconst.InitialStateID, state)
106 for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
107 switch opconst.OpType(encOp.Data[0]) {
108 case opconst.TypeSave:
109 id := ops.DecodeSave(encOp.Data)
110 q.save(id, state)
111 case opconst.TypeLoad:
112 id, mask := ops.DecodeLoad(encOp.Data)
113 s := q.states[id]
114 if mask&opconst.TransformState != 0 {
115 state.t = s.t
116 }
117 if mask&^opconst.TransformState != 0 {
118 state = s
119 }
120 case opconst.TypePass:
121 state.pass = encOp.Data[1] != 0
122 case opconst.TypeArea:
123 var op areaOp
124 op.Decode(encOp.Data)
125 q.areas = append(q.areas, areaNode{trans: state.t, next: state.area, area: op})
126 state.area = len(q.areas) - 1
127 q.hitTree = append(q.hitTree, hitNode{
128 next: state.node,
129 area: state.area,
130 pass: state.pass,
131 })
132 state.node = len(q.hitTree) - 1
133 case opconst.TypeTransform:
134 dop := ops.DecodeTransform(encOp.Data)
135 state.t = state.t.Mul(dop)
136 case opconst.TypePointerInput:
137 op := pointer.InputOp{
138 Tag: encOp.Refs[0].(event.Tag),
139 Grab: encOp.Data[1] != 0,
140 Types: pointer.Type(encOp.Data[2]),
141 }
142 q.hitTree = append(q.hitTree, hitNode{
143 next: state.node,
144 area: state.area,
145 pass: state.pass,
146 tag: op.Tag,
147 })
148 state.node = len(q.hitTree) - 1
149 h, ok := q.handlers[op.Tag]
150 if !ok {
151 h = new(pointerHandler)
152 q.handlers[op.Tag] = h
153 // Cancel handlers on (each) first appearance, but don't
154 // trigger redraw.
155 events.AddNoRedraw(op.Tag, pointer.Event{Type: pointer.Cancel})
156 }
157 h.active = true
158 h.area = state.area
159 h.wantsGrab = h.wantsGrab || op.Grab
160 h.types = h.types | op.Types
161 bo := binary.LittleEndian.Uint32
162 h.scrollRange = image.Rectangle{
163 Min: image.Point{
164 X: int(int32(bo(encOp.Data[3:]))),
165 Y: int(int32(bo(encOp.Data[7:]))),
166 },
167 Max: image.Point{
168 X: int(int32(bo(encOp.Data[11:]))),
169 Y: int(int32(bo(encOp.Data[15:]))),
170 },
171 }
172 case opconst.TypeCursor:
173 q.cursors = append(q.cursors, cursorNode{
174 name: encOp.Refs[0].(pointer.CursorName),
175 area: len(q.areas) - 1,
176 })
177 }
178 }
179 }
180
181 func (q *pointerQueue) opHit(handlers *[]event.Tag, pos f32.Point) {
182 // Track whether we're passing through hits.
183 pass := true
184 idx := len(q.hitTree) - 1
185 for idx >= 0 {
186 n := &q.hitTree[idx]
187 if !q.hit(n.area, pos) {
188 idx--
189 continue
190 }
191 pass = pass && n.pass
192 if pass {
193 idx--
194 } else {
195 idx = n.next
196 }
197 if n.tag != nil {
198 if _, exists := q.handlers[n.tag]; exists {
199 *handlers = append(*handlers, n.tag)
200 }
201 }
202 }
203 }
204
205 func (q *pointerQueue) invTransform(areaIdx int, p f32.Point) f32.Point {
206 if areaIdx == -1 {
207 return p
208 }
209 return q.areas[areaIdx].trans.Invert().Transform(p)
210 }
211
212 func (q *pointerQueue) hit(areaIdx int, p f32.Point) bool {
213 for areaIdx != -1 {
214 a := &q.areas[areaIdx]
215 p := a.trans.Invert().Transform(p)
216 if !a.area.Hit(p) {
217 return false
218 }
219 areaIdx = a.next
220 }
221 return true
222 }
223
224 func (q *pointerQueue) reset() {
225 if q.handlers == nil {
226 q.handlers = make(map[event.Tag]*pointerHandler)
227 }
228 }
229
230 func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) {
231 q.reset()
232 for _, h := range q.handlers {
233 // Reset handler.
234 h.active = false
235 h.wantsGrab = false
236 h.types = 0
237 }
238 q.hitTree = q.hitTree[:0]
239 q.areas = q.areas[:0]
240 q.cursors = q.cursors[:0]
241 q.reader.Reset(root)
242 q.collectHandlers(&q.reader, events)
243 for k, h := range q.handlers {
244 if !h.active {
245 q.dropHandlers(events, k)
246 delete(q.handlers, k)
247 }
248 if h.wantsGrab {
249 for _, p := range q.pointers {
250 if !p.pressed {
251 continue
252 }
253 for i, k2 := range p.handlers {
254 if k2 == k {
255 // Drop other handlers that lost their grab.
256 dropped := make([]event.Tag, 0, len(p.handlers)-1)
257 dropped = append(dropped, p.handlers[:i]...)
258 dropped = append(dropped, p.handlers[i+1:]...)
259 cancelHandlers(events, dropped...)
260 q.dropHandlers(events, dropped...)
261 break
262 }
263 }
264 }
265 }
266 }
267 for i := range q.pointers {
268 p := &q.pointers[i]
269 q.deliverEnterLeaveEvents(p, events, p.last)
270 }
271 }
272
273 func cancelHandlers(events *handlerEvents, tags ...event.Tag) {
274 for _, k := range tags {
275 events.Add(k, pointer.Event{Type: pointer.Cancel})
276 }
277 }
278
279 func (q *pointerQueue) dropHandlers(events *handlerEvents, tags ...event.Tag) {
280 for _, k := range tags {
281 for i := range q.pointers {
282 p := &q.pointers[i]
283 for i := len(p.handlers) - 1; i >= 0; i-- {
284 if p.handlers[i] == k {
285 p.handlers = append(p.handlers[:i], p.handlers[i+1:]...)
286 }
287 }
288 for i := len(p.entered) - 1; i >= 0; i-- {
289 if p.entered[i] == k {
290 p.entered = append(p.entered[:i], p.entered[i+1:]...)
291 }
292 }
293 }
294 }
295 }
296
297 func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
298 q.reset()
299 if e.Type == pointer.Cancel {
300 q.pointers = q.pointers[:0]
301 for k := range q.handlers {
302 cancelHandlers(events, k)
303 q.dropHandlers(events, k)
304 }
305 return
306 }
307 pidx := -1
308 for i, p := range q.pointers {
309 if p.id == e.PointerID {
310 pidx = i
311 break
312 }
313 }
314 if pidx == -1 {
315 q.pointers = append(q.pointers, pointerInfo{id: e.PointerID})
316 pidx = len(q.pointers) - 1
317 }
318 p := &q.pointers[pidx]
319 p.last = e
320
321 if e.Type == pointer.Move && p.pressed {
322 e.Type = pointer.Drag
323 }
324
325 if e.Type == pointer.Release {
326 q.deliverEvent(p, events, e)
327 p.pressed = false
328 }
329 q.deliverEnterLeaveEvents(p, events, e)
330
331 if !p.pressed {
332 p.handlers = append(p.handlers[:0], q.scratch...)
333 }
334 if e.Type == pointer.Press {
335 p.pressed = true
336 }
337 switch e.Type {
338 case pointer.Release:
339 case pointer.Scroll:
340 q.deliverScrollEvent(p, events, e)
341 default:
342 q.deliverEvent(p, events, e)
343 }
344 if !p.pressed && len(p.entered) == 0 {
345 // No longer need to track pointer.
346 q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...)
347 }
348 }
349
350 func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e pointer.Event) {
351 foremost := true
352 if p.pressed && len(p.handlers) == 1 {
353 e.Priority = pointer.Grabbed
354 foremost = false
355 }
356 for _, k := range p.handlers {
357 h := q.handlers[k]
358 if e.Type&h.types == 0 {
359 continue
360 }
361 e := e
362 if foremost {
363 foremost = false
364 e.Priority = pointer.Foremost
365 }
366 e.Position = q.invTransform(h.area, e.Position)
367 events.Add(k, e)
368 }
369 }
370
371 func (q *pointerQueue) deliverScrollEvent(p *pointerInfo, events *handlerEvents, e pointer.Event) {
372 foremost := true
373 if p.pressed && len(p.handlers) == 1 {
374 e.Priority = pointer.Grabbed
375 foremost = false
376 }
377 var sx, sy = e.Scroll.X, e.Scroll.Y
378 for _, k := range p.handlers {
379 if sx == 0 && sy == 0 {
380 return
381 }
382 h := q.handlers[k]
383 // Distribute the scroll to the handler based on its ScrollRange.
384 sx, e.Scroll.X = setScrollEvent(sx, h.scrollRange.Min.X, h.scrollRange.Max.X)
385 sy, e.Scroll.Y = setScrollEvent(sy, h.scrollRange.Min.Y, h.scrollRange.Max.Y)
386 e := e
387 if foremost {
388 foremost = false
389 e.Priority = pointer.Foremost
390 }
391 e.Position = q.invTransform(h.area, e.Position)
392 events.Add(k, e)
393 }
394 }
395
396 func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEvents, e pointer.Event) {
397 q.scratch = q.scratch[:0]
398 q.opHit(&q.scratch, e.Position)
399 if p.pressed {
400 // Filter out non-participating handlers.
401 for i := len(q.scratch) - 1; i >= 0; i-- {
402 if _, found := searchTag(p.handlers, q.scratch[i]); !found {
403 q.scratch = append(q.scratch[:i], q.scratch[i+1:]...)
404 }
405 }
406 }
407 hits := q.scratch
408 if e.Source != pointer.Mouse && !p.pressed && e.Type != pointer.Press {
409 // Consider non-mouse pointers leaving when they're released.
410 hits = nil
411 }
412 // Deliver Leave events.
413 for _, k := range p.entered {
414 if _, found := searchTag(hits, k); found {
415 continue
416 }
417 h := q.handlers[k]
418 e.Type = pointer.Leave
419
420 if e.Type&h.types != 0 {
421 e.Position = q.invTransform(h.area, e.Position)
422 events.Add(k, e)
423 }
424 }
425 // Deliver Enter events and update cursor.
426 q.cursor = pointer.CursorDefault
427 for _, k := range hits {
428 h := q.handlers[k]
429 for i := len(q.cursors) - 1; i >= 0; i-- {
430 if c := q.cursors[i]; c.area == h.area {
431 q.cursor = c.name
432 break
433 }
434 }
435 if _, found := searchTag(p.entered, k); found {
436 continue
437 }
438 e.Type = pointer.Enter
439
440 if e.Type&h.types != 0 {
441 e.Position = q.invTransform(h.area, e.Position)
442 events.Add(k, e)
443 }
444 }
445 p.entered = append(p.entered[:0], hits...)
446 }
447
448 func searchTag(tags []event.Tag, tag event.Tag) (int, bool) {
449 for i, t := range tags {
450 if t == tag {
451 return i, true
452 }
453 }
454 return 0, false
455 }
456
457 func opDecodeFloat32(d []byte) float32 {
458 return float32(int32(binary.LittleEndian.Uint32(d)))
459 }
460
461 func (op *areaOp) Decode(d []byte) {
462 if opconst.OpType(d[0]) != opconst.TypeArea {
463 panic("invalid op")
464 }
465 rect := f32.Rectangle{
466 Min: f32.Point{
467 X: opDecodeFloat32(d[2:]),
468 Y: opDecodeFloat32(d[6:]),
469 },
470 Max: f32.Point{
471 X: opDecodeFloat32(d[10:]),
472 Y: opDecodeFloat32(d[14:]),
473 },
474 }
475 *op = areaOp{
476 kind: areaKind(d[1]),
477 rect: rect,
478 }
479 }
480
481 func (op *areaOp) Hit(pos f32.Point) bool {
482 pos = pos.Sub(op.rect.Min)
483 size := op.rect.Size()
484 switch op.kind {
485 case areaRect:
486 return 0 <= pos.X && pos.X < size.X &&
487 0 <= pos.Y && pos.Y < size.Y
488 case areaEllipse:
489 rx := size.X / 2
490 ry := size.Y / 2
491 xh := pos.X - rx
492 yk := pos.Y - ry
493 // The ellipse function works in all cases because
494 // 0/0 is not <= 1.
495 return (xh*xh)/(rx*rx)+(yk*yk)/(ry*ry) <= 1
496 default:
497 panic("invalid area kind")
498 }
499 }
500
501 func setScrollEvent(scroll float32, min, max int) (left, scrolled float32) {
502 if v := float32(max); scroll > v {
503 return scroll - v, v
504 }
505 if v := float32(min); scroll < v {
506 return scroll - v, v
507 }
508 return 0, scroll
509 }
510