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  }