router.mx raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 // Full port of gioui.org/io/input (router.go, pointer.go, key.go, clipboard.go).
4 // All sub-packages merged into package gio.
5
6 package gio
7
8 import (
9 "image"
10 "io"
11 "time"
12 )
13
14 // ---------------------------------------------------------------------------
15 // Router
16 // ---------------------------------------------------------------------------
17
18 // Router tracks event.Tag identifiers and routes events to widget handlers.
19 // Source is its interface exposed to widgets.
20 type Router struct {
21 savedTrans []Affine2D
22 transStack []Affine2D
23 handlers map[Tag]*routerHandler
24 pointer struct {
25 queue pointerQueue
26 collector pointerCollector
27 }
28 key struct {
29 queue keyQueue
30 filter keyFilterList
31 nextFilter keyFilterList
32 scratchFilter keyFilterList
33 }
34 cqueue clipboardQueue
35 changes []stateChange
36 reader Reader
37 // InvalidateCmd summary.
38 wakeup bool
39 wakeupTime time.Time
40 commands []InputCommand
41 transfers []io.ReadCloser
42 deferring bool
43 // scratchFilters for garbage-free construction.
44 scratchFilters []taggedFilter
45 }
46
47 // Source implements the interface between a Router and widget handlers.
48 // The zero value is disabled.
49 type Source struct {
50 r *Router
51 disabled bool
52 }
53
54 // routerHandler contains per-handler state tracked by a Router.
55 type routerHandler struct {
56 active bool
57 pointer pointerHandler
58 key keyHandler
59 filter routerFilter
60 nextFilter routerFilter
61 processedFilter routerFilter
62 }
63
64 // routerFilter is the union of a set of Filters.
65 type routerFilter struct {
66 pointer pointerFilter
67 focusable bool
68 }
69
70 // taggedFilter is a filter for a particular tag.
71 type taggedFilter struct {
72 tag Tag
73 filter routerFilter
74 }
75
76 // stateChange represents new state and outgoing events resulting from an incoming event.
77 type stateChange struct {
78 event Event
79 state inputState
80 events []taggedEvent
81 }
82
83 // inputState is an immutable snapshot of routing state.
84 type inputState struct {
85 clipboardState
86 keyState
87 pointerState
88 }
89
90 // taggedEvent represents an event and its target handler.
91 type taggedEvent struct {
92 event Event
93 tag Tag
94 }
95
96 // SystemEvent marks events with platform-specific side-effects.
97 // SystemEvents are never matched by catch-all filters.
98 type SystemEvent struct {
99 Event Event
100 }
101
102 func (SystemEvent) ImplementsEvent() {}
103
104 // Source returns a Source backed by this Router.
105 func (q *Router) Source() Source { return Source{r: q} }
106
107 // Disabled returns a copy of this source that delivers no events.
108 func (s Source) Disabled() Source { s2 := s; s2.disabled = true; return s2 }
109
110 // Enabled reports whether the source is enabled.
111 func (s Source) Enabled() bool { return s.r != nil && !s.disabled }
112
113 // Focused reports whether tag is focused per the last delivered KeyFocusEvent.
114 func (s Source) Focused(tag Tag) bool {
115 if !s.Enabled() {
116 return false
117 }
118 return s.r.state().keyState.focus == tag
119 }
120
121 // Execute queues a command for execution.
122 func (s Source) Execute(c InputCommand) {
123 if !s.Enabled() {
124 return
125 }
126 s.r.execute(c)
127 }
128
129 // Event returns the next event matching at least one of filters.
130 func (s Source) Event(filters ...Filter) (Event, bool) {
131 if !s.Enabled() {
132 return nil, false
133 }
134 return s.r.Event(filters...)
135 }
136
137 func (q *Router) Event(filters ...Filter) (Event, bool) {
138 q.scratchFilters = q.scratchFilters[:0]
139 q.key.scratchFilter = q.key.scratchFilter[:0]
140 for _, f := range filters {
141 var t Tag
142 switch f := f.(type) {
143 case KeyFilter:
144 q.key.scratchFilter = append(q.key.scratchFilter, f)
145 continue
146 case TransferSourceFilter:
147 t = f.Target
148 case TransferTargetFilter:
149 t = f.Target
150 case KeyFocusFilter:
151 t = f.Target
152 case PointerFilter:
153 t = f.Target
154 }
155 if t == nil {
156 continue
157 }
158 var flt *routerFilter
159 for i := range q.scratchFilters {
160 s := &q.scratchFilters[i]
161 if s.tag == t {
162 flt = &s.filter
163 break
164 }
165 }
166 if flt == nil {
167 n := len(q.scratchFilters)
168 if n < cap(q.scratchFilters) {
169 q.scratchFilters = q.scratchFilters[:n+1]
170 tf := &q.scratchFilters[n]
171 tf.tag = t
172 flt = &tf.filter
173 flt.Reset()
174 } else {
175 q.scratchFilters = append(q.scratchFilters, taggedFilter{tag: t})
176 flt = &q.scratchFilters[n].filter
177 }
178 }
179 flt.Add(f)
180 }
181 for _, tf := range q.scratchFilters {
182 h := q.stateFor(tf.tag)
183 h.filter.Merge(tf.filter)
184 h.nextFilter.Merge(tf.filter)
185 }
186 q.key.filter = append(q.key.filter, q.key.scratchFilter...)
187 q.key.nextFilter = append(q.key.nextFilter, q.key.scratchFilter...)
188 // Deliver reset events, if any.
189 for _, f := range filters {
190 switch f := f.(type) {
191 case KeyFocusFilter:
192 if f.Target == nil {
193 break
194 }
195 h := q.stateFor(f.Target)
196 if reset, ok := h.key.ResetEvent(); ok {
197 return reset, true
198 }
199 case PointerFilter:
200 if f.Target == nil {
201 break
202 }
203 h := q.stateFor(f.Target)
204 if reset, ok := h.pointer.ResetEvent(); ok && h.filter.pointer.Matches(reset) {
205 return reset, true
206 }
207 }
208 }
209 for i := range q.changes {
210 if q.deferring && i > 0 {
211 break
212 }
213 change := &q.changes[i]
214 for j, evt := range change.events {
215 match := false
216 switch e := evt.event.(type) {
217 case KeyEvent:
218 match = q.key.scratchFilter.Matches(change.state.keyState.focus, e, false)
219 default:
220 for _, tf := range q.scratchFilters {
221 if evt.tag == tf.tag && tf.filter.Matches(evt.event) {
222 match = true
223 break
224 }
225 }
226 }
227 if match {
228 change.events = deleteTaggedEvent(change.events, j, j+1)
229 q.collapseState(i)
230 return evt.event, true
231 }
232 }
233 }
234 for _, tf := range q.scratchFilters {
235 h := q.stateFor(tf.tag)
236 h.processedFilter.Merge(tf.filter)
237 }
238 return nil, false
239 }
240
241 func (q *Router) collapseState(idx int) {
242 if idx == 0 {
243 return
244 }
245 first := &q.changes[0]
246 first.state = q.changes[idx].state
247 for _, ch := range q.changes[1 : idx+1] {
248 first.events = append(first.events, ch.events...)
249 }
250 q.changes = append(q.changes[:1], q.changes[idx+1:]...)
251 }
252
253 // Frame completes the current frame, collects handlers from ops, and starts a new frame.
254 func (q *Router) Frame(frame *OpList) {
255 var remaining []Event
256 if n := len(q.changes); n > 0 {
257 if q.deferring {
258 for _, ch := range q.changes[1:] {
259 remaining = append(remaining, ch.event)
260 }
261 q.changes = append(q.changes[:0], stateChange{state: q.changes[0].state})
262 } else {
263 state := q.changes[n-1].state
264 q.changes = append(q.changes[:0], stateChange{state: state})
265 }
266 }
267 for _, rc := range q.transfers {
268 if rc != nil {
269 rc.Close()
270 }
271 }
272 q.transfers = nil
273 q.deferring = false
274 for _, h := range q.handlers {
275 h.filter, h.nextFilter = h.nextFilter, h.filter
276 h.nextFilter.Reset()
277 h.processedFilter.Reset()
278 h.pointer.Reset()
279 h.key.Reset()
280 }
281 q.key.filter, q.key.nextFilter = q.key.nextFilter, q.key.filter
282 q.key.nextFilter = q.key.nextFilter[:0]
283 var ops *Ops
284 if frame != nil {
285 ops = &frame.Internal
286 }
287 q.reader.ResetReader(ops)
288 q.collect()
289 for k, h := range q.handlers {
290 if !h.active {
291 delete(q.handlers, k)
292 } else {
293 h.active = false
294 }
295 }
296 q.executeCommands()
297 q.Queue(remaining...)
298 st := q.lastState()
299 pst, evts := q.pointer.queue.Frame(q.handlers, st.pointerState)
300 st.pointerState = pst
301 st.keyState = q.key.queue.Frame(q.handlers, q.lastState().keyState)
302 q.changeState(nil, st, evts)
303 q.collapseState(len(q.changes) - 1)
304 }
305
306 // Queue events to be routed.
307 func (q *Router) Queue(events ...Event) {
308 for _, e := range events {
309 se, system := e.(SystemEvent)
310 if system {
311 e = se.Event
312 }
313 q.processEvent(e, system)
314 }
315 }
316
317 // QueuePointer translates and queues AppPointerEvents.
318 func (q *Router) QueuePointer(events []AppPointerEvent) {
319 for _, e := range events {
320 q.Queue(appPointerToPointerEvent(e))
321 }
322 }
323
324 // QueueKey translates and queues AppKeyEvents.
325 func (q *Router) QueueKey(events []AppKeyEvent) {
326 for _, e := range events {
327 if ke, ok := appKeyToKeyEvent(e); ok {
328 q.Queue(ke)
329 }
330 }
331 }
332
333 func (f *routerFilter) Add(flt Filter) {
334 switch flt := flt.(type) {
335 case KeyFocusFilter:
336 f.focusable = true
337 case PointerFilter:
338 f.pointer.Add(flt)
339 case TransferSourceFilter, TransferTargetFilter:
340 f.pointer.AddTransfer(flt)
341 }
342 }
343
344 func (f *routerFilter) Merge(f2 routerFilter) {
345 f.focusable = f.focusable || f2.focusable
346 f.pointer.Merge(f2.pointer)
347 }
348
349 func (f *routerFilter) Matches(e Event) bool {
350 switch e.(type) {
351 case KeyFocusEvent, SnippetEvent, KeyEditEvent, SelectionEvent:
352 return f.focusable
353 default:
354 return f.pointer.Matches(e)
355 }
356 }
357
358 func (f *routerFilter) Reset() {
359 *f = routerFilter{
360 pointer: pointerFilter{
361 sourceMimes: f.pointer.sourceMimes[:0],
362 targetMimes: f.pointer.targetMimes[:0],
363 },
364 }
365 }
366
367 func (q *Router) processEvent(e Event, system bool) {
368 state := q.lastState()
369 switch e := e.(type) {
370 case PointerEvent:
371 pstate, evts := q.pointer.queue.Push(q.handlers, state.pointerState, e)
372 state.pointerState = pstate
373 q.changeState(e, state, evts)
374 case KeyEvent:
375 var evts []taggedEvent
376 if q.key.filter.Matches(state.keyState.focus, e, system) {
377 evts = append(evts, taggedEvent{event: e})
378 }
379 q.changeState(e, state, evts)
380 case SnippetEvent:
381 if r := state.content.Snippet.KeyRange; rangeOverlaps(r, KeyRange(e)) {
382 if e.Start > r.Start {
383 e.Start = r.Start
384 }
385 if e.End < r.End {
386 e.End = r.End
387 }
388 }
389 var evts []taggedEvent
390 if f := state.focus; f != nil {
391 evts = append(evts, taggedEvent{tag: f, event: e})
392 }
393 q.changeState(e, state, evts)
394 case KeyEditEvent, KeyFocusEvent, SelectionEvent:
395 var evts []taggedEvent
396 if f := state.focus; f != nil {
397 evts = append(evts, taggedEvent{tag: f, event: e})
398 }
399 q.changeState(e, state, evts)
400 case TransferDataEvent:
401 cstate, evts := q.cqueue.Push(state.clipboardState, e)
402 state.clipboardState = cstate
403 q.changeState(e, state, evts)
404 default:
405 // Unknown event type - ignore rather than panic for robustness.
406 }
407 }
408
409 func (q *Router) execute(c InputCommand) {
410 if !q.deferring {
411 ch := q.executeCommand(c)
412 immediate := true
413 for _, e := range ch.events {
414 h, ok := q.handlers[e.tag]
415 immediate = immediate && (!ok || !h.processedFilter.Matches(e.event))
416 }
417 if immediate {
418 var evts []Event
419 for _, ch := range q.changes {
420 if ch.event != nil {
421 evts = append(evts, ch.event)
422 }
423 }
424 if len(q.changes) > 1 {
425 q.changes = q.changes[:1]
426 }
427 q.changeState(nil, ch.state, ch.events)
428 q.Queue(evts...)
429 return
430 }
431 }
432 q.deferring = true
433 q.commands = append(q.commands, c)
434 }
435
436 func (q *Router) state() inputState {
437 if len(q.changes) > 0 {
438 return q.changes[0].state
439 }
440 return inputState{}
441 }
442
443 func (q *Router) lastState() inputState {
444 if n := len(q.changes); n > 0 {
445 return q.changes[n-1].state
446 }
447 return inputState{}
448 }
449
450 func (q *Router) executeCommands() {
451 for _, c := range q.commands {
452 ch := q.executeCommand(c)
453 q.changeState(nil, ch.state, ch.events)
454 }
455 q.commands = nil
456 }
457
458 func (q *Router) executeCommand(c InputCommand) stateChange {
459 state := q.state()
460 var evts []taggedEvent
461 switch req := c.(type) {
462 case SelectionCmd:
463 state.keyState = q.key.queue.setSelection(state.keyState, req)
464 case FocusCmd:
465 state.keyState, evts = q.key.queue.Focus(q.handlers, state.keyState, req.Tag)
466 case SoftKeyboardCmd:
467 state.keyState = state.keyState.softKeyboard(req.Show)
468 case SnippetCmd:
469 state.keyState = q.key.queue.setSnippet(state.keyState, req)
470 case TransferOfferCmd:
471 state.pointerState, evts = q.pointer.queue.offerData(q.handlers, state.pointerState, req)
472 case ClipboardWriteCmd:
473 q.cqueue.ProcessWriteClipboard(req)
474 case ClipboardReadCmd:
475 state.clipboardState = q.cqueue.ProcessReadClipboard(state.clipboardState, req.Tag)
476 case GrabCmd:
477 state.pointerState, evts = q.pointer.queue.grab(state.pointerState, req)
478 case InvalidateCmd:
479 if !q.wakeup || req.At.Before(q.wakeupTime) {
480 q.wakeup = true
481 q.wakeupTime = req.At
482 }
483 }
484 return stateChange{state: state, events: evts}
485 }
486
487 func (q *Router) changeState(e Event, state inputState, evts []taggedEvent) {
488 for i := range evts {
489 ev := &evts[i]
490 if de, ok := ev.event.(TransferDataEvent); ok {
491 transferIdx := len(q.transfers)
492 data := de.Open()
493 q.transfers = append(q.transfers, data)
494 de.Open = func() io.ReadCloser {
495 q.transfers[transferIdx] = nil
496 return data
497 }
498 ev.event = de
499 }
500 }
501 if len(q.changes) == 0 {
502 q.changes = append(q.changes, stateChange{})
503 }
504 if e != nil && len(evts) > 0 {
505 q.changes = append(q.changes, stateChange{event: e, state: state, events: evts})
506 } else {
507 prev := &q.changes[len(q.changes)-1]
508 prev.state = state
509 prev.events = append(prev.events, evts...)
510 }
511 }
512
513 func rangeOverlaps(r1, r2 KeyRange) bool {
514 r1 = rangeNorm(r1)
515 r2 = rangeNorm(r2)
516 return r1.Start <= r2.Start && r2.Start < r1.End ||
517 r1.Start <= r2.End && r2.End < r1.End
518 }
519
520 func rangeNorm(r KeyRange) KeyRange {
521 if r.End < r.Start {
522 r.End, r.Start = r.Start, r.End
523 }
524 return r
525 }
526
527 // MoveFocus attempts to move the focus in direction dir.
528 func (q *Router) MoveFocus(dir FocusDirection) {
529 state := q.lastState()
530 kstate, evts := q.key.queue.MoveFocus(q.handlers, state.keyState, dir)
531 state.keyState = kstate
532 q.changeState(nil, state, evts)
533 }
534
535 // RevealFocus scrolls the current focus into viewport.
536 func (q *Router) RevealFocus(viewport image.Rectangle) {
537 state := q.lastState()
538 focus := state.focus
539 if focus == nil {
540 return
541 }
542 kh := &q.handlers[focus].key
543 bounds := q.key.queue.BoundsFor(kh)
544 area := q.key.queue.AreaFor(kh)
545 viewport = q.pointer.queue.ClipFor(area, viewport)
546 topleft := bounds.Min.Sub(viewport.Min)
547 topleft = maxImgPt(topleft, bounds.Max.Sub(viewport.Max))
548 topleft = minImgPt(image.Pt(0, 0), topleft)
549 bottomright := bounds.Max.Sub(viewport.Max)
550 bottomright = minImgPt(bottomright, bounds.Min.Sub(viewport.Min))
551 bottomright = maxImgPt(image.Pt(0, 0), bottomright)
552 s := topleft
553 if s.X == 0 {
554 s.X = bottomright.X
555 }
556 if s.Y == 0 {
557 s.Y = bottomright.Y
558 }
559 q.ScrollFocus(s)
560 }
561
562 // ScrollFocus scrolls the focused widget by dist.
563 func (q *Router) ScrollFocus(dist image.Point) {
564 state := q.lastState()
565 focus := state.focus
566 if focus == nil {
567 return
568 }
569 kh := &q.handlers[focus].key
570 area := q.key.queue.AreaFor(kh)
571 q.changeState(nil, q.lastState(), q.pointer.queue.Deliver(q.handlers, area, PointerEvent{
572 Kind: PointerScroll,
573 Source: Touch,
574 Scroll: FPt(dist),
575 }))
576 }
577
578 // ActionAt returns the action at pos, if any.
579 func (q *Router) ActionAt(p Point) (SystemAction, bool) {
580 return q.pointer.queue.ActionAt(p)
581 }
582
583 // ClickFocus synthesizes a click on the focused widget.
584 func (q *Router) ClickFocus() {
585 focus := q.lastState().focus
586 if focus == nil {
587 return
588 }
589 kh := &q.handlers[focus].key
590 bounds := q.key.queue.BoundsFor(kh)
591 center := bounds.Max.Add(bounds.Min).Div(2)
592 e := PointerEvent{
593 Position: Pt(float32(center.X), float32(center.Y)),
594 Source: Touch,
595 }
596 area := q.key.queue.AreaFor(kh)
597 e.Kind = PointerPress
598 state := q.lastState()
599 q.changeState(nil, state, q.pointer.queue.Deliver(q.handlers, area, e))
600 e.Kind = PointerRelease
601 q.changeState(nil, state, q.pointer.queue.Deliver(q.handlers, area, e))
602 }
603
604 // TextInputState returns the current text input state.
605 func (q *Router) TextInputState() TextInputState {
606 state := q.state()
607 kstate, s := state.InputState()
608 state.keyState = kstate
609 q.changeState(nil, state, nil)
610 return s
611 }
612
613 // TextInputHint returns the current key input hint.
614 func (q *Router) TextInputHint() (InputHint, bool) {
615 return q.key.queue.InputHint(q.handlers, q.state().keyState)
616 }
617
618 // WriteClipboard returns the most recent content to copy to clipboard.
619 func (q *Router) WriteClipboard() (mime string, content []byte, ok bool) {
620 return q.cqueue.WriteClipboard()
621 }
622
623 // ClipboardRequested reports if any handler is waiting for clipboard contents.
624 func (q *Router) ClipboardRequested() bool {
625 return q.cqueue.ClipboardRequested(q.lastState().clipboardState)
626 }
627
628 // Cursor returns the last cursor set.
629 func (q *Router) Cursor() Cursor {
630 return q.state().cursor
631 }
632
633 // WakeupTime returns the most recent requested wakeup time.
634 func (q *Router) WakeupTime() (time.Time, bool) {
635 t, w := q.wakeupTime, q.wakeup
636 q.wakeup = false
637 if len(q.changes) > 1 || len(q.changes) == 1 && len(q.changes[0].events) > 0 {
638 t, w = time.Time{}, true
639 }
640 return t, w
641 }
642
643 func (q *Router) stateFor(tag Tag) *routerHandler {
644 if tag == nil {
645 panic("internal error: nil tag")
646 }
647 s, ok := q.handlers[tag]
648 if !ok {
649 s = &routerHandler{}
650 if q.handlers == nil {
651 q.handlers = map[Tag]*routerHandler{}
652 }
653 q.handlers[tag] = s
654 }
655 s.active = true
656 return s
657 }
658
659 func (q *Router) collect() {
660 q.transStack = q.transStack[:0]
661 pc := &q.pointer.collector
662 pc.q = &q.pointer.queue
663 pc.Reset()
664 kq := &q.key.queue
665 q.key.queue.Reset()
666 t := AffineId()
667 for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
668 switch OpType(encOp.Data[0]) {
669 case TypeSave:
670 id := DecodeSave(encOp.Data)
671 for extra := id - len(q.savedTrans) + 1; extra > 0; extra-- {
672 q.savedTrans = append(q.savedTrans, AffineId())
673 }
674 q.savedTrans[id] = t
675 case TypeLoad:
676 id := DecodeLoad(encOp.Data)
677 t = q.savedTrans[id]
678 pc.resetState()
679 pc.setTrans(t)
680 case TypeClip:
681 var op DecodedClipOp
682 op.Decode(encOp.Data)
683 pc.clip(op)
684 case TypePopClip:
685 pc.popArea()
686 case TypeTransform:
687 t2, push := DecodeTransform(encOp.Data)
688 if push {
689 q.transStack = append(q.transStack, t)
690 }
691 t = t.Mul(t2)
692 pc.setTrans(t)
693 case TypePopTransform:
694 n := len(q.transStack)
695 t = q.transStack[n-1]
696 q.transStack = q.transStack[:n-1]
697 pc.setTrans(t)
698 case TypeInput:
699 tag := encOp.Refs[0].(Tag)
700 s := q.stateFor(tag)
701 pc.inputOp(tag, &s.pointer)
702 a := pc.currentArea()
703 b := pc.currentAreaBounds()
704 if s.filter.focusable {
705 kq.inputOp(tag, &s.key, t, a, b)
706 }
707 case TypePass:
708 pc.pass()
709 case TypePopPass:
710 pc.popPass()
711 case TypeCursor:
712 name := Cursor(encOp.Data[1])
713 pc.cursor(name)
714 case TypeActionInput:
715 act := SystemAction(encOp.Data[1])
716 pc.actionInputOp(act)
717 case TypeKeyInputHint:
718 op := KeyInputHintOp{
719 Tag: encOp.Refs[0].(Tag),
720 Hint: InputHint(encOp.Data[1]),
721 }
722 s := q.stateFor(op.Tag)
723 s.key.inputHint(op.Hint)
724 case TypeSemanticLabel:
725 lbl := *encOp.Refs[0].(*string)
726 pc.semanticLabel(lbl)
727 case TypeSemanticDesc:
728 desc := *encOp.Refs[0].(*string)
729 pc.semanticDesc(desc)
730 case TypeSemanticClass:
731 class := SemanticClassOp(encOp.Data[1])
732 pc.semanticClass(class)
733 case TypeSemanticSelected:
734 pc.semanticSelected(encOp.Data[1] != 0)
735 case TypeSemanticEnabled:
736 pc.semanticEnabled(encOp.Data[1] != 0)
737 }
738 }
739 }
740
741 // SemanticID uniquely identifies a SemanticDesc.
742 type SemanticID uint
743
744 // SemanticGestures is a bit-set of supported gestures.
745 type SemanticGestures int
746
747 const (
748 ClickGesture SemanticGestures = 1 << iota
749 ScrollGesture
750 )
751
752 // SemanticNode represents a node in the semantic tree.
753 type SemanticNode struct {
754 ID SemanticID
755 ParentID SemanticID
756 Children []SemanticNode
757 Desc SemanticDesc
758 areaIdx int
759 }
760
761 // SemanticDesc provides a semantic description of a UI component.
762 type SemanticDesc struct {
763 Class SemanticClassOp
764 Description string
765 Label string
766 Selected bool
767 Disabled bool
768 Gestures SemanticGestures
769 Bounds image.Rectangle
770 }
771
772 // SemanticAt returns the first semantic description under pos, if any.
773 func (q *Router) SemanticAt(pos Point) (SemanticID, bool) {
774 return q.pointer.queue.SemanticAt(pos)
775 }
776
777 // AppendSemantics appends the semantic tree to nodes and returns the result.
778 func (q *Router) AppendSemantics(nodes []SemanticNode) []SemanticNode {
779 q.pointer.collector.q = &q.pointer.queue
780 q.pointer.collector.ensureRoot()
781 return q.pointer.queue.AppendSemantics(nodes)
782 }
783
784 // EditorState returns the editor state for the focused handler.
785 func (q *Router) EditorState() EditorState {
786 return q.key.queue.editorState(q.handlers, q.state().keyState)
787 }
788
789 // EditorState represents the state of an editor.
790 type EditorState struct {
791 Selection struct {
792 Transform Affine2D
793 KeyRange
794 Caret
795 }
796 Snippet Snippet
797 }
798
799 // TextInputState represents the text input open/close state.
800 type TextInputState uint8
801
802 const (
803 TextInputKeep TextInputState = iota
804 TextInputClose
805 TextInputOpen
806 )
807
808 func maxImgPt(p1, p2 image.Point) image.Point {
809 if p2.X > p1.X {
810 p1.X = p2.X
811 }
812 if p2.Y > p1.Y {
813 p1.Y = p2.Y
814 }
815 return p1
816 }
817
818 func minImgPt(p1, p2 image.Point) image.Point {
819 if p2.X < p1.X {
820 p1.X = p2.X
821 }
822 if p2.Y < p1.Y {
823 p1.Y = p2.Y
824 }
825 return p1
826 }
827
828 // ---------------------------------------------------------------------------
829 // Pointer queue (ported from pointer.go)
830 // ---------------------------------------------------------------------------
831
832 type pointerQueue struct {
833 hitTree []hitNode
834 areas []areaNode
835
836 semantic struct {
837 idsAssigned bool
838 lastID SemanticID
839 contentIDs map[semanticContent][]semanticID
840 }
841 }
842
843 type hitNode struct {
844 next int
845 area int
846 tag Tag
847 pass bool
848 }
849
850 type pointerState struct {
851 cursor Cursor
852 pointers []pointerInfo
853 }
854
855 type pointerInfo struct {
856 id PointerID
857 pressed bool
858 handlers []Tag
859 last PointerEvent
860 entered []Tag
861 dataSource Tag
862 dataTarget Tag
863 }
864
865 type pointerHandler struct {
866 areaPlusOne int
867 setup bool
868 }
869
870 type pointerFilter struct {
871 kinds PointerKind
872 scrollX ScrollRange
873 scrollY ScrollRange
874 sourceMimes []string
875 targetMimes []string
876 }
877
878 type areaOp struct {
879 kind areaKind
880 rect image.Rectangle
881 }
882
883 type areaNode struct {
884 trans Affine2D
885 area areaOp
886 cursor Cursor
887 parent int
888 firstChild int
889 lastChild int
890 sibling int
891 semantic struct {
892 valid bool
893 id SemanticID
894 content semanticContent
895 }
896 action SystemAction
897 }
898
899 type areaKind uint8
900
901 const (
902 areaRect areaKind = iota
903 areaEllipse
904 )
905
906 type collectState struct {
907 t Affine2D
908 nodePlusOne int
909 pass int
910 }
911
912 type pointerCollector struct {
913 q *pointerQueue
914 state collectState
915 nodeStack []int
916 }
917
918 type semanticContent struct {
919 tag Tag
920 label string
921 desc string
922 class SemanticClassOp
923 gestures SemanticGestures
924 selected bool
925 disabled bool
926 }
927
928 type semanticID struct {
929 id SemanticID
930 used bool
931 }
932
933 func (c *pointerCollector) resetState() {
934 c.state = collectState{t: AffineId()}
935 c.nodeStack = c.nodeStack[:0]
936 if len(c.q.hitTree) > 0 {
937 c.state.nodePlusOne = 0 + 1
938 }
939 }
940
941 func (c *pointerCollector) setTrans(t Affine2D) { c.state.t = t }
942
943 func (c *pointerCollector) clip(op DecodedClipOp) {
944 kind := areaRect
945 if op.Shape == ShapeEllipse {
946 kind = areaEllipse
947 }
948 c.pushArea(kind, op.Bounds)
949 }
950
951 func (c *pointerCollector) pushArea(kind areaKind, bounds image.Rectangle) {
952 parentID := c.currentArea()
953 areaID := len(c.q.areas)
954 if parentID != -1 {
955 parent := &c.q.areas[parentID]
956 if parent.firstChild == -1 {
957 parent.firstChild = areaID
958 }
959 if siblingID := parent.lastChild; siblingID != -1 {
960 c.q.areas[siblingID].sibling = areaID
961 }
962 parent.lastChild = areaID
963 }
964 an := areaNode{
965 trans: c.state.t,
966 area: areaOp{kind: kind, rect: bounds},
967 parent: parentID,
968 sibling: -1,
969 firstChild: -1,
970 lastChild: -1,
971 }
972 c.q.areas = append(c.q.areas, an)
973 c.nodeStack = append(c.nodeStack, c.state.nodePlusOne-1)
974 c.addHitNode(hitNode{area: areaID, pass: true})
975 }
976
977 func (c *pointerCollector) popArea() {
978 n := len(c.nodeStack)
979 c.state.nodePlusOne = c.nodeStack[n-1] + 1
980 c.nodeStack = c.nodeStack[:n-1]
981 }
982
983 func (c *pointerCollector) pass() { c.state.pass++ }
984 func (c *pointerCollector) popPass() { c.state.pass-- }
985
986 func (c *pointerCollector) currentArea() int {
987 if i := c.state.nodePlusOne - 1; i != -1 {
988 return c.q.hitTree[i].area
989 }
990 return -1
991 }
992
993 func (c *pointerCollector) currentAreaBounds() image.Rectangle {
994 a := c.currentArea()
995 if a == -1 {
996 panic("no root area")
997 }
998 return c.q.areas[a].bounds()
999 }
1000
1001 func (c *pointerCollector) addHitNode(n hitNode) {
1002 n.next = c.state.nodePlusOne - 1
1003 c.q.hitTree = append(c.q.hitTree, n)
1004 c.state.nodePlusOne = len(c.q.hitTree) - 1 + 1
1005 }
1006
1007 func (c *pointerCollector) newHandler(tag Tag, state *pointerHandler) {
1008 areaID := c.currentArea()
1009 c.addHitNode(hitNode{area: areaID, tag: tag, pass: c.state.pass > 0})
1010 state.areaPlusOne = areaID + 1
1011 }
1012
1013 func (s *pointerHandler) Reset() { s.areaPlusOne = 0 }
1014 func (s *pointerHandler) ResetEvent() (Event, bool) {
1015 if s.setup {
1016 return nil, false
1017 }
1018 s.setup = true
1019 return PointerEvent{Kind: PointerCancel}, true
1020 }
1021
1022 func (c *pointerCollector) actionInputOp(act SystemAction) {
1023 areaID := c.currentArea()
1024 c.q.areas[areaID].action = act
1025 }
1026
1027 func (c *pointerCollector) inputOp(tag Tag, state *pointerHandler) {
1028 areaID := c.currentArea()
1029 c.q.areas[areaID].semantic.content.tag = tag
1030 c.newHandler(tag, state)
1031 }
1032
1033 func (c *pointerCollector) cursor(cur Cursor) {
1034 areaID := c.currentArea()
1035 c.q.areas[areaID].cursor = cur
1036 }
1037
1038 func (c *pointerCollector) semanticLabel(lbl string) {
1039 areaID := c.currentArea()
1040 a := &c.q.areas[areaID]
1041 a.semantic.valid = true
1042 a.semantic.content.label = lbl
1043 }
1044
1045 func (c *pointerCollector) semanticDesc(desc string) {
1046 areaID := c.currentArea()
1047 a := &c.q.areas[areaID]
1048 a.semantic.valid = true
1049 a.semantic.content.desc = desc
1050 }
1051
1052 func (c *pointerCollector) semanticClass(class SemanticClassOp) {
1053 areaID := c.currentArea()
1054 a := &c.q.areas[areaID]
1055 a.semantic.valid = true
1056 a.semantic.content.class = class
1057 }
1058
1059 func (c *pointerCollector) semanticSelected(selected bool) {
1060 areaID := c.currentArea()
1061 a := &c.q.areas[areaID]
1062 a.semantic.valid = true
1063 a.semantic.content.selected = selected
1064 }
1065
1066 func (c *pointerCollector) semanticEnabled(enabled bool) {
1067 areaID := c.currentArea()
1068 a := &c.q.areas[areaID]
1069 a.semantic.valid = true
1070 a.semantic.content.disabled = !enabled
1071 }
1072
1073 func (c *pointerCollector) Reset() {
1074 c.q.reset()
1075 c.resetState()
1076 c.ensureRoot()
1077 }
1078
1079 func (c *pointerCollector) ensureRoot() {
1080 if len(c.q.areas) > 0 {
1081 return
1082 }
1083 c.pushArea(areaRect, image.Rect(-1e6, -1e6, 1e6, 1e6))
1084 c.q.areas[0].semantic.valid = true
1085 }
1086
1087 func (q *pointerQueue) reset() {
1088 q.hitTree = q.hitTree[:0]
1089 q.areas = q.areas[:0]
1090 q.semantic.idsAssigned = false
1091 for k, ids := range q.semantic.contentIDs {
1092 for i := len(ids) - 1; i >= 0; i-- {
1093 if !ids[i].used {
1094 ids = deleteSemanticID(ids, i, i+1)
1095 } else {
1096 ids[i].used = false
1097 }
1098 }
1099 if len(ids) > 0 {
1100 q.semantic.contentIDs[k] = ids
1101 } else {
1102 delete(q.semantic.contentIDs, k)
1103 }
1104 }
1105 }
1106
1107 func (q *pointerQueue) assignSemIDs() {
1108 if q.semantic.idsAssigned {
1109 return
1110 }
1111 q.semantic.idsAssigned = true
1112 for i, a := range q.areas {
1113 if a.semantic.valid {
1114 q.areas[i].semantic.id = q.semanticIDFor(a.semantic.content)
1115 }
1116 }
1117 }
1118
1119 func (q *pointerQueue) AppendSemantics(nodes []SemanticNode) []SemanticNode {
1120 q.assignSemIDs()
1121 nodes = q.appendSemanticChildren(nodes, 0)
1122 nodes = q.appendSemanticArea(nodes, 0, 0)
1123 return nodes
1124 }
1125
1126 func (q *pointerQueue) appendSemanticArea(nodes []SemanticNode, parentID SemanticID, nodeIdx int) []SemanticNode {
1127 areaIdx := nodes[nodeIdx].areaIdx
1128 a := q.areas[areaIdx]
1129 childStart := len(nodes)
1130 nodes = q.appendSemanticChildren(nodes, a.firstChild)
1131 childEnd := len(nodes)
1132 for i := childStart; i < childEnd; i++ {
1133 nodes = q.appendSemanticArea(nodes, a.semantic.id, i)
1134 }
1135 n := &nodes[nodeIdx]
1136 n.ParentID = parentID
1137 n.Children = nodes[childStart:childEnd]
1138 return nodes
1139 }
1140
1141 func (q *pointerQueue) appendSemanticChildren(nodes []SemanticNode, areaIdx int) []SemanticNode {
1142 if areaIdx == -1 {
1143 return nodes
1144 }
1145 a := q.areas[areaIdx]
1146 if semID := a.semantic.id; semID != 0 {
1147 cnt := a.semantic.content
1148 nodes = append(nodes, SemanticNode{
1149 ID: semID,
1150 Desc: SemanticDesc{
1151 Bounds: a.bounds(),
1152 Label: cnt.label,
1153 Description: cnt.desc,
1154 Class: cnt.class,
1155 Gestures: cnt.gestures,
1156 Selected: cnt.selected,
1157 Disabled: cnt.disabled,
1158 },
1159 areaIdx: areaIdx,
1160 })
1161 } else {
1162 nodes = q.appendSemanticChildren(nodes, a.firstChild)
1163 }
1164 return q.appendSemanticChildren(nodes, a.sibling)
1165 }
1166
1167 func (q *pointerQueue) semanticIDFor(content semanticContent) SemanticID {
1168 ids := q.semantic.contentIDs[content]
1169 for i, id := range ids {
1170 if !id.used {
1171 ids[i].used = true
1172 return id.id
1173 }
1174 }
1175 q.semantic.lastID++
1176 id := semanticID{id: q.semantic.lastID, used: true}
1177 if q.semantic.contentIDs == nil {
1178 q.semantic.contentIDs = map[semanticContent][]semanticID{}
1179 }
1180 q.semantic.contentIDs[content] = append(q.semantic.contentIDs[content], id)
1181 return id.id
1182 }
1183
1184 func (q *pointerQueue) ActionAt(pos Point) (action SystemAction, hasAction bool) {
1185 q.hitTest(pos, func(n *hitNode) bool {
1186 area := q.areas[n.area]
1187 if area.action != 0 {
1188 action = area.action
1189 hasAction = true
1190 return false
1191 }
1192 return true
1193 })
1194 return action, hasAction
1195 }
1196
1197 func (q *pointerQueue) SemanticAt(pos Point) (semID SemanticID, hasSemID bool) {
1198 q.assignSemIDs()
1199 q.hitTest(pos, func(n *hitNode) bool {
1200 area := q.areas[n.area]
1201 if area.semantic.id != 0 {
1202 semID = area.semantic.id
1203 hasSemID = true
1204 return false
1205 }
1206 return true
1207 })
1208 return semID, hasSemID
1209 }
1210
1211 func (q *pointerQueue) hitTest(pos Point, onNode func(*hitNode) bool) Cursor {
1212 pass := true
1213 idx := len(q.hitTree) - 1
1214 cursor := CursorDefault
1215 for idx >= 0 {
1216 n := &q.hitTree[idx]
1217 hit, c := q.hit(n.area, pos)
1218 if !hit {
1219 idx--
1220 continue
1221 }
1222 if cursor == CursorDefault {
1223 cursor = c
1224 }
1225 pass = pass && n.pass
1226 if pass {
1227 idx--
1228 } else {
1229 idx = n.next
1230 }
1231 if !onNode(n) {
1232 break
1233 }
1234 }
1235 return cursor
1236 }
1237
1238 func (q *pointerQueue) invTransform(areaIdx int, p Point) Point {
1239 if areaIdx == -1 {
1240 return p
1241 }
1242 return q.areas[areaIdx].trans.Invert().Transform(p)
1243 }
1244
1245 func (q *pointerQueue) hit(areaIdx int, p Point) (bool, Cursor) {
1246 c := CursorDefault
1247 for areaIdx != -1 {
1248 a := &q.areas[areaIdx]
1249 if c == CursorDefault {
1250 c = a.cursor
1251 }
1252 tp := a.trans.Invert().Transform(p)
1253 if !a.area.Hit(tp) {
1254 return false, c
1255 }
1256 areaIdx = a.parent
1257 }
1258 return true, c
1259 }
1260
1261 func (q *pointerQueue) grab(state pointerState, req GrabCmd) (pointerState, []taggedEvent) {
1262 var evts []taggedEvent
1263 for _, p := range state.pointers {
1264 if !p.pressed || p.id != req.ID {
1265 continue
1266 }
1267 found := containsTag(p.handlers, req.Tag)
1268 if !found {
1269 continue
1270 }
1271 for i := len(p.handlers) - 1; i >= 0; i-- {
1272 if tag := p.handlers[i]; tag != req.Tag {
1273 evts = append(evts, taggedEvent{
1274 tag: tag,
1275 event: PointerEvent{Kind: PointerCancel},
1276 })
1277 state = dropHandler(state, tag)
1278 }
1279 }
1280 break
1281 }
1282 return state, evts
1283 }
1284
1285 func (q *pointerQueue) offerData(handlers map[Tag]*routerHandler, state pointerState, req TransferOfferCmd) (pointerState, []taggedEvent) {
1286 var evts []taggedEvent
1287 for i, p := range state.pointers {
1288 if p.dataSource != req.Tag {
1289 continue
1290 }
1291 if p.dataTarget != nil {
1292 evts = append(evts, taggedEvent{tag: p.dataTarget, event: TransferDataEvent{
1293 Type: req.Type,
1294 Open: func() io.ReadCloser { return req.Data },
1295 }})
1296 }
1297 state.pointers = clonePointers(state.pointers)
1298 state.pointers[i], evts = q.deliverTransferCancelEvent(handlers, p, evts)
1299 break
1300 }
1301 return state, evts
1302 }
1303
1304 func (q *pointerQueue) Frame(handlers map[Tag]*routerHandler, state pointerState) (pointerState, []taggedEvent) {
1305 for _, h := range handlers {
1306 if h.pointer.areaPlusOne != 0 {
1307 area := &q.areas[h.pointer.areaPlusOne-1]
1308 if h.filter.pointer.kinds&(PointerPress|PointerRelease) != 0 {
1309 area.semantic.content.gestures |= ClickGesture
1310 }
1311 if h.filter.pointer.kinds&PointerScroll != 0 {
1312 area.semantic.content.gestures |= ScrollGesture
1313 }
1314 area.semantic.valid = area.semantic.content.gestures != 0
1315 }
1316 }
1317 var evts []taggedEvent
1318 for i, p := range state.pointers {
1319 changed := false
1320 p, evts, state.cursor, changed = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, p.last)
1321 if changed {
1322 state.pointers = clonePointers(state.pointers)
1323 state.pointers[i] = p
1324 }
1325 }
1326 return state, evts
1327 }
1328
1329 func dropHandler(state pointerState, tag Tag) pointerState {
1330 pointers := state.pointers
1331 state.pointers = nil
1332 for _, p := range pointers {
1333 handlers := p.handlers
1334 p.handlers = nil
1335 for _, h := range handlers {
1336 if h != tag {
1337 p.handlers = append(p.handlers, h)
1338 }
1339 }
1340 entered := p.entered
1341 p.entered = nil
1342 for _, h := range entered {
1343 if h != tag {
1344 p.entered = append(p.entered, h)
1345 }
1346 }
1347 state.pointers = append(state.pointers, p)
1348 }
1349 return state
1350 }
1351
1352 func (s pointerState) pointerOf(e PointerEvent) (pointerState, int) {
1353 for i, p := range s.pointers {
1354 if p.id == e.PointerID {
1355 return s, i
1356 }
1357 }
1358 n := len(s.pointers)
1359 s.pointers = append(s.pointers[:n:n], pointerInfo{id: e.PointerID})
1360 return s, len(s.pointers) - 1
1361 }
1362
1363 func (q *pointerQueue) Deliver(handlers map[Tag]*routerHandler, areaIdx int, e PointerEvent) []taggedEvent {
1364 scroll := e.Scroll
1365 idx := len(q.hitTree) - 1
1366 for idx != -1 {
1367 n := &q.hitTree[idx]
1368 if n.area == areaIdx {
1369 break
1370 }
1371 idx--
1372 }
1373 var evts []taggedEvent
1374 for idx != -1 {
1375 n := &q.hitTree[idx]
1376 idx = n.next
1377 h, ok := handlers[n.tag]
1378 if !ok || !h.filter.pointer.Matches(e) {
1379 continue
1380 }
1381 e := e
1382 if e.Kind == PointerScroll {
1383 if scroll == (Point{}) {
1384 break
1385 }
1386 scroll, e.Scroll = h.filter.pointer.clampScroll(scroll)
1387 }
1388 e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
1389 evts = append(evts, taggedEvent{tag: n.tag, event: e})
1390 if e.Kind != PointerScroll {
1391 break
1392 }
1393 }
1394 return evts
1395 }
1396
1397 func (q *pointerQueue) ClipFor(area int, r image.Rectangle) image.Rectangle {
1398 a := &q.areas[area]
1399 parent := a.parent
1400 for parent != -1 {
1401 a := &q.areas[parent]
1402 r = r.Intersect(a.bounds())
1403 parent = a.parent
1404 }
1405 return r
1406 }
1407
1408 func (q *pointerQueue) Push(handlers map[Tag]*routerHandler, state pointerState, e PointerEvent) (pointerState, []taggedEvent) {
1409 var evts []taggedEvent
1410 if e.Kind == PointerCancel {
1411 for k := range handlers {
1412 evts = append(evts, taggedEvent{
1413 event: PointerEvent{Kind: PointerCancel},
1414 tag: k,
1415 })
1416 }
1417 state.pointers = nil
1418 return state, evts
1419 }
1420 state, pidx := state.pointerOf(e)
1421 p := state.pointers[pidx]
1422
1423 switch e.Kind {
1424 case PointerPress:
1425 p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
1426 p.pressed = true
1427 evts = q.deliverEvent(handlers, p, evts, e)
1428 case PointerMove:
1429 if p.pressed {
1430 e.Kind = PointerDrag
1431 }
1432 p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
1433 evts = q.deliverEvent(handlers, p, evts, e)
1434 if p.pressed {
1435 p, evts = q.deliverDragEvent(handlers, p, evts)
1436 }
1437 case PointerRelease:
1438 evts = q.deliverEvent(handlers, p, evts, e)
1439 p.pressed = false
1440 p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
1441 p, evts = q.deliverDropEvent(handlers, p, evts)
1442 case PointerScroll:
1443 p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
1444 evts = q.deliverEvent(handlers, p, evts, e)
1445 }
1446
1447 p.last = e
1448 if !p.pressed && len(p.entered) == 0 {
1449 // concat equivalent: remove pidx
1450 state.pointers = concatPointers(state.pointers[:pidx:pidx], state.pointers[pidx+1:])
1451 } else {
1452 state.pointers = clonePointers(state.pointers)
1453 state.pointers[pidx] = p
1454 }
1455 return state, evts
1456 }
1457
1458 func (q *pointerQueue) deliverEvent(handlers map[Tag]*routerHandler, p pointerInfo, evts []taggedEvent, e PointerEvent) []taggedEvent {
1459 if p.pressed && len(p.handlers) == 1 {
1460 e.Priority = Grabbed
1461 }
1462 scroll := e.Scroll
1463 for _, k := range p.handlers {
1464 h, ok := handlers[k]
1465 if !ok {
1466 continue
1467 }
1468 f := h.filter.pointer
1469 if !f.Matches(e) {
1470 continue
1471 }
1472 if e.Kind == PointerScroll {
1473 if scroll == (Point{}) {
1474 return evts
1475 }
1476 scroll, e.Scroll = f.clampScroll(scroll)
1477 }
1478 ev := e
1479 ev.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
1480 evts = append(evts, taggedEvent{event: ev, tag: k})
1481 }
1482 return evts
1483 }
1484
1485 func (q *pointerQueue) deliverEnterLeaveEvents(handlers map[Tag]*routerHandler, cursor Cursor, p pointerInfo, evts []taggedEvent, e PointerEvent) (pointerInfo, []taggedEvent, Cursor, bool) {
1486 changed := false
1487 var hits []Tag
1488 if e.Source != Mouse && !p.pressed && e.Kind != PointerPress {
1489 // Non-mouse pointers leave when released.
1490 } else {
1491 var transSrc *pointerFilter
1492 if p.dataSource != nil {
1493 transSrc = &handlers[p.dataSource].filter.pointer
1494 }
1495 cursor = q.hitTest(e.Position, func(n *hitNode) bool {
1496 h, ok := handlers[n.tag]
1497 if !ok {
1498 return true
1499 }
1500 add := true
1501 if p.pressed {
1502 add = false
1503 if _, found := searchTag(p.handlers, n.tag); found {
1504 add = true
1505 }
1506 if transSrc != nil {
1507 if _, ok := firstMimeMatch(transSrc, &h.filter.pointer); ok {
1508 add = true
1509 }
1510 }
1511 }
1512 if add {
1513 hits = addTagToList(hits, n.tag)
1514 }
1515 return true
1516 })
1517 if !p.pressed {
1518 changed = true
1519 p.handlers = hits
1520 }
1521 }
1522 // Deliver Leave events.
1523 for _, k := range p.entered {
1524 if _, found := searchTag(hits, k); found {
1525 continue
1526 }
1527 h, ok := handlers[k]
1528 if !ok {
1529 continue
1530 }
1531 changed = true
1532 ev := e
1533 ev.Kind = PointerLeave
1534 if h.filter.pointer.Matches(ev) {
1535 ev.Position = q.invTransform(h.pointer.areaPlusOne-1, ev.Position)
1536 evts = append(evts, taggedEvent{tag: k, event: ev})
1537 }
1538 }
1539 // Deliver Enter events.
1540 for _, k := range hits {
1541 if _, found := searchTag(p.entered, k); found {
1542 continue
1543 }
1544 h, ok := handlers[k]
1545 if !ok {
1546 continue
1547 }
1548 changed = true
1549 ev := e
1550 ev.Kind = PointerEnter
1551 if h.filter.pointer.Matches(ev) {
1552 ev.Position = q.invTransform(h.pointer.areaPlusOne-1, ev.Position)
1553 evts = append(evts, taggedEvent{tag: k, event: ev})
1554 }
1555 }
1556 p.entered = hits
1557 return p, evts, cursor, changed
1558 }
1559
1560 func (q *pointerQueue) deliverDragEvent(handlers map[Tag]*routerHandler, p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
1561 if p.dataSource != nil {
1562 return p, evts
1563 }
1564 for _, k := range p.entered {
1565 src := &handlers[k].filter.pointer
1566 if len(src.sourceMimes) == 0 {
1567 continue
1568 }
1569 p.dataSource = k
1570 for k, tgt := range handlers {
1571 if _, ok := firstMimeMatch(src, &tgt.filter.pointer); ok {
1572 evts = append(evts, taggedEvent{tag: k, event: TransferInitiateEvent{}})
1573 }
1574 }
1575 break
1576 }
1577 return p, evts
1578 }
1579
1580 func (q *pointerQueue) deliverDropEvent(handlers map[Tag]*routerHandler, p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
1581 if p.dataSource == nil {
1582 return p, evts
1583 }
1584 src := &handlers[p.dataSource].filter.pointer
1585 for _, k := range p.entered {
1586 h := handlers[k]
1587 if m, ok := firstMimeMatch(src, &h.filter.pointer); ok {
1588 p.dataTarget = k
1589 evts = append(evts, taggedEvent{tag: p.dataSource, event: TransferRequestEvent{Type: m}})
1590 return p, evts
1591 }
1592 }
1593 return q.deliverTransferCancelEvent(handlers, p, evts)
1594 }
1595
1596 func (q *pointerQueue) deliverTransferCancelEvent(handlers map[Tag]*routerHandler, p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
1597 evts = append(evts, taggedEvent{tag: p.dataSource, event: TransferCancelEvent{}})
1598 src := &handlers[p.dataSource].filter.pointer
1599 for k, h := range handlers {
1600 if _, ok := firstMimeMatch(src, &h.filter.pointer); ok {
1601 evts = append(evts, taggedEvent{tag: k, event: TransferCancelEvent{}})
1602 }
1603 }
1604 p.dataSource = nil
1605 p.dataTarget = nil
1606 return p, evts
1607 }
1608
1609 func (p *pointerFilter) Add(f Filter) {
1610 switch f := f.(type) {
1611 case PointerFilter:
1612 p.kinds = p.kinds | f.Kinds
1613 p.scrollX = p.scrollX.Union(f.ScrollX)
1614 p.scrollY = p.scrollY.Union(f.ScrollY)
1615 }
1616 }
1617
1618 func (p *pointerFilter) AddTransfer(f Filter) {
1619 switch f := f.(type) {
1620 case TransferSourceFilter:
1621 if !containsStr(p.sourceMimes, f.Type) {
1622 p.sourceMimes = append(p.sourceMimes, f.Type)
1623 }
1624 case TransferTargetFilter:
1625 if !containsStr(p.targetMimes, f.Type) {
1626 p.targetMimes = append(p.targetMimes, f.Type)
1627 }
1628 }
1629 }
1630
1631 func (p *pointerFilter) Matches(e Event) bool {
1632 switch e := e.(type) {
1633 case PointerEvent:
1634 return e.Kind&p.kinds == e.Kind
1635 case TransferCancelEvent, TransferInitiateEvent:
1636 return len(p.sourceMimes) > 0 || len(p.targetMimes) > 0
1637 case TransferRequestEvent:
1638 return containsStr(p.sourceMimes, e.Type)
1639 case TransferDataEvent:
1640 return containsStr(p.targetMimes, e.Type)
1641 }
1642 return false
1643 }
1644
1645 func (p *pointerFilter) Merge(p2 pointerFilter) {
1646 p.kinds = p.kinds | p2.kinds
1647 p.scrollX = p.scrollX.Union(p2.scrollX)
1648 p.scrollY = p.scrollY.Union(p2.scrollY)
1649 p.sourceMimes = append(p.sourceMimes, p2.sourceMimes...)
1650 p.targetMimes = append(p.targetMimes, p2.targetMimes...)
1651 }
1652
1653 func (p *pointerFilter) clampScroll(scroll Point) (left, scrolled Point) {
1654 left.X, scrolled.X = clampSplit(scroll.X, p.scrollX.Min, p.scrollX.Max)
1655 left.Y, scrolled.Y = clampSplit(scroll.Y, p.scrollY.Min, p.scrollY.Max)
1656 return
1657 }
1658
1659 func clampSplit(v float32, min, max int) (float32, float32) {
1660 if m := float32(max); v > m {
1661 return v - m, m
1662 }
1663 if m := float32(min); v < m {
1664 return v - m, m
1665 }
1666 return 0, v
1667 }
1668
1669 func (op *areaOp) Hit(pos Point) bool {
1670 pos = pos.Sub(FPt(op.rect.Min))
1671 size := FPt(op.rect.Size())
1672 switch op.kind {
1673 case areaRect:
1674 return 0 <= pos.X && pos.X < size.X &&
1675 0 <= pos.Y && pos.Y < size.Y
1676 case areaEllipse:
1677 rx := size.X / 2
1678 ry := size.Y / 2
1679 xh := pos.X - rx
1680 yk := pos.Y - ry
1681 return (xh*xh)/(rx*rx)+(yk*yk)/(ry*ry) <= 1
1682 default:
1683 panic("invalid area kind")
1684 }
1685 }
1686
1687 func (a *areaNode) bounds() image.Rectangle {
1688 return Rectangle{
1689 Min: a.trans.Transform(FPt(a.area.rect.Min)),
1690 Max: a.trans.Transform(FPt(a.area.rect.Max)),
1691 }.Round()
1692 }
1693
1694 // ---------------------------------------------------------------------------
1695 // Key queue (ported from key.go)
1696 // ---------------------------------------------------------------------------
1697
1698 type keyQueue struct {
1699 order []Tag
1700 dirOrder []dirFocusEntry
1701 hint InputHint
1702 }
1703
1704 type keyState struct {
1705 focus Tag
1706 state TextInputState
1707 content EditorState
1708 }
1709
1710 type keyHandler struct {
1711 visible bool
1712 reset bool
1713 hint InputHint
1714 orderPlusOne int
1715 dirOrder int
1716 trans Affine2D
1717 }
1718
1719 type keyFilterList []KeyFilter
1720
1721 type dirFocusEntry struct {
1722 tag Tag
1723 row int
1724 area int
1725 bounds image.Rectangle
1726 }
1727
1728 func (k *keyHandler) inputHint(hint InputHint) { k.hint = hint }
1729
1730 func (s keyState) InputState() (keyState, TextInputState) {
1731 state := s.state
1732 s.state = TextInputKeep
1733 return s, state
1734 }
1735
1736 func (q *keyQueue) InputHint(handlers map[Tag]*routerHandler, state keyState) (InputHint, bool) {
1737 focused, ok := handlers[state.focus]
1738 if !ok {
1739 return q.hint, false
1740 }
1741 old := q.hint
1742 q.hint = focused.key.hint
1743 return q.hint, old != q.hint
1744 }
1745
1746 func (k *keyHandler) Reset() {
1747 k.visible = false
1748 k.orderPlusOne = 0
1749 k.hint = HintAny
1750 }
1751
1752 func (q *keyQueue) Reset() {
1753 q.order = q.order[:0]
1754 q.dirOrder = q.dirOrder[:0]
1755 }
1756
1757 func (k *keyHandler) ResetEvent() (Event, bool) {
1758 if k.reset {
1759 return nil, false
1760 }
1761 k.reset = true
1762 return KeyFocusEvent{Focus: false}, true
1763 }
1764
1765 func (q *keyQueue) Frame(handlers map[Tag]*routerHandler, state keyState) keyState {
1766 if state.focus != nil {
1767 if h, ok := handlers[state.focus]; !ok || !h.filter.focusable || !h.key.visible {
1768 state.focus = nil
1769 state.state = TextInputClose
1770 }
1771 }
1772 q.updateFocusLayout(handlers)
1773 return state
1774 }
1775
1776 func (q *keyQueue) updateFocusLayout(handlers map[Tag]*routerHandler) {
1777 order := q.dirOrder
1778 // Sort by ascending Y - insertion sort (stable).
1779 for i := 1; i < len(order); i++ {
1780 for j := i; j > 0 && order[j].bounds.Min.Y < order[j-1].bounds.Min.Y; j-- {
1781 order[j], order[j-1] = order[j-1], order[j]
1782 }
1783 }
1784 row := 0
1785 i := 0
1786 for i < len(order) {
1787 h := &order[i]
1788 h.row = row
1789 bottom := h.bounds.Max.Y
1790 end := i + 1
1791 for end < len(order) {
1792 h2 := &order[end]
1793 center := (h2.bounds.Min.Y + h2.bounds.Max.Y) / 2
1794 if center > bottom {
1795 break
1796 }
1797 h2.row = row
1798 end++
1799 }
1800 // Sort row by ascending X.
1801 for a := i + 1; a < end; a++ {
1802 for b := a; b > i && order[b].bounds.Min.X < order[b-1].bounds.Min.X; b-- {
1803 order[b], order[b-1] = order[b-1], order[b]
1804 }
1805 }
1806 i = end
1807 row++
1808 }
1809 for i, o := range q.dirOrder {
1810 handlers[o.tag].key.dirOrder = i
1811 }
1812 }
1813
1814 func (q *keyQueue) MoveFocus(handlers map[Tag]*routerHandler, state keyState, dir FocusDirection) (keyState, []taggedEvent) {
1815 if len(q.dirOrder) == 0 {
1816 return state, nil
1817 }
1818 order := 0
1819 if state.focus != nil {
1820 order = handlers[state.focus].key.dirOrder
1821 }
1822 focus := q.dirOrder[order]
1823 switch dir {
1824 case FocusForward, FocusBackward:
1825 if len(q.order) == 0 {
1826 break
1827 }
1828 ord := 0
1829 if dir == FocusBackward {
1830 ord = -1
1831 }
1832 if state.focus != nil {
1833 ord = handlers[state.focus].key.orderPlusOne - 1
1834 if dir == FocusForward {
1835 ord++
1836 } else {
1837 ord--
1838 }
1839 }
1840 ord = (ord + len(q.order)) % len(q.order)
1841 return q.Focus(handlers, state, q.order[ord])
1842 case FocusRight, FocusLeft:
1843 next := order
1844 if state.focus != nil {
1845 next = order + 1
1846 if dir == FocusLeft {
1847 next = order - 1
1848 }
1849 }
1850 if 0 <= next && next < len(q.dirOrder) {
1851 newFocus := q.dirOrder[next]
1852 if newFocus.row == focus.row {
1853 return q.Focus(handlers, state, newFocus.tag)
1854 }
1855 }
1856 case FocusUp, FocusDown:
1857 delta := +1
1858 if dir == FocusUp {
1859 delta = -1
1860 }
1861 nextRow := 0
1862 if state.focus != nil {
1863 nextRow = focus.row + delta
1864 }
1865 var closest Tag
1866 dist := 1 << 30
1867 center := (focus.bounds.Min.X + focus.bounds.Max.X) / 2
1868 ord := order
1869 focusLoop:
1870 for 0 <= ord && ord < len(q.dirOrder) {
1871 next := q.dirOrder[ord]
1872 switch next.row {
1873 case nextRow:
1874 nextCenter := (next.bounds.Min.X + next.bounds.Max.X) / 2
1875 d := center - nextCenter
1876 if d < 0 {
1877 d = -d
1878 }
1879 if d > dist {
1880 break focusLoop
1881 }
1882 dist = d
1883 closest = next.tag
1884 case nextRow + delta:
1885 break focusLoop
1886 }
1887 ord += delta
1888 }
1889 if closest != nil {
1890 return q.Focus(handlers, state, closest)
1891 }
1892 }
1893 return state, nil
1894 }
1895
1896 func (q *keyQueue) BoundsFor(k *keyHandler) image.Rectangle {
1897 return q.dirOrder[k.dirOrder].bounds
1898 }
1899
1900 func (q *keyQueue) AreaFor(k *keyHandler) int {
1901 return q.dirOrder[k.dirOrder].area
1902 }
1903
1904 func (k *keyFilterList) Matches(focus Tag, e KeyEvent, system bool) bool {
1905 for _, f := range *k {
1906 if keyFilterMatch(focus, f, e, system) {
1907 return true
1908 }
1909 }
1910 return false
1911 }
1912
1913 func keyFilterMatch(focus Tag, f KeyFilter, e KeyEvent, system bool) bool {
1914 if f.Focus != nil && f.Focus != focus {
1915 return false
1916 }
1917 if (f.Name != "" || system) && f.Name != e.Name {
1918 return false
1919 }
1920 if e.Modifiers&f.Required != f.Required {
1921 return false
1922 }
1923 if e.Modifiers&^(f.Required|f.Optional) != 0 {
1924 return false
1925 }
1926 return true
1927 }
1928
1929 func (q *keyQueue) Focus(handlers map[Tag]*routerHandler, state keyState, focus Tag) (keyState, []taggedEvent) {
1930 if focus == state.focus {
1931 return state, nil
1932 }
1933 state.content = EditorState{}
1934 state.content.Selection.Transform = AffineId()
1935 var evts []taggedEvent
1936 if state.focus != nil {
1937 evts = append(evts, taggedEvent{tag: state.focus, event: KeyFocusEvent{Focus: false}})
1938 }
1939 state.focus = focus
1940 if state.focus != nil {
1941 evts = append(evts, taggedEvent{tag: state.focus, event: KeyFocusEvent{Focus: true}})
1942 }
1943 if state.focus == nil || state.state == TextInputKeep {
1944 state.state = TextInputClose
1945 }
1946 return state, evts
1947 }
1948
1949 func (s keyState) softKeyboard(show bool) keyState {
1950 if show {
1951 s.state = TextInputOpen
1952 } else {
1953 s.state = TextInputClose
1954 }
1955 return s
1956 }
1957
1958 func (k *keyFilterList) Add(f KeyFilter) {
1959 for _, existing := range *k {
1960 if existing == f {
1961 return
1962 }
1963 }
1964 *k = append(*k, f)
1965 }
1966
1967 func (k *keyFilterList) Merge(k2 keyFilterList) {
1968 *k = append(*k, k2...)
1969 }
1970
1971 func (q *keyQueue) inputOp(tag Tag, state *keyHandler, t Affine2D, area int, bounds image.Rectangle) {
1972 state.visible = true
1973 if state.orderPlusOne == 0 {
1974 state.orderPlusOne = len(q.order) + 1
1975 q.order = append(q.order, tag)
1976 q.dirOrder = append(q.dirOrder, dirFocusEntry{tag: tag, area: area, bounds: bounds})
1977 }
1978 state.trans = t
1979 }
1980
1981 func (q *keyQueue) setSelection(state keyState, req SelectionCmd) keyState {
1982 if req.Tag != state.focus {
1983 return state
1984 }
1985 state.content.Selection.KeyRange = req.KeyRange
1986 state.content.Selection.Caret = req.Caret
1987 return state
1988 }
1989
1990 func (q *keyQueue) editorState(handlers map[Tag]*routerHandler, state keyState) EditorState {
1991 s := state.content
1992 if f := state.focus; f != nil {
1993 s.Selection.Transform = handlers[f].key.trans
1994 }
1995 return s
1996 }
1997
1998 func (q *keyQueue) setSnippet(state keyState, req SnippetCmd) keyState {
1999 if req.Tag == state.focus {
2000 state.content.Snippet = req.Snippet
2001 }
2002 return state
2003 }
2004
2005 // ---------------------------------------------------------------------------
2006 // Clipboard queue (ported from clipboard.go)
2007 // ---------------------------------------------------------------------------
2008
2009 type clipboardState struct {
2010 receivers []Tag
2011 }
2012
2013 type clipboardQueue struct {
2014 requested bool
2015 mime string
2016 text []byte
2017 }
2018
2019 func (q *clipboardQueue) WriteClipboard() (mime string, content []byte, ok bool) {
2020 if q.text == nil {
2021 return "", nil, false
2022 }
2023 content = q.text
2024 q.text = nil
2025 return q.mime, content, true
2026 }
2027
2028 func (q *clipboardQueue) ClipboardRequested(state clipboardState) bool {
2029 req := len(state.receivers) > 0 && q.requested
2030 q.requested = false
2031 return req
2032 }
2033
2034 func (q *clipboardQueue) Push(state clipboardState, e Event) (clipboardState, []taggedEvent) {
2035 var evts []taggedEvent
2036 for _, r := range state.receivers {
2037 evts = append(evts, taggedEvent{tag: r, event: e})
2038 }
2039 state.receivers = nil
2040 return state, evts
2041 }
2042
2043 func (q *clipboardQueue) ProcessWriteClipboard(req ClipboardWriteCmd) {
2044 if req.Data != nil {
2045 content, err := io.ReadAll(req.Data)
2046 req.Data.Close()
2047 if err != nil {
2048 return
2049 }
2050 q.mime = req.Type
2051 q.text = content
2052 }
2053 }
2054
2055 func (q *clipboardQueue) ProcessReadClipboard(state clipboardState, tag Tag) clipboardState {
2056 if containsTag(state.receivers, tag) {
2057 return state
2058 }
2059 n := len(state.receivers)
2060 state.receivers = append(state.receivers[:n:n], tag)
2061 q.requested = true
2062 return state
2063 }
2064
2065 // ---------------------------------------------------------------------------
2066 // Translation: AppPointerEvent → PointerEvent, AppKeyEvent → KeyEvent
2067 // ---------------------------------------------------------------------------
2068
2069 func appPointerToPointerEvent(e AppPointerEvent) PointerEvent {
2070 var kind PointerKind
2071 switch e.Kind {
2072 case WinPointerMove:
2073 kind = PointerMove
2074 case WinPointerPress:
2075 kind = PointerPress
2076 case WinPointerRelease:
2077 kind = PointerRelease
2078 case WinPointerScroll:
2079 kind = PointerScroll
2080 case WinPointerCancel:
2081 kind = PointerCancel
2082 }
2083 src := Mouse
2084 touchID := PointerID(e.Buttons >> 8 & 0xff)
2085 if touchID != 0 {
2086 src = Touch
2087 }
2088 btns := PointerButtons(e.Buttons & 0xff)
2089 var mods Modifiers
2090 if e.Mods&ModAltBit != 0 {
2091 mods |= ModAlt
2092 }
2093 if e.Mods&ModCtrlBit != 0 {
2094 mods |= ModCtrl
2095 }
2096 if e.Mods&ModShiftBit != 0 {
2097 mods |= ModShift
2098 }
2099 if e.Mods&ModMetaBit != 0 {
2100 mods |= ModCommand
2101 }
2102 return PointerEvent{
2103 Kind: kind,
2104 Source: src,
2105 PointerID: touchID,
2106 Position: Pt(e.X, e.Y),
2107 Scroll: Pt(e.Dx, e.Dy),
2108 Buttons: btns,
2109 Modifiers: mods,
2110 }
2111 }
2112
2113 func appKeyToKeyEvent(e AppKeyEvent) (KeyEvent, bool) {
2114 name := keyCodeToKeyName(e.Code, e.Mods)
2115 if name == "" {
2116 return KeyEvent{}, false
2117 }
2118 var mods Modifiers
2119 if e.Mods&ModAltBit != 0 {
2120 mods |= ModAlt
2121 }
2122 if e.Mods&ModCtrlBit != 0 {
2123 mods |= ModCtrl
2124 }
2125 if e.Mods&ModShiftBit != 0 {
2126 mods |= ModShift
2127 }
2128 if e.Mods&ModMetaBit != 0 {
2129 mods |= ModCommand
2130 }
2131 state := KeyRelease
2132 if e.Press {
2133 state = KeyPress
2134 }
2135 return KeyEvent{Name: name, Modifiers: mods, State: state}, true
2136 }
2137
2138 // keyCodeToKeyName maps AppKeyEvent.Code to a KeyName.
2139 func keyCodeToKeyName(code, mods int32) KeyName {
2140 switch code {
2141 case KeyArrowUp:
2142 return KeyNameUpArrow
2143 case KeyArrowDown:
2144 return KeyNameDownArrow
2145 case KeyArrowLeft:
2146 return KeyNameLeftArrow
2147 case KeyArrowRight:
2148 return KeyNameRightArrow
2149 case KeyEscape:
2150 return KeyNameEscape
2151 case KeyEnter:
2152 return KeyNameReturn
2153 case KeyBackspace:
2154 return KeyNameDeleteBackward
2155 case KeyDelete:
2156 return KeyNameDeleteForward
2157 case KeyHome:
2158 return KeyNameHome
2159 case KeyEnd:
2160 return KeyNameEnd
2161 case KeyPageUp:
2162 return KeyNamePageUp
2163 case KeyPageDown:
2164 return KeyNamePageDown
2165 case KeyTab:
2166 return KeyNameTab
2167 case KeySpace:
2168 return KeyNameSpace
2169 case KeyF1:
2170 return KeyNameF1
2171 case KeyF1 + 1:
2172 return KeyNameF2
2173 case KeyF1 + 2:
2174 return KeyNameF3
2175 case KeyF1 + 3:
2176 return KeyNameF4
2177 case KeyF1 + 4:
2178 return KeyNameF5
2179 case KeyF1 + 5:
2180 return KeyNameF6
2181 case KeyF1 + 6:
2182 return KeyNameF7
2183 case KeyF1 + 7:
2184 return KeyNameF8
2185 case KeyF1 + 8:
2186 return KeyNameF9
2187 case KeyF1 + 9:
2188 return KeyNameF10
2189 case KeyF1 + 10:
2190 return KeyNameF11
2191 case KeyF12:
2192 return KeyNameF12
2193 case KeyCtrl:
2194 return KeyNameCtrl
2195 case KeyShift:
2196 return KeyNameShift
2197 case KeyAlt:
2198 return KeyNameAlt
2199 case KeySuper:
2200 return KeyNameSuper
2201 case KeyCommand:
2202 return KeyNameCommand
2203 }
2204 // Printable ASCII: code is uppercase codepoint.
2205 if code >= 0x20 && code <= 0x7e {
2206 // Apply shift for lowercase vs uppercase.
2207 if mods&ModShiftBit == 0 && code >= 'A' && code <= 'Z' {
2208 // return lowercase
2209 return KeyName(string(rune(code + 32)))
2210 }
2211 return KeyName(string(rune(code)))
2212 }
2213 return ""
2214 }
2215
2216 // ---------------------------------------------------------------------------
2217 // Slice helpers (replaces slices package)
2218 // ---------------------------------------------------------------------------
2219
2220 func containsTag(tags []Tag, tag Tag) bool {
2221 for _, t := range tags {
2222 if t == tag {
2223 return true
2224 }
2225 }
2226 return false
2227 }
2228
2229 func containsStr(ss []string, s string) bool {
2230 for _, v := range ss {
2231 if v == s {
2232 return true
2233 }
2234 }
2235 return false
2236 }
2237
2238 func searchTag(tags []Tag, tag Tag) (int, bool) {
2239 for i, t := range tags {
2240 if t == tag {
2241 return i, true
2242 }
2243 }
2244 return 0, false
2245 }
2246
2247 func addTagToList(tags []Tag, tag Tag) []Tag {
2248 if containsTag(tags, tag) {
2249 return tags
2250 }
2251 return append(tags, tag)
2252 }
2253
2254 func firstMimeMatch(src, tgt *pointerFilter) (first string, matched bool) {
2255 for _, m1 := range tgt.targetMimes {
2256 if containsStr(src.sourceMimes, m1) {
2257 return m1, true
2258 }
2259 }
2260 return "", false
2261 }
2262
2263 func clonePointers(s []pointerInfo) []pointerInfo {
2264 c := []pointerInfo{:len(s):len(s)}
2265 copy(c, s)
2266 return c
2267 }
2268
2269 func concatPointers(a, b []pointerInfo) []pointerInfo {
2270 r := []pointerInfo{:len(a)+len(b):len(a)+len(b)}
2271 copy(r, a)
2272 copy(r[len(a):], b)
2273 return r
2274 }
2275
2276 func deleteTaggedEvent(s []taggedEvent, i, j int) []taggedEvent {
2277 copy(s[i:], s[j:])
2278 return s[:len(s)-(j-i)]
2279 }
2280
2281 func deleteSemanticID(s []semanticID, i, j int) []semanticID {
2282 copy(s[i:], s[j:])
2283 return s[:len(s)-(j-i)]
2284 }
2285