123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881 |
- // SPDX-License-Identifier: Unlicense OR MIT
- package input
- import (
- "image"
- "io"
- "strings"
- "time"
- "gioui.org/f32"
- f32internal "gioui.org/internal/f32"
- "gioui.org/internal/ops"
- "gioui.org/io/clipboard"
- "gioui.org/io/event"
- "gioui.org/io/key"
- "gioui.org/io/pointer"
- "gioui.org/io/semantic"
- "gioui.org/io/system"
- "gioui.org/io/transfer"
- "gioui.org/op"
- )
- // Router tracks the [io/event.Tag] identifiers of user interface widgets
- // and routes events to them. [Source] is its interface exposed to widgets.
- type Router struct {
- savedTrans []f32.Affine2D
- transStack []f32.Affine2D
- handlers map[event.Tag]*handler
- pointer struct {
- queue pointerQueue
- collector pointerCollector
- }
- key struct {
- queue keyQueue
- // The following fields have the same purpose as the fields in
- // type handler, but for key.Events.
- filter keyFilter
- nextFilter keyFilter
- scratchFilter keyFilter
- }
- cqueue clipboardQueue
- // states is the list of pending state changes resulting from
- // incoming events. The first element, if present, contains the state
- // and events for the current frame.
- changes []stateChange
- reader ops.Reader
- // InvalidateCmd summary.
- wakeup bool
- wakeupTime time.Time
- // Changes queued for next call to Frame.
- commands []Command
- // transfers is the pending transfer.DataEvent.Open functions.
- transfers []io.ReadCloser
- // deferring is set if command execution and event delivery is deferred
- // to the next frame.
- deferring bool
- // scratchFilters is for garbage-free construction of ephemeral filters.
- scratchFilters []taggedFilter
- }
- // Source implements the interface between a Router and user interface widgets.
- // The zero-value Source is disabled.
- type Source struct {
- r *Router
- }
- // Command represents a request such as moving the focus, or initiating a clipboard read.
- // Commands are queued by calling [Source.Queue].
- type Command interface {
- ImplementsCommand()
- }
- // SemanticNode represents a node in the tree describing the components
- // contained in a frame.
- type SemanticNode struct {
- ID SemanticID
- ParentID SemanticID
- Children []SemanticNode
- Desc SemanticDesc
- areaIdx int
- }
- // SemanticDesc provides a semantic description of a UI component.
- type SemanticDesc struct {
- Class semantic.ClassOp
- Description string
- Label string
- Selected bool
- Disabled bool
- Gestures SemanticGestures
- Bounds image.Rectangle
- }
- // SemanticGestures is a bit-set of supported gestures.
- type SemanticGestures int
- const (
- ClickGesture SemanticGestures = 1 << iota
- ScrollGesture
- )
- // SemanticID uniquely identifies a SemanticDescription.
- //
- // By convention, the zero value denotes the non-existent ID.
- type SemanticID uint
- // SystemEvent is a marker for events that have platform specific
- // side-effects. SystemEvents are never matched by catch-all filters.
- type SystemEvent struct {
- Event event.Event
- }
- // handler contains the per-handler state tracked by a [Router].
- type handler struct {
- // active tracks whether the handler was active in the current
- // frame. Router deletes state belonging to inactive handlers during Frame.
- active bool
- pointer pointerHandler
- key keyHandler
- // filter the handler has asked for through event handling
- // in the previous frame. It is used for routing events in the
- // current frame.
- filter filter
- // prevFilter is the filter being built in the current frame.
- nextFilter filter
- // processedFilter is the filters that have exhausted available events.
- processedFilter filter
- }
- // filter is the union of a set of [io/event.Filters].
- type filter struct {
- pointer pointerFilter
- focusable bool
- }
- // taggedFilter is a filter for a particular tag.
- type taggedFilter struct {
- tag event.Tag
- filter filter
- }
- // stateChange represents the new state and outgoing events
- // resulting from an incoming event.
- type stateChange struct {
- // event, if set, is the trigger for the change.
- event event.Event
- state inputState
- events []taggedEvent
- }
- // inputState represent a immutable snapshot of the state required
- // to route events.
- type inputState struct {
- clipboardState
- keyState
- pointerState
- }
- // taggedEvent represents an event and its target handler.
- type taggedEvent struct {
- event event.Event
- tag event.Tag
- }
- // Source returns a Source backed by this Router.
- func (q *Router) Source() Source {
- return Source{r: q}
- }
- // Execute a command.
- func (s Source) Execute(c Command) {
- if !s.enabled() {
- return
- }
- s.r.execute(c)
- }
- // enabled reports whether the source is enabled. Only enabled
- // Sources deliver events and respond to commands.
- func (s Source) enabled() bool {
- return s.r != nil
- }
- // Focused reports whether tag is focused, according to the most recent
- // [key.FocusEvent] delivered.
- func (s Source) Focused(tag event.Tag) bool {
- if !s.enabled() {
- return false
- }
- return s.r.state().keyState.focus == tag
- }
- // Event returns the next event that matches at least one of filters.
- func (s Source) Event(filters ...event.Filter) (event.Event, bool) {
- if !s.enabled() {
- return nil, false
- }
- return s.r.Event(filters...)
- }
- func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
- // Merge filters into scratch filters.
- q.scratchFilters = q.scratchFilters[:0]
- q.key.scratchFilter = q.key.scratchFilter[:0]
- for _, f := range filters {
- var t event.Tag
- switch f := f.(type) {
- case key.Filter:
- q.key.scratchFilter = append(q.key.scratchFilter, f)
- continue
- case transfer.SourceFilter:
- t = f.Target
- case transfer.TargetFilter:
- t = f.Target
- case key.FocusFilter:
- t = f.Target
- case pointer.Filter:
- t = f.Target
- }
- if t == nil {
- continue
- }
- var filter *filter
- for i := range q.scratchFilters {
- s := &q.scratchFilters[i]
- if s.tag == t {
- filter = &s.filter
- break
- }
- }
- if filter == nil {
- n := len(q.scratchFilters)
- if n < cap(q.scratchFilters) {
- // Re-use previously allocated filter.
- q.scratchFilters = q.scratchFilters[:n+1]
- tf := &q.scratchFilters[n]
- tf.tag = t
- filter = &tf.filter
- filter.Reset()
- } else {
- q.scratchFilters = append(q.scratchFilters, taggedFilter{tag: t})
- filter = &q.scratchFilters[n].filter
- }
- }
- filter.Add(f)
- }
- for _, tf := range q.scratchFilters {
- h := q.stateFor(tf.tag)
- h.filter.Merge(tf.filter)
- h.nextFilter.Merge(tf.filter)
- }
- q.key.filter = append(q.key.filter, q.key.scratchFilter...)
- q.key.nextFilter = append(q.key.nextFilter, q.key.scratchFilter...)
- // Deliver reset event, if any.
- for _, f := range filters {
- switch f := f.(type) {
- case key.FocusFilter:
- if f.Target == nil {
- break
- }
- h := q.stateFor(f.Target)
- if reset, ok := h.key.ResetEvent(); ok {
- return reset, true
- }
- case pointer.Filter:
- if f.Target == nil {
- break
- }
- h := q.stateFor(f.Target)
- if reset, ok := h.pointer.ResetEvent(); ok && h.filter.pointer.Matches(reset) {
- return reset, true
- }
- }
- }
- for i := range q.changes {
- if q.deferring && i > 0 {
- break
- }
- change := &q.changes[i]
- for j, evt := range change.events {
- match := false
- switch e := evt.event.(type) {
- case key.Event:
- match = q.key.scratchFilter.Matches(change.state.keyState.focus, e, false)
- default:
- for _, tf := range q.scratchFilters {
- if evt.tag == tf.tag && tf.filter.Matches(evt.event) {
- match = true
- break
- }
- }
- }
- if match {
- change.events = append(change.events[:j], change.events[j+1:]...)
- // Fast forward state to last matched.
- q.collapseState(i)
- return evt.event, true
- }
- }
- }
- for _, tf := range q.scratchFilters {
- h := q.stateFor(tf.tag)
- h.processedFilter.Merge(tf.filter)
- }
- return nil, false
- }
- // collapseState in the interval [1;idx] into q.changes[0].
- func (q *Router) collapseState(idx int) {
- if idx == 0 {
- return
- }
- first := &q.changes[0]
- first.state = q.changes[idx].state
- for _, ch := range q.changes[1 : idx+1] {
- first.events = append(first.events, ch.events...)
- }
- q.changes = append(q.changes[:1], q.changes[idx+1:]...)
- }
- // Frame completes the current frame and starts a new with the
- // handlers from the frame argument. Remaining events are discarded,
- // unless they were deferred by a command.
- func (q *Router) Frame(frame *op.Ops) {
- var remaining []event.Event
- if n := len(q.changes); n > 0 {
- if q.deferring {
- // Collect events for replay.
- for _, ch := range q.changes[1:] {
- remaining = append(remaining, ch.event)
- }
- q.changes = append(q.changes[:0], stateChange{state: q.changes[0].state})
- } else {
- // Collapse state.
- state := q.changes[n-1].state
- q.changes = append(q.changes[:0], stateChange{state: state})
- }
- }
- for _, rc := range q.transfers {
- if rc != nil {
- rc.Close()
- }
- }
- q.transfers = nil
- q.deferring = false
- for _, h := range q.handlers {
- h.filter, h.nextFilter = h.nextFilter, h.filter
- h.nextFilter.Reset()
- h.processedFilter.Reset()
- h.pointer.Reset()
- h.key.Reset()
- }
- q.key.filter, q.key.nextFilter = q.key.nextFilter, q.key.filter
- q.key.nextFilter = q.key.nextFilter[:0]
- var ops *ops.Ops
- if frame != nil {
- ops = &frame.Internal
- }
- q.reader.Reset(ops)
- q.collect()
- for k, h := range q.handlers {
- if !h.active {
- delete(q.handlers, k)
- } else {
- h.active = false
- }
- }
- q.executeCommands()
- q.Queue(remaining...)
- st := q.lastState()
- pst, evts := q.pointer.queue.Frame(q.handlers, st.pointerState)
- st.pointerState = pst
- st.keyState = q.key.queue.Frame(q.handlers, q.lastState().keyState)
- q.changeState(nil, st, evts)
- // Collapse state and events.
- q.collapseState(len(q.changes) - 1)
- }
- // Queue events to be routed.
- func (q *Router) Queue(events ...event.Event) {
- for _, e := range events {
- se, system := e.(SystemEvent)
- if system {
- e = se.Event
- }
- q.processEvent(e, system)
- }
- }
- func (f *filter) Add(flt event.Filter) {
- switch flt := flt.(type) {
- case key.FocusFilter:
- f.focusable = true
- case pointer.Filter:
- f.pointer.Add(flt)
- case transfer.SourceFilter, transfer.TargetFilter:
- f.pointer.Add(flt)
- }
- }
- // Merge f2 into f.
- func (f *filter) Merge(f2 filter) {
- f.focusable = f.focusable || f2.focusable
- f.pointer.Merge(f2.pointer)
- }
- func (f *filter) Matches(e event.Event) bool {
- switch e.(type) {
- case key.FocusEvent, key.SnippetEvent, key.EditEvent, key.SelectionEvent:
- return f.focusable
- default:
- return f.pointer.Matches(e)
- }
- }
- func (f *filter) Reset() {
- *f = filter{
- pointer: pointerFilter{
- sourceMimes: f.pointer.sourceMimes[:0],
- targetMimes: f.pointer.targetMimes[:0],
- },
- }
- }
- func (q *Router) processEvent(e event.Event, system bool) {
- state := q.lastState()
- switch e := e.(type) {
- case pointer.Event:
- pstate, evts := q.pointer.queue.Push(q.handlers, state.pointerState, e)
- state.pointerState = pstate
- q.changeState(e, state, evts)
- case key.Event:
- var evts []taggedEvent
- if q.key.filter.Matches(state.keyState.focus, e, system) {
- evts = append(evts, taggedEvent{event: e})
- }
- q.changeState(e, state, evts)
- case key.SnippetEvent:
- // Expand existing, overlapping snippet.
- if r := state.content.Snippet.Range; rangeOverlaps(r, key.Range(e)) {
- if e.Start > r.Start {
- e.Start = r.Start
- }
- if e.End < r.End {
- e.End = r.End
- }
- }
- var evts []taggedEvent
- if f := state.focus; f != nil {
- evts = append(evts, taggedEvent{tag: f, event: e})
- }
- q.changeState(e, state, evts)
- case key.EditEvent, key.FocusEvent, key.SelectionEvent:
- var evts []taggedEvent
- if f := state.focus; f != nil {
- evts = append(evts, taggedEvent{tag: f, event: e})
- }
- q.changeState(e, state, evts)
- case transfer.DataEvent:
- cstate, evts := q.cqueue.Push(state.clipboardState, e)
- state.clipboardState = cstate
- q.changeState(e, state, evts)
- default:
- panic("unknown event type")
- }
- }
- func (q *Router) execute(c Command) {
- // The command can be executed immediately if event delivery is not frozen, and
- // no event receiver has completed their event handling.
- if !q.deferring {
- ch := q.executeCommand(c)
- immediate := true
- for _, e := range ch.events {
- h, ok := q.handlers[e.tag]
- immediate = immediate && (!ok || !h.processedFilter.Matches(e.event))
- }
- if immediate {
- // Hold on to the remaining events for state replay.
- var evts []event.Event
- for _, ch := range q.changes {
- if ch.event != nil {
- evts = append(evts, ch.event)
- }
- }
- if len(q.changes) > 1 {
- q.changes = q.changes[:1]
- }
- q.changeState(nil, ch.state, ch.events)
- q.Queue(evts...)
- return
- }
- }
- q.deferring = true
- q.commands = append(q.commands, c)
- }
- func (q *Router) state() inputState {
- if len(q.changes) > 0 {
- return q.changes[0].state
- }
- return inputState{}
- }
- func (q *Router) lastState() inputState {
- if n := len(q.changes); n > 0 {
- return q.changes[n-1].state
- }
- return inputState{}
- }
- func (q *Router) executeCommands() {
- for _, c := range q.commands {
- ch := q.executeCommand(c)
- q.changeState(nil, ch.state, ch.events)
- }
- q.commands = nil
- }
- // executeCommand the command and return the resulting state change along with the
- // tag the state change depended on, if any.
- func (q *Router) executeCommand(c Command) stateChange {
- state := q.state()
- var evts []taggedEvent
- switch req := c.(type) {
- case key.SelectionCmd:
- state.keyState = q.key.queue.setSelection(state.keyState, req)
- case key.FocusCmd:
- state.keyState, evts = q.key.queue.Focus(q.handlers, state.keyState, req.Tag)
- case key.SoftKeyboardCmd:
- state.keyState = state.keyState.softKeyboard(req.Show)
- case key.SnippetCmd:
- state.keyState = q.key.queue.setSnippet(state.keyState, req)
- case transfer.OfferCmd:
- state.pointerState, evts = q.pointer.queue.offerData(q.handlers, state.pointerState, req)
- case clipboard.WriteCmd:
- q.cqueue.ProcessWriteClipboard(req)
- case clipboard.ReadCmd:
- state.clipboardState = q.cqueue.ProcessReadClipboard(state.clipboardState, req.Tag)
- case pointer.GrabCmd:
- state.pointerState, evts = q.pointer.queue.grab(state.pointerState, req)
- case op.InvalidateCmd:
- if !q.wakeup || req.At.Before(q.wakeupTime) {
- q.wakeup = true
- q.wakeupTime = req.At
- }
- }
- return stateChange{state: state, events: evts}
- }
- func (q *Router) changeState(e event.Event, state inputState, evts []taggedEvent) {
- // Wrap pointer.DataEvent.Open functions to detect them not being called.
- for i := range evts {
- e := &evts[i]
- if de, ok := e.event.(transfer.DataEvent); ok {
- transferIdx := len(q.transfers)
- data := de.Open()
- q.transfers = append(q.transfers, data)
- de.Open = func() io.ReadCloser {
- q.transfers[transferIdx] = nil
- return data
- }
- e.event = de
- }
- }
- // Initialize the first change to contain the current state
- // and events that are bound for the current frame.
- if len(q.changes) == 0 {
- q.changes = append(q.changes, stateChange{})
- }
- if e != nil && len(evts) > 0 {
- // An event triggered events bound for user receivers. Add a state change to be
- // able to redo the change in case of a command execution.
- q.changes = append(q.changes, stateChange{event: e, state: state, events: evts})
- } else {
- // Otherwise, merge with previous change.
- prev := &q.changes[len(q.changes)-1]
- prev.state = state
- prev.events = append(prev.events, evts...)
- }
- }
- func rangeOverlaps(r1, r2 key.Range) bool {
- r1 = rangeNorm(r1)
- r2 = rangeNorm(r2)
- return r1.Start <= r2.Start && r2.Start < r1.End ||
- r1.Start <= r2.End && r2.End < r1.End
- }
- func rangeNorm(r key.Range) key.Range {
- if r.End < r.Start {
- r.End, r.Start = r.Start, r.End
- }
- return r
- }
- func (q *Router) MoveFocus(dir key.FocusDirection) {
- state := q.lastState()
- kstate, evts := q.key.queue.MoveFocus(q.handlers, state.keyState, dir)
- state.keyState = kstate
- q.changeState(nil, state, evts)
- }
- // RevealFocus scrolls the current focus (if any) into viewport
- // if there are scrollable parent handlers.
- func (q *Router) RevealFocus(viewport image.Rectangle) {
- state := q.lastState()
- focus := state.focus
- if focus == nil {
- return
- }
- kh := &q.handlers[focus].key
- bounds := q.key.queue.BoundsFor(kh)
- area := q.key.queue.AreaFor(kh)
- viewport = q.pointer.queue.ClipFor(area, viewport)
- topleft := bounds.Min.Sub(viewport.Min)
- topleft = max(topleft, bounds.Max.Sub(viewport.Max))
- topleft = min(image.Pt(0, 0), topleft)
- bottomright := bounds.Max.Sub(viewport.Max)
- bottomright = min(bottomright, bounds.Min.Sub(viewport.Min))
- bottomright = max(image.Pt(0, 0), bottomright)
- s := topleft
- if s.X == 0 {
- s.X = bottomright.X
- }
- if s.Y == 0 {
- s.Y = bottomright.Y
- }
- q.ScrollFocus(s)
- }
- // ScrollFocus scrolls the focused widget, if any, by dist.
- func (q *Router) ScrollFocus(dist image.Point) {
- state := q.lastState()
- focus := state.focus
- if focus == nil {
- return
- }
- kh := &q.handlers[focus].key
- area := q.key.queue.AreaFor(kh)
- q.changeState(nil, q.lastState(), q.pointer.queue.Deliver(q.handlers, area, pointer.Event{
- Kind: pointer.Scroll,
- Source: pointer.Touch,
- Scroll: f32internal.FPt(dist),
- }))
- }
- func max(p1, p2 image.Point) image.Point {
- m := p1
- if p2.X > m.X {
- m.X = p2.X
- }
- if p2.Y > m.Y {
- m.Y = p2.Y
- }
- return m
- }
- func min(p1, p2 image.Point) image.Point {
- m := p1
- if p2.X < m.X {
- m.X = p2.X
- }
- if p2.Y < m.Y {
- m.Y = p2.Y
- }
- return m
- }
- func (q *Router) ActionAt(p f32.Point) (system.Action, bool) {
- return q.pointer.queue.ActionAt(p)
- }
- func (q *Router) ClickFocus() {
- focus := q.lastState().focus
- if focus == nil {
- return
- }
- kh := &q.handlers[focus].key
- bounds := q.key.queue.BoundsFor(kh)
- center := bounds.Max.Add(bounds.Min).Div(2)
- e := pointer.Event{
- Position: f32.Pt(float32(center.X), float32(center.Y)),
- Source: pointer.Touch,
- }
- area := q.key.queue.AreaFor(kh)
- e.Kind = pointer.Press
- state := q.lastState()
- q.changeState(nil, state, q.pointer.queue.Deliver(q.handlers, area, e))
- e.Kind = pointer.Release
- q.changeState(nil, state, q.pointer.queue.Deliver(q.handlers, area, e))
- }
- // TextInputState returns the input state from the most recent
- // call to Frame.
- func (q *Router) TextInputState() TextInputState {
- state := q.state()
- kstate, s := state.InputState()
- state.keyState = kstate
- q.changeState(nil, state, nil)
- return s
- }
- // TextInputHint returns the input mode from the most recent key.InputOp.
- func (q *Router) TextInputHint() (key.InputHint, bool) {
- return q.key.queue.InputHint(q.handlers, q.state().keyState)
- }
- // WriteClipboard returns the most recent content to be copied
- // to the clipboard, if any.
- func (q *Router) WriteClipboard() (mime string, content []byte, ok bool) {
- return q.cqueue.WriteClipboard()
- }
- // ClipboardRequested reports if any new handler is waiting
- // to read the clipboard.
- func (q *Router) ClipboardRequested() bool {
- return q.cqueue.ClipboardRequested(q.lastState().clipboardState)
- }
- // Cursor returns the last cursor set.
- func (q *Router) Cursor() pointer.Cursor {
- return q.state().cursor
- }
- // SemanticAt returns the first semantic description under pos, if any.
- func (q *Router) SemanticAt(pos f32.Point) (SemanticID, bool) {
- return q.pointer.queue.SemanticAt(pos)
- }
- // AppendSemantics appends the semantic tree to nodes, and returns the result.
- // The root node is the first added.
- func (q *Router) AppendSemantics(nodes []SemanticNode) []SemanticNode {
- q.pointer.collector.q = &q.pointer.queue
- q.pointer.collector.ensureRoot()
- return q.pointer.queue.AppendSemantics(nodes)
- }
- // EditorState returns the editor state for the focused handler, or the
- // zero value if there is none.
- func (q *Router) EditorState() EditorState {
- return q.key.queue.editorState(q.handlers, q.state().keyState)
- }
- func (q *Router) stateFor(tag event.Tag) *handler {
- if tag == nil {
- panic("internal error: nil tag")
- }
- s, ok := q.handlers[tag]
- if !ok {
- s = new(handler)
- if q.handlers == nil {
- q.handlers = make(map[event.Tag]*handler)
- }
- q.handlers[tag] = s
- }
- s.active = true
- return s
- }
- func (q *Router) collect() {
- q.transStack = q.transStack[:0]
- pc := &q.pointer.collector
- pc.q = &q.pointer.queue
- pc.Reset()
- kq := &q.key.queue
- q.key.queue.Reset()
- var t f32.Affine2D
- for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
- switch ops.OpType(encOp.Data[0]) {
- case ops.TypeSave:
- id := ops.DecodeSave(encOp.Data)
- if extra := id - len(q.savedTrans) + 1; extra > 0 {
- q.savedTrans = append(q.savedTrans, make([]f32.Affine2D, extra)...)
- }
- q.savedTrans[id] = t
- case ops.TypeLoad:
- id := ops.DecodeLoad(encOp.Data)
- t = q.savedTrans[id]
- pc.resetState()
- pc.setTrans(t)
- case ops.TypeClip:
- var op ops.ClipOp
- op.Decode(encOp.Data)
- pc.clip(op)
- case ops.TypePopClip:
- pc.popArea()
- case ops.TypeTransform:
- t2, push := ops.DecodeTransform(encOp.Data)
- if push {
- q.transStack = append(q.transStack, t)
- }
- t = t.Mul(t2)
- pc.setTrans(t)
- case ops.TypePopTransform:
- n := len(q.transStack)
- t = q.transStack[n-1]
- q.transStack = q.transStack[:n-1]
- pc.setTrans(t)
- case ops.TypeInput:
- tag := encOp.Refs[0].(event.Tag)
- s := q.stateFor(tag)
- pc.inputOp(tag, &s.pointer)
- a := pc.currentArea()
- b := pc.currentAreaBounds()
- if s.filter.focusable {
- kq.inputOp(tag, &s.key, t, a, b)
- }
- // Pointer ops.
- case ops.TypePass:
- pc.pass()
- case ops.TypePopPass:
- pc.popPass()
- case ops.TypeCursor:
- name := pointer.Cursor(encOp.Data[1])
- pc.cursor(name)
- case ops.TypeActionInput:
- act := system.Action(encOp.Data[1])
- pc.actionInputOp(act)
- case ops.TypeKeyInputHint:
- op := key.InputHintOp{
- Tag: encOp.Refs[0].(event.Tag),
- Hint: key.InputHint(encOp.Data[1]),
- }
- s := q.stateFor(op.Tag)
- s.key.inputHint(op.Hint)
- // Semantic ops.
- case ops.TypeSemanticLabel:
- lbl := *encOp.Refs[0].(*string)
- pc.semanticLabel(lbl)
- case ops.TypeSemanticDesc:
- desc := *encOp.Refs[0].(*string)
- pc.semanticDesc(desc)
- case ops.TypeSemanticClass:
- class := semantic.ClassOp(encOp.Data[1])
- pc.semanticClass(class)
- case ops.TypeSemanticSelected:
- if encOp.Data[1] != 0 {
- pc.semanticSelected(true)
- } else {
- pc.semanticSelected(false)
- }
- case ops.TypeSemanticEnabled:
- if encOp.Data[1] != 0 {
- pc.semanticEnabled(true)
- } else {
- pc.semanticEnabled(false)
- }
- }
- }
- }
- // WakeupTime returns the most recent time for doing another frame,
- // as determined from the last call to Frame.
- func (q *Router) WakeupTime() (time.Time, bool) {
- t, w := q.wakeupTime, q.wakeup
- q.wakeup = false
- // Pending events always trigger wakeups.
- if len(q.changes) > 1 || len(q.changes) == 1 && len(q.changes[0].events) > 0 {
- t, w = time.Time{}, true
- }
- return t, w
- }
- func (s SemanticGestures) String() string {
- var gestures []string
- if s&ClickGesture != 0 {
- gestures = append(gestures, "Click")
- }
- return strings.Join(gestures, ",")
- }
- func (SystemEvent) ImplementsEvent() {}
|