github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/io/router/router.go (about) 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/cybriq/giocore/internal/opconst" 18 "github.com/cybriq/giocore/internal/ops" 19 "github.com/cybriq/giocore/io/clipboard" 20 "github.com/cybriq/giocore/io/event" 21 "github.com/cybriq/giocore/io/key" 22 "github.com/cybriq/giocore/io/pointer" 23 "github.com/cybriq/giocore/io/profile" 24 "github.com/cybriq/giocore/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 // TextInputHint returns the input mode from the most recent key.InputOp. 106 func (q *Router) TextInputHint() (key.InputHint, bool) { 107 return q.kqueue.InputHint() 108 } 109 110 // WriteClipboard returns the most recent text to be copied 111 // to the clipboard, if any. 112 func (q *Router) WriteClipboard() (string, bool) { 113 return q.cqueue.WriteClipboard() 114 } 115 116 // ReadClipboard reports if any new handler is waiting 117 // to read the clipboard. 118 func (q *Router) ReadClipboard() bool { 119 return q.cqueue.ReadClipboard() 120 } 121 122 // Cursor returns the last cursor set. 123 func (q *Router) Cursor() pointer.CursorName { 124 return q.pqueue.cursor 125 } 126 127 func (q *Router) collect() { 128 for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() { 129 switch opconst.OpType(encOp.Data[0]) { 130 case opconst.TypeInvalidate: 131 op := decodeInvalidateOp(encOp.Data) 132 if !q.wakeup || op.At.Before(q.wakeupTime) { 133 q.wakeup = true 134 q.wakeupTime = op.At 135 } 136 case opconst.TypeProfile: 137 op := decodeProfileOp(encOp.Data, encOp.Refs) 138 if q.profHandlers == nil { 139 q.profHandlers = make(map[event.Tag]struct{}) 140 } 141 q.profHandlers[op.Tag] = struct{}{} 142 case opconst.TypeClipboardRead: 143 q.cqueue.ProcessReadClipboard(encOp.Data, encOp.Refs) 144 case opconst.TypeClipboardWrite: 145 q.cqueue.ProcessWriteClipboard(encOp.Data, encOp.Refs) 146 } 147 } 148 } 149 150 // Profiling reports whether there was profile handlers in the 151 // most recent Frame call. 152 func (q *Router) Profiling() bool { 153 return len(q.profHandlers) > 0 154 } 155 156 // WakeupTime returns the most recent time for doing another frame, 157 // as determined from the last call to Frame. 158 func (q *Router) WakeupTime() (time.Time, bool) { 159 return q.wakeupTime, q.wakeup 160 } 161 162 func (h *handlerEvents) init() { 163 if h.handlers == nil { 164 h.handlers = make(map[event.Tag][]event.Event) 165 } 166 } 167 168 func (h *handlerEvents) AddNoRedraw(k event.Tag, e event.Event) { 169 h.init() 170 h.handlers[k] = append(h.handlers[k], e) 171 } 172 173 func (h *handlerEvents) Add(k event.Tag, e event.Event) { 174 h.AddNoRedraw(k, e) 175 h.hadEvents = true 176 } 177 178 func (h *handlerEvents) HadEvents() bool { 179 u := h.hadEvents 180 h.hadEvents = false 181 return u 182 } 183 184 func (h *handlerEvents) Events(k event.Tag) []event.Event { 185 if events, ok := h.handlers[k]; ok { 186 h.handlers[k] = h.handlers[k][:0] 187 // Schedule another frame if we delivered events to the user 188 // to flush half-updated state. This is important when an 189 // event changes UI state that has already been laid out. In 190 // the worst case, we waste a frame, increasing power usage. 191 // 192 // Gio is expected to grow the ability to construct 193 // frame-to-frame differences and only render to changed 194 // areas. In that case, the waste of a spurious frame should 195 // be minimal. 196 h.hadEvents = h.hadEvents || len(events) > 0 197 return events 198 } 199 return nil 200 } 201 202 func (h *handlerEvents) Clear() { 203 for k := range h.handlers { 204 delete(h.handlers, k) 205 } 206 } 207 208 func decodeProfileOp(d []byte, refs []interface{}) profile.Op { 209 if opconst.OpType(d[0]) != opconst.TypeProfile { 210 panic("invalid op") 211 } 212 return profile.Op{ 213 Tag: refs[0].(event.Tag), 214 } 215 } 216 217 func decodeInvalidateOp(d []byte) op.InvalidateOp { 218 bo := binary.LittleEndian 219 if opconst.OpType(d[0]) != opconst.TypeInvalidate { 220 panic("invalid op") 221 } 222 var o op.InvalidateOp 223 if nanos := bo.Uint64(d[1:]); nanos > 0 { 224 o.At = time.Unix(0, int64(nanos)) 225 } 226 return o 227 }