gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/app/internal/input/router.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package input 4 5 import ( 6 "encoding/binary" 7 "time" 8 9 "gioui.org/ui" 10 "gioui.org/ui/internal/opconst" 11 "gioui.org/ui/internal/ops" 12 "gioui.org/ui/key" 13 "gioui.org/ui/pointer" 14 "gioui.org/ui/system" 15 ) 16 17 // Router is a Queue implementation that routes events from 18 // all available input sources to registered handlers. 19 type Router struct { 20 pqueue pointerQueue 21 kqueue keyQueue 22 23 handlers handlerEvents 24 25 reader ops.Reader 26 27 // deliveredEvents tracks whether events has been returned to the 28 // user from Events. If so, another frame is scheduled to flush 29 // half-updated state. This is important when a an event changes 30 // UI state that has already been laid out. In the worst case, we 31 // waste a frame, increasing power usage. 32 // Gio is expected to grow the ability to construct frame-to-frame 33 // differences and only render to changed areas. In that case, the 34 // waste of a spurious frame should be minimal. 35 deliveredEvents bool 36 // InvalidateOp summary. 37 wakeup bool 38 wakeupTime time.Time 39 40 // ProfileOp summary. 41 profHandlers []ui.Key 42 } 43 44 type handlerEvents struct { 45 handlers map[ui.Key][]ui.Event 46 hadEvents bool 47 } 48 49 func (q *Router) Events(k ui.Key) []ui.Event { 50 events := q.handlers.Events(k) 51 q.deliveredEvents = q.deliveredEvents || len(events) > 0 52 return events 53 } 54 55 func (q *Router) Frame(ops *ui.Ops) { 56 q.handlers.Clear() 57 q.wakeup = false 58 q.profHandlers = q.profHandlers[:0] 59 q.reader.Reset(ops) 60 q.collect() 61 62 q.pqueue.Frame(ops, &q.handlers) 63 q.kqueue.Frame(ops, &q.handlers) 64 if q.deliveredEvents || q.handlers.HadEvents() { 65 q.deliveredEvents = false 66 q.wakeup = true 67 q.wakeupTime = time.Time{} 68 } 69 } 70 71 func (q *Router) Add(e ui.Event) bool { 72 switch e := e.(type) { 73 case pointer.Event: 74 q.pqueue.Push(e, &q.handlers) 75 case key.EditEvent, key.Event, key.FocusEvent: 76 q.kqueue.Push(e, &q.handlers) 77 } 78 return q.handlers.HadEvents() 79 } 80 81 func (q *Router) TextInputState() TextInputState { 82 return q.kqueue.InputState() 83 } 84 85 func (q *Router) collect() { 86 for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() { 87 switch opconst.OpType(encOp.Data[0]) { 88 case opconst.TypeInvalidate: 89 op := decodeInvalidateOp(encOp.Data) 90 if !q.wakeup || op.At.Before(q.wakeupTime) { 91 q.wakeup = true 92 q.wakeupTime = op.At 93 } 94 case opconst.TypeProfile: 95 op := decodeProfileOp(encOp.Data, encOp.Refs) 96 q.profHandlers = append(q.profHandlers, op.Key) 97 } 98 } 99 } 100 101 func (q *Router) AddProfile(e system.ProfileEvent) { 102 for _, h := range q.profHandlers { 103 q.handlers.Add(h, e) 104 } 105 } 106 107 func (q *Router) Profiling() bool { 108 return len(q.profHandlers) > 0 109 } 110 111 func (q *Router) WakeupTime() (time.Time, bool) { 112 return q.wakeupTime, q.wakeup 113 } 114 115 func (h *handlerEvents) init() { 116 if h.handlers == nil { 117 h.handlers = make(map[ui.Key][]ui.Event) 118 } 119 } 120 121 func (h *handlerEvents) Set(k ui.Key, evts []ui.Event) { 122 h.init() 123 h.handlers[k] = evts 124 h.hadEvents = true 125 } 126 127 func (h *handlerEvents) Add(k ui.Key, e ui.Event) { 128 h.init() 129 h.handlers[k] = append(h.handlers[k], e) 130 h.hadEvents = true 131 } 132 133 func (h *handlerEvents) HadEvents() bool { 134 u := h.hadEvents 135 h.hadEvents = false 136 return u 137 } 138 139 func (h *handlerEvents) Events(k ui.Key) []ui.Event { 140 if events, ok := h.handlers[k]; ok { 141 h.handlers[k] = h.handlers[k][:0] 142 return events 143 } 144 return nil 145 } 146 147 func (h *handlerEvents) Clear() { 148 for k := range h.handlers { 149 delete(h.handlers, k) 150 } 151 } 152 153 func decodeProfileOp(d []byte, refs []interface{}) system.ProfileOp { 154 if opconst.OpType(d[0]) != opconst.TypeProfile { 155 panic("invalid op") 156 } 157 return system.ProfileOp{ 158 Key: refs[0].(ui.Key), 159 } 160 } 161 162 func decodeInvalidateOp(d []byte) ui.InvalidateOp { 163 bo := binary.LittleEndian 164 if opconst.OpType(d[0]) != opconst.TypeInvalidate { 165 panic("invalid op") 166 } 167 var o ui.InvalidateOp 168 if nanos := bo.Uint64(d[1:]); nanos > 0 { 169 o.At = time.Unix(0, int64(nanos)) 170 } 171 return o 172 }