github.com/Seikaijyu/gio@v0.0.1/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  	"image"
    16  	"io"
    17  	"math"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/Seikaijyu/gio/f32"
    22  	f32internal "github.com/Seikaijyu/gio/internal/f32"
    23  	"github.com/Seikaijyu/gio/internal/ops"
    24  	"github.com/Seikaijyu/gio/io/clipboard"
    25  	"github.com/Seikaijyu/gio/io/event"
    26  	"github.com/Seikaijyu/gio/io/key"
    27  	"github.com/Seikaijyu/gio/io/pointer"
    28  	"github.com/Seikaijyu/gio/io/profile"
    29  	"github.com/Seikaijyu/gio/io/semantic"
    30  	"github.com/Seikaijyu/gio/io/system"
    31  	"github.com/Seikaijyu/gio/io/transfer"
    32  	"github.com/Seikaijyu/gio/op"
    33  )
    34  
    35  // Router is a Queue implementation that routes events
    36  // to handlers declared in operation lists.
    37  type Router struct {
    38  	savedTrans []f32.Affine2D
    39  	transStack []f32.Affine2D
    40  	pointer    struct {
    41  		queue     pointerQueue
    42  		collector pointerCollector
    43  	}
    44  	key struct {
    45  		queue     keyQueue
    46  		collector keyCollector
    47  	}
    48  	cqueue clipboardQueue
    49  
    50  	handlers handlerEvents
    51  
    52  	reader ops.Reader
    53  
    54  	// InvalidateOp summary.
    55  	wakeup     bool
    56  	wakeupTime time.Time
    57  
    58  	// ProfileOp summary.
    59  	profHandlers map[event.Tag]struct{}
    60  	profile      profile.Event
    61  }
    62  
    63  // SemanticNode represents a node in the tree describing the components
    64  // contained in a frame.
    65  type SemanticNode struct {
    66  	ID       SemanticID
    67  	ParentID SemanticID
    68  	Children []SemanticNode
    69  	Desc     SemanticDesc
    70  
    71  	areaIdx int
    72  }
    73  
    74  // SemanticDesc provides a semantic description of a UI component.
    75  type SemanticDesc struct {
    76  	Class       semantic.ClassOp
    77  	Description string
    78  	Label       string
    79  	Selected    bool
    80  	Disabled    bool
    81  	Gestures    SemanticGestures
    82  	Bounds      image.Rectangle
    83  }
    84  
    85  // SemanticGestures is a bit-set of supported gestures.
    86  type SemanticGestures int
    87  
    88  const (
    89  	ClickGesture SemanticGestures = 1 << iota
    90  	ScrollGesture
    91  )
    92  
    93  // SemanticID uniquely identifies a SemanticDescription.
    94  //
    95  // By convention, the zero value denotes the non-existent ID.
    96  type SemanticID uint64
    97  
    98  type handlerEvents struct {
    99  	handlers  map[event.Tag][]event.Event
   100  	hadEvents bool
   101  }
   102  
   103  // Events returns the available events for the handler key.
   104  func (q *Router) Events(k event.Tag) []event.Event {
   105  	events := q.handlers.Events(k)
   106  	if _, isprof := q.profHandlers[k]; isprof {
   107  		delete(q.profHandlers, k)
   108  		events = append(events, q.profile)
   109  	}
   110  	return events
   111  }
   112  
   113  // Frame replaces the declared handlers from the supplied
   114  // operation list. The text input state, wakeup time and whether
   115  // there are active profile handlers is also saved.
   116  func (q *Router) Frame(frame *op.Ops) {
   117  	q.handlers.Clear()
   118  	q.wakeup = false
   119  	for k := range q.profHandlers {
   120  		delete(q.profHandlers, k)
   121  	}
   122  	var ops *ops.Ops
   123  	if frame != nil {
   124  		ops = &frame.Internal
   125  	}
   126  	q.reader.Reset(ops)
   127  	q.collect()
   128  
   129  	q.pointer.queue.Frame(&q.handlers)
   130  	q.key.queue.Frame(&q.handlers, q.key.collector)
   131  	if q.handlers.HadEvents() {
   132  		q.wakeup = true
   133  		q.wakeupTime = time.Time{}
   134  	}
   135  }
   136  
   137  // Queue key events to the topmost handler.
   138  func (q *Router) QueueTopmost(events ...key.Event) bool {
   139  	var topmost event.Tag
   140  	pq := &q.pointer.queue
   141  	for _, h := range pq.hitTree {
   142  		if h.ktag != nil {
   143  			topmost = h.ktag
   144  			break
   145  		}
   146  	}
   147  	if topmost == nil {
   148  		return false
   149  	}
   150  	for _, e := range events {
   151  		q.handlers.Add(topmost, e)
   152  	}
   153  	return q.handlers.HadEvents()
   154  }
   155  
   156  // Queue events and report whether at least one handler had an event queued.
   157  func (q *Router) Queue(events ...event.Event) bool {
   158  	for _, e := range events {
   159  		switch e := e.(type) {
   160  		case profile.Event:
   161  			q.profile = e
   162  		case pointer.Event:
   163  			q.pointer.queue.Push(e, &q.handlers)
   164  		case key.Event:
   165  			q.queueKeyEvent(e)
   166  		case key.SnippetEvent:
   167  			// Expand existing, overlapping snippet.
   168  			if r := q.key.queue.content.Snippet.Range; rangeOverlaps(r, key.Range(e)) {
   169  				if e.Start > r.Start {
   170  					e.Start = r.Start
   171  				}
   172  				if e.End < r.End {
   173  					e.End = r.End
   174  				}
   175  			}
   176  			if f := q.key.queue.focus; f != nil {
   177  				q.handlers.Add(f, e)
   178  			}
   179  		case key.EditEvent, key.FocusEvent, key.SelectionEvent:
   180  			if f := q.key.queue.focus; f != nil {
   181  				q.handlers.Add(f, e)
   182  			}
   183  		case clipboard.Event:
   184  			q.cqueue.Push(e, &q.handlers)
   185  		}
   186  	}
   187  	return q.handlers.HadEvents()
   188  }
   189  
   190  func rangeOverlaps(r1, r2 key.Range) bool {
   191  	r1 = rangeNorm(r1)
   192  	r2 = rangeNorm(r2)
   193  	return r1.Start <= r2.Start && r2.Start < r1.End ||
   194  		r1.Start <= r2.End && r2.End < r1.End
   195  }
   196  
   197  func rangeNorm(r key.Range) key.Range {
   198  	if r.End < r.Start {
   199  		r.End, r.Start = r.Start, r.End
   200  	}
   201  	return r
   202  }
   203  
   204  func (q *Router) queueKeyEvent(e key.Event) {
   205  	kq := &q.key.queue
   206  	f := q.key.queue.focus
   207  	if f != nil && kq.Accepts(f, e) {
   208  		q.handlers.Add(f, e)
   209  		return
   210  	}
   211  	pq := &q.pointer.queue
   212  	idx := len(pq.hitTree) - 1
   213  	focused := f != nil
   214  	if focused {
   215  		// If there is a focused tag, traverse its ancestry through the
   216  		// hit tree to search for handlers.
   217  		for ; pq.hitTree[idx].ktag != f; idx-- {
   218  		}
   219  	}
   220  	for idx != -1 {
   221  		n := &pq.hitTree[idx]
   222  		if focused {
   223  			idx = n.next
   224  		} else {
   225  			idx--
   226  		}
   227  		if n.ktag == nil {
   228  			continue
   229  		}
   230  		if kq.Accepts(n.ktag, e) {
   231  			q.handlers.Add(n.ktag, e)
   232  			break
   233  		}
   234  	}
   235  }
   236  
   237  func (q *Router) MoveFocus(dir FocusDirection) bool {
   238  	return q.key.queue.MoveFocus(dir, &q.handlers)
   239  }
   240  
   241  // RevealFocus scrolls the current focus (if any) into viewport
   242  // if there are scrollable parent handlers.
   243  func (q *Router) RevealFocus(viewport image.Rectangle) {
   244  	focus := q.key.queue.focus
   245  	if focus == nil {
   246  		return
   247  	}
   248  	bounds := q.key.queue.BoundsFor(focus)
   249  	area := q.key.queue.AreaFor(focus)
   250  	viewport = q.pointer.queue.ClipFor(area, viewport)
   251  
   252  	topleft := bounds.Min.Sub(viewport.Min)
   253  	topleft = max(topleft, bounds.Max.Sub(viewport.Max))
   254  	topleft = min(image.Pt(0, 0), topleft)
   255  	bottomright := bounds.Max.Sub(viewport.Max)
   256  	bottomright = min(bottomright, bounds.Min.Sub(viewport.Min))
   257  	bottomright = max(image.Pt(0, 0), bottomright)
   258  	s := topleft
   259  	if s.X == 0 {
   260  		s.X = bottomright.X
   261  	}
   262  	if s.Y == 0 {
   263  		s.Y = bottomright.Y
   264  	}
   265  	q.ScrollFocus(s)
   266  }
   267  
   268  // ScrollFocus scrolls the focused widget, if any, by dist.
   269  func (q *Router) ScrollFocus(dist image.Point) {
   270  	focus := q.key.queue.focus
   271  	if focus == nil {
   272  		return
   273  	}
   274  	area := q.key.queue.AreaFor(focus)
   275  	q.pointer.queue.Deliver(area, pointer.Event{
   276  		Kind:   pointer.Scroll,
   277  		Source: pointer.Touch,
   278  		Scroll: f32internal.FPt(dist),
   279  	}, &q.handlers)
   280  }
   281  
   282  func max(p1, p2 image.Point) image.Point {
   283  	m := p1
   284  	if p2.X > m.X {
   285  		m.X = p2.X
   286  	}
   287  	if p2.Y > m.Y {
   288  		m.Y = p2.Y
   289  	}
   290  	return m
   291  }
   292  
   293  func min(p1, p2 image.Point) image.Point {
   294  	m := p1
   295  	if p2.X < m.X {
   296  		m.X = p2.X
   297  	}
   298  	if p2.Y < m.Y {
   299  		m.Y = p2.Y
   300  	}
   301  	return m
   302  }
   303  
   304  func (q *Router) ActionAt(p f32.Point) (system.Action, bool) {
   305  	return q.pointer.queue.ActionAt(p)
   306  }
   307  
   308  func (q *Router) ClickFocus() {
   309  	focus := q.key.queue.focus
   310  	if focus == nil {
   311  		return
   312  	}
   313  	bounds := q.key.queue.BoundsFor(focus)
   314  	center := bounds.Max.Add(bounds.Min).Div(2)
   315  	e := pointer.Event{
   316  		Position: f32.Pt(float32(center.X), float32(center.Y)),
   317  		Source:   pointer.Touch,
   318  	}
   319  	area := q.key.queue.AreaFor(focus)
   320  	e.Kind = pointer.Press
   321  	q.pointer.queue.Deliver(area, e, &q.handlers)
   322  	e.Kind = pointer.Release
   323  	q.pointer.queue.Deliver(area, e, &q.handlers)
   324  }
   325  
   326  // TextInputState returns the input state from the most recent
   327  // call to Frame.
   328  func (q *Router) TextInputState() TextInputState {
   329  	return q.key.queue.InputState()
   330  }
   331  
   332  // TextInputHint returns the input mode from the most recent key.InputOp.
   333  func (q *Router) TextInputHint() (key.InputHint, bool) {
   334  	return q.key.queue.InputHint()
   335  }
   336  
   337  // WriteClipboard returns the most recent text to be copied
   338  // to the clipboard, if any.
   339  func (q *Router) WriteClipboard() (string, bool) {
   340  	return q.cqueue.WriteClipboard()
   341  }
   342  
   343  // ReadClipboard reports if any new handler is waiting
   344  // to read the clipboard.
   345  func (q *Router) ReadClipboard() bool {
   346  	return q.cqueue.ReadClipboard()
   347  }
   348  
   349  // Cursor returns the last cursor set.
   350  func (q *Router) Cursor() pointer.Cursor {
   351  	return q.pointer.queue.cursor
   352  }
   353  
   354  // SemanticAt returns the first semantic description under pos, if any.
   355  func (q *Router) SemanticAt(pos f32.Point) (SemanticID, bool) {
   356  	return q.pointer.queue.SemanticAt(pos)
   357  }
   358  
   359  // AppendSemantics appends the semantic tree to nodes, and returns the result.
   360  // The root node is the first added.
   361  func (q *Router) AppendSemantics(nodes []SemanticNode) []SemanticNode {
   362  	q.pointer.collector.q = &q.pointer.queue
   363  	q.pointer.collector.ensureRoot()
   364  	return q.pointer.queue.AppendSemantics(nodes)
   365  }
   366  
   367  // EditorState returns the editor state for the focused handler, or the
   368  // zero value if there is none.
   369  func (q *Router) EditorState() EditorState {
   370  	return q.key.queue.content
   371  }
   372  
   373  func (q *Router) collect() {
   374  	q.transStack = q.transStack[:0]
   375  	pc := &q.pointer.collector
   376  	pc.q = &q.pointer.queue
   377  	pc.reset()
   378  	kc := &q.key.collector
   379  	*kc = keyCollector{q: &q.key.queue}
   380  	q.key.queue.Reset()
   381  	var t f32.Affine2D
   382  	bo := binary.LittleEndian
   383  	for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
   384  		switch ops.OpType(encOp.Data[0]) {
   385  		case ops.TypeInvalidate:
   386  			op := decodeInvalidateOp(encOp.Data)
   387  			if !q.wakeup || op.At.Before(q.wakeupTime) {
   388  				q.wakeup = true
   389  				q.wakeupTime = op.At
   390  			}
   391  		case ops.TypeProfile:
   392  			op := decodeProfileOp(encOp.Data, encOp.Refs)
   393  			if q.profHandlers == nil {
   394  				q.profHandlers = make(map[event.Tag]struct{})
   395  			}
   396  			q.profHandlers[op.Tag] = struct{}{}
   397  		case ops.TypeClipboardRead:
   398  			q.cqueue.ProcessReadClipboard(encOp.Refs)
   399  		case ops.TypeClipboardWrite:
   400  			q.cqueue.ProcessWriteClipboard(encOp.Refs)
   401  		case ops.TypeSave:
   402  			id := ops.DecodeSave(encOp.Data)
   403  			if extra := id - len(q.savedTrans) + 1; extra > 0 {
   404  				q.savedTrans = append(q.savedTrans, make([]f32.Affine2D, extra)...)
   405  			}
   406  			q.savedTrans[id] = t
   407  		case ops.TypeLoad:
   408  			id := ops.DecodeLoad(encOp.Data)
   409  			t = q.savedTrans[id]
   410  			pc.resetState()
   411  			pc.setTrans(t)
   412  
   413  		case ops.TypeClip:
   414  			var op ops.ClipOp
   415  			op.Decode(encOp.Data)
   416  			pc.clip(op)
   417  		case ops.TypePopClip:
   418  			pc.popArea()
   419  		case ops.TypeTransform:
   420  			t2, push := ops.DecodeTransform(encOp.Data)
   421  			if push {
   422  				q.transStack = append(q.transStack, t)
   423  			}
   424  			t = t.Mul(t2)
   425  			pc.setTrans(t)
   426  		case ops.TypePopTransform:
   427  			n := len(q.transStack)
   428  			t = q.transStack[n-1]
   429  			q.transStack = q.transStack[:n-1]
   430  			pc.setTrans(t)
   431  
   432  		// Pointer ops.
   433  		case ops.TypePass:
   434  			pc.pass()
   435  		case ops.TypePopPass:
   436  			pc.popPass()
   437  		case ops.TypePointerInput:
   438  			op := pointer.InputOp{
   439  				Tag:   encOp.Refs[0].(event.Tag),
   440  				Grab:  encOp.Data[1] != 0,
   441  				Kinds: pointer.Kind(bo.Uint16(encOp.Data[2:])),
   442  				ScrollBounds: image.Rectangle{
   443  					Min: image.Point{
   444  						X: int(int32(bo.Uint32(encOp.Data[4:]))),
   445  						Y: int(int32(bo.Uint32(encOp.Data[8:]))),
   446  					},
   447  					Max: image.Point{
   448  						X: int(int32(bo.Uint32(encOp.Data[12:]))),
   449  						Y: int(int32(bo.Uint32(encOp.Data[16:]))),
   450  					},
   451  				},
   452  			}
   453  			pc.inputOp(op, &q.handlers)
   454  		case ops.TypeCursor:
   455  			name := pointer.Cursor(encOp.Data[1])
   456  			pc.cursor(name)
   457  		case ops.TypeSource:
   458  			op := transfer.SourceOp{
   459  				Tag:  encOp.Refs[0].(event.Tag),
   460  				Type: encOp.Refs[1].(string),
   461  			}
   462  			pc.sourceOp(op, &q.handlers)
   463  		case ops.TypeTarget:
   464  			op := transfer.TargetOp{
   465  				Tag:  encOp.Refs[0].(event.Tag),
   466  				Type: encOp.Refs[1].(string),
   467  			}
   468  			pc.targetOp(op, &q.handlers)
   469  		case ops.TypeOffer:
   470  			op := transfer.OfferOp{
   471  				Tag:  encOp.Refs[0].(event.Tag),
   472  				Type: encOp.Refs[1].(string),
   473  				Data: encOp.Refs[2].(io.ReadCloser),
   474  			}
   475  			pc.offerOp(op, &q.handlers)
   476  		case ops.TypeActionInput:
   477  			act := system.Action(encOp.Data[1])
   478  			pc.actionInputOp(act)
   479  
   480  		// Key ops.
   481  		case ops.TypeKeyFocus:
   482  			tag, _ := encOp.Refs[0].(event.Tag)
   483  			op := key.FocusOp{
   484  				Tag: tag,
   485  			}
   486  			kc.focusOp(op.Tag)
   487  		case ops.TypeKeySoftKeyboard:
   488  			op := key.SoftKeyboardOp{
   489  				Show: encOp.Data[1] != 0,
   490  			}
   491  			kc.softKeyboard(op.Show)
   492  		case ops.TypeKeyInput:
   493  			filter := key.Set(*encOp.Refs[1].(*string))
   494  			op := key.InputOp{
   495  				Tag:  encOp.Refs[0].(event.Tag),
   496  				Hint: key.InputHint(encOp.Data[1]),
   497  				Keys: filter,
   498  			}
   499  			a := pc.currentArea()
   500  			b := pc.currentAreaBounds()
   501  			pc.keyInputOp(op)
   502  			kc.inputOp(op, a, b)
   503  		case ops.TypeSnippet:
   504  			op := key.SnippetOp{
   505  				Tag: encOp.Refs[0].(event.Tag),
   506  				Snippet: key.Snippet{
   507  					Range: key.Range{
   508  						Start: int(int32(bo.Uint32(encOp.Data[1:]))),
   509  						End:   int(int32(bo.Uint32(encOp.Data[5:]))),
   510  					},
   511  					Text: *(encOp.Refs[1].(*string)),
   512  				},
   513  			}
   514  			kc.snippetOp(op)
   515  		case ops.TypeSelection:
   516  			op := key.SelectionOp{
   517  				Tag: encOp.Refs[0].(event.Tag),
   518  				Range: key.Range{
   519  					Start: int(int32(bo.Uint32(encOp.Data[1:]))),
   520  					End:   int(int32(bo.Uint32(encOp.Data[5:]))),
   521  				},
   522  				Caret: key.Caret{
   523  					Pos: f32.Point{
   524  						X: math.Float32frombits(bo.Uint32(encOp.Data[9:])),
   525  						Y: math.Float32frombits(bo.Uint32(encOp.Data[13:])),
   526  					},
   527  					Ascent:  math.Float32frombits(bo.Uint32(encOp.Data[17:])),
   528  					Descent: math.Float32frombits(bo.Uint32(encOp.Data[21:])),
   529  				},
   530  			}
   531  			kc.selectionOp(t, op)
   532  
   533  		// Semantic ops.
   534  		case ops.TypeSemanticLabel:
   535  			lbl := *encOp.Refs[0].(*string)
   536  			pc.semanticLabel(lbl)
   537  		case ops.TypeSemanticDesc:
   538  			desc := *encOp.Refs[0].(*string)
   539  			pc.semanticDesc(desc)
   540  		case ops.TypeSemanticClass:
   541  			class := semantic.ClassOp(encOp.Data[1])
   542  			pc.semanticClass(class)
   543  		case ops.TypeSemanticSelected:
   544  			if encOp.Data[1] != 0 {
   545  				pc.semanticSelected(true)
   546  			} else {
   547  				pc.semanticSelected(false)
   548  			}
   549  		case ops.TypeSemanticEnabled:
   550  			if encOp.Data[1] != 0 {
   551  				pc.semanticEnabled(true)
   552  			} else {
   553  				pc.semanticEnabled(false)
   554  			}
   555  		}
   556  	}
   557  }
   558  
   559  // Profiling reports whether there was profile handlers in the
   560  // most recent Frame call.
   561  func (q *Router) Profiling() bool {
   562  	return len(q.profHandlers) > 0
   563  }
   564  
   565  // WakeupTime returns the most recent time for doing another frame,
   566  // as determined from the last call to Frame.
   567  func (q *Router) WakeupTime() (time.Time, bool) {
   568  	return q.wakeupTime, q.wakeup
   569  }
   570  
   571  func (h *handlerEvents) init() {
   572  	if h.handlers == nil {
   573  		h.handlers = make(map[event.Tag][]event.Event)
   574  	}
   575  }
   576  
   577  func (h *handlerEvents) AddNoRedraw(k event.Tag, e event.Event) {
   578  	h.init()
   579  	h.handlers[k] = append(h.handlers[k], e)
   580  }
   581  
   582  func (h *handlerEvents) Add(k event.Tag, e event.Event) {
   583  	h.AddNoRedraw(k, e)
   584  	h.hadEvents = true
   585  }
   586  
   587  func (h *handlerEvents) HadEvents() bool {
   588  	u := h.hadEvents
   589  	h.hadEvents = false
   590  	return u
   591  }
   592  
   593  func (h *handlerEvents) Events(k event.Tag) []event.Event {
   594  	if events, ok := h.handlers[k]; ok {
   595  		h.handlers[k] = h.handlers[k][:0]
   596  		return events
   597  	}
   598  	return nil
   599  }
   600  
   601  func (h *handlerEvents) Clear() {
   602  	for k := range h.handlers {
   603  		delete(h.handlers, k)
   604  	}
   605  }
   606  
   607  func decodeProfileOp(d []byte, refs []interface{}) profile.Op {
   608  	if ops.OpType(d[0]) != ops.TypeProfile {
   609  		panic("invalid op")
   610  	}
   611  	return profile.Op{
   612  		Tag: refs[0].(event.Tag),
   613  	}
   614  }
   615  
   616  func decodeInvalidateOp(d []byte) op.InvalidateOp {
   617  	bo := binary.LittleEndian
   618  	if ops.OpType(d[0]) != ops.TypeInvalidate {
   619  		panic("invalid op")
   620  	}
   621  	var o op.InvalidateOp
   622  	if nanos := bo.Uint64(d[1:]); nanos > 0 {
   623  		o.At = time.Unix(0, int64(nanos))
   624  	}
   625  	return o
   626  }
   627  
   628  func (s SemanticGestures) String() string {
   629  	var gestures []string
   630  	if s&ClickGesture != 0 {
   631  		gestures = append(gestures, "Click")
   632  	}
   633  	return strings.Join(gestures, ",")
   634  }