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