router.go raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 /*
4 Package router implements Router, a event.Queue implementation
5 that that disambiguates and routes events to handlers declared
6 in operation lists.
7
8 Router is used by app.Window and is otherwise only useful for
9 using Gio with external window implementations.
10 */
11 package router
12
13 import (
14 "encoding/binary"
15 "time"
16
17 "github.com/p9c/p9/pkg/gel/gio/internal/opconst"
18 "github.com/p9c/p9/pkg/gel/gio/internal/ops"
19 "github.com/p9c/p9/pkg/gel/gio/io/clipboard"
20 "github.com/p9c/p9/pkg/gel/gio/io/event"
21 "github.com/p9c/p9/pkg/gel/gio/io/key"
22 "github.com/p9c/p9/pkg/gel/gio/io/pointer"
23 "github.com/p9c/p9/pkg/gel/gio/io/profile"
24 "github.com/p9c/p9/pkg/gel/gio/op"
25 )
26
27 // Router is a Queue implementation that routes events
28 // to handlers declared in operation lists.
29 type Router struct {
30 pqueue pointerQueue
31 kqueue keyQueue
32 cqueue clipboardQueue
33
34 handlers handlerEvents
35
36 reader ops.Reader
37
38 // InvalidateOp summary.
39 wakeup bool
40 wakeupTime time.Time
41
42 // ProfileOp summary.
43 profHandlers map[event.Tag]struct{}
44 profile profile.Event
45 }
46
47 type handlerEvents struct {
48 handlers map[event.Tag][]event.Event
49 hadEvents bool
50 }
51
52 // Events returns the available events for the handler key.
53 func (q *Router) Events(k event.Tag) []event.Event {
54 events := q.handlers.Events(k)
55 if _, isprof := q.profHandlers[k]; isprof {
56 delete(q.profHandlers, k)
57 events = append(events, q.profile)
58 }
59 return events
60 }
61
62 // Frame replaces the declared handlers from the supplied
63 // operation list. The text input state, wakeup time and whether
64 // there are active profile handlers is also saved.
65 func (q *Router) Frame(ops *op.Ops) {
66 q.handlers.Clear()
67 q.wakeup = false
68 for k := range q.profHandlers {
69 delete(q.profHandlers, k)
70 }
71 q.reader.Reset(ops)
72 q.collect()
73
74 q.pqueue.Frame(ops, &q.handlers)
75 q.kqueue.Frame(ops, &q.handlers)
76 if q.handlers.HadEvents() {
77 q.wakeup = true
78 q.wakeupTime = time.Time{}
79 }
80 }
81
82 // Queue an event and report whether at least one handler had an event queued.
83 func (q *Router) Queue(events ...event.Event) bool {
84 for _, e := range events {
85 switch e := e.(type) {
86 case profile.Event:
87 q.profile = e
88 case pointer.Event:
89 q.pqueue.Push(e, &q.handlers)
90 case key.EditEvent, key.Event, key.FocusEvent:
91 q.kqueue.Push(e, &q.handlers)
92 case clipboard.Event:
93 q.cqueue.Push(e, &q.handlers)
94 }
95 }
96 return q.handlers.HadEvents()
97 }
98
99 // TextInputState returns the input state from the most recent
100 // call to Frame.
101 func (q *Router) TextInputState() TextInputState {
102 return q.kqueue.InputState()
103 }
104
105 // WriteClipboard returns the most recent text to be copied
106 // to the clipboard, if any.
107 func (q *Router) WriteClipboard() (string, bool) {
108 return q.cqueue.WriteClipboard()
109 }
110
111 // ReadClipboard reports if any new handler is waiting
112 // to read the clipboard.
113 func (q *Router) ReadClipboard() bool {
114 return q.cqueue.ReadClipboard()
115 }
116
117 // Cursor returns the last cursor set.
118 func (q *Router) Cursor() pointer.CursorName {
119 return q.pqueue.cursor
120 }
121
122 func (q *Router) collect() {
123 for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
124 switch opconst.OpType(encOp.Data[0]) {
125 case opconst.TypeInvalidate:
126 op := decodeInvalidateOp(encOp.Data)
127 if !q.wakeup || op.At.Before(q.wakeupTime) {
128 q.wakeup = true
129 q.wakeupTime = op.At
130 }
131 case opconst.TypeProfile:
132 op := decodeProfileOp(encOp.Data, encOp.Refs)
133 if q.profHandlers == nil {
134 q.profHandlers = make(map[event.Tag]struct{})
135 }
136 q.profHandlers[op.Tag] = struct{}{}
137 case opconst.TypeClipboardRead:
138 q.cqueue.ProcessReadClipboard(encOp.Data, encOp.Refs)
139 case opconst.TypeClipboardWrite:
140 q.cqueue.ProcessWriteClipboard(encOp.Data, encOp.Refs)
141 }
142 }
143 }
144
145 // Profiling reports whether there was profile handlers in the
146 // most recent Frame call.
147 func (q *Router) Profiling() bool {
148 return len(q.profHandlers) > 0
149 }
150
151 // WakeupTime returns the most recent time for doing another frame,
152 // as determined from the last call to Frame.
153 func (q *Router) WakeupTime() (time.Time, bool) {
154 return q.wakeupTime, q.wakeup
155 }
156
157 func (h *handlerEvents) init() {
158 if h.handlers == nil {
159 h.handlers = make(map[event.Tag][]event.Event)
160 }
161 }
162
163 func (h *handlerEvents) AddNoRedraw(k event.Tag, e event.Event) {
164 h.init()
165 h.handlers[k] = append(h.handlers[k], e)
166 }
167
168 func (h *handlerEvents) Add(k event.Tag, e event.Event) {
169 h.AddNoRedraw(k, e)
170 h.hadEvents = true
171 }
172
173 func (h *handlerEvents) HadEvents() bool {
174 u := h.hadEvents
175 h.hadEvents = false
176 return u
177 }
178
179 func (h *handlerEvents) Events(k event.Tag) []event.Event {
180 if events, ok := h.handlers[k]; ok {
181 h.handlers[k] = h.handlers[k][:0]
182 // Schedule another frame if we delivered events to the user
183 // to flush half-updated state. This is important when an
184 // event changes UI state that has already been laid out. In
185 // the worst case, we waste a frame, increasing power usage.
186 //
187 // Gio is expected to grow the ability to construct
188 // frame-to-frame differences and only render to changed
189 // areas. In that case, the waste of a spurious frame should
190 // be minimal.
191 h.hadEvents = h.hadEvents || len(events) > 0
192 return events
193 }
194 return nil
195 }
196
197 func (h *handlerEvents) Clear() {
198 for k := range h.handlers {
199 delete(h.handlers, k)
200 }
201 }
202
203 func decodeProfileOp(d []byte, refs []interface{}) profile.Op {
204 if opconst.OpType(d[0]) != opconst.TypeProfile {
205 panic("invalid op")
206 }
207 return profile.Op{
208 Tag: refs[0].(event.Tag),
209 }
210 }
211
212 func decodeInvalidateOp(d []byte) op.InvalidateOp {
213 bo := binary.LittleEndian
214 if opconst.OpType(d[0]) != opconst.TypeInvalidate {
215 panic("invalid op")
216 }
217 var o op.InvalidateOp
218 if nanos := bo.Uint64(d[1:]); nanos > 0 {
219 o.At = time.Unix(0, int64(nanos))
220 }
221 return o
222 }
223