chore(v2): vendor dependencies for offline/China builds
go mod vendor pins onnxruntime_go v1.12.1, Gio and the rest into v2/vendor so go run/build work without hitting proxy.golang.org (blocked/slow in China). Verified: CGO_ENABLED=1 go build -mod=vendor ./internal/spike and GOOS=windows go build -mod=vendor ./internal/ui both pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+71
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package input
|
||||
|
||||
import (
|
||||
"io"
|
||||
"slices"
|
||||
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/event"
|
||||
)
|
||||
|
||||
// clipboardState contains the state for clipboard event routing.
|
||||
type clipboardState struct {
|
||||
receivers []event.Tag
|
||||
}
|
||||
|
||||
type clipboardQueue struct {
|
||||
// request avoid read clipboard every frame while waiting.
|
||||
requested bool
|
||||
mime string
|
||||
text []byte
|
||||
}
|
||||
|
||||
// WriteClipboard returns the most recent data to be copied
|
||||
// to the clipboard, if any.
|
||||
func (q *clipboardQueue) WriteClipboard() (mime string, content []byte, ok bool) {
|
||||
if q.text == nil {
|
||||
return "", nil, false
|
||||
}
|
||||
content = q.text
|
||||
q.text = nil
|
||||
return q.mime, content, true
|
||||
}
|
||||
|
||||
// ClipboardRequested reports if any new handler is waiting
|
||||
// to read the clipboard.
|
||||
func (q *clipboardQueue) ClipboardRequested(state clipboardState) bool {
|
||||
req := len(state.receivers) > 0 && q.requested
|
||||
q.requested = false
|
||||
return req
|
||||
}
|
||||
|
||||
func (q *clipboardQueue) Push(state clipboardState, e event.Event) (clipboardState, []taggedEvent) {
|
||||
var evts []taggedEvent
|
||||
for _, r := range state.receivers {
|
||||
evts = append(evts, taggedEvent{tag: r, event: e})
|
||||
}
|
||||
state.receivers = nil
|
||||
return state, evts
|
||||
}
|
||||
|
||||
func (q *clipboardQueue) ProcessWriteClipboard(req clipboard.WriteCmd) {
|
||||
defer req.Data.Close()
|
||||
content, err := io.ReadAll(req.Data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
q.mime = req.Type
|
||||
q.text = content
|
||||
}
|
||||
|
||||
func (q *clipboardQueue) ProcessReadClipboard(state clipboardState, tag event.Tag) clipboardState {
|
||||
if slices.Contains(state.receivers, tag) {
|
||||
return state
|
||||
}
|
||||
n := len(state.receivers)
|
||||
state.receivers = append(state.receivers[:n:n], tag)
|
||||
q.requested = true
|
||||
return state
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package input implements input routing and tracking of interface
|
||||
state for a window.
|
||||
|
||||
The [Source] is the interface between the window and the widgets
|
||||
of a user interface and is exposed by [gioui.org/app.FrameEvent]
|
||||
received from windows.
|
||||
|
||||
The [Router] is used by [gioui.org/app.Window] to track window state and route
|
||||
events from the platform to event handlers. It is otherwise only
|
||||
useful for using Gio with external window implementations.
|
||||
*/
|
||||
package input
|
||||
+364
@@ -0,0 +1,364 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package input
|
||||
|
||||
import (
|
||||
"image"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
)
|
||||
|
||||
// EditorState represents the state of an editor needed by input handlers.
|
||||
type EditorState struct {
|
||||
Selection struct {
|
||||
Transform f32.Affine2D
|
||||
key.Range
|
||||
key.Caret
|
||||
}
|
||||
Snippet key.Snippet
|
||||
}
|
||||
|
||||
type TextInputState uint8
|
||||
|
||||
type keyQueue struct {
|
||||
order []event.Tag
|
||||
dirOrder []dirFocusEntry
|
||||
hint key.InputHint
|
||||
}
|
||||
|
||||
// keyState is the input state related to key events.
|
||||
type keyState struct {
|
||||
focus event.Tag
|
||||
state TextInputState
|
||||
content EditorState
|
||||
}
|
||||
|
||||
type keyHandler struct {
|
||||
// visible will be true if the InputOp is present
|
||||
// in the current frame.
|
||||
visible bool
|
||||
// reset tracks whether the handler has seen a
|
||||
// focus reset.
|
||||
reset bool
|
||||
hint key.InputHint
|
||||
orderPlusOne int
|
||||
dirOrder int
|
||||
trans f32.Affine2D
|
||||
}
|
||||
|
||||
type keyFilter []key.Filter
|
||||
|
||||
type dirFocusEntry struct {
|
||||
tag event.Tag
|
||||
row int
|
||||
area int
|
||||
bounds image.Rectangle
|
||||
}
|
||||
|
||||
const (
|
||||
TextInputKeep TextInputState = iota
|
||||
TextInputClose
|
||||
TextInputOpen
|
||||
)
|
||||
|
||||
func (k *keyHandler) inputHint(hint key.InputHint) {
|
||||
k.hint = hint
|
||||
}
|
||||
|
||||
// InputState returns the input state and returns a state
|
||||
// reset to [TextInputKeep].
|
||||
func (s keyState) InputState() (keyState, TextInputState) {
|
||||
state := s.state
|
||||
s.state = TextInputKeep
|
||||
return s, state
|
||||
}
|
||||
|
||||
// InputHint returns the input hint from the focused handler and whether it was
|
||||
// changed since the last call.
|
||||
func (q *keyQueue) InputHint(handlers map[event.Tag]*handler, state keyState) (key.InputHint, bool) {
|
||||
focused, ok := handlers[state.focus]
|
||||
if !ok {
|
||||
return q.hint, false
|
||||
}
|
||||
old := q.hint
|
||||
q.hint = focused.key.hint
|
||||
return q.hint, old != q.hint
|
||||
}
|
||||
|
||||
func (k *keyHandler) Reset() {
|
||||
k.visible = false
|
||||
k.orderPlusOne = 0
|
||||
k.hint = key.HintAny
|
||||
}
|
||||
|
||||
func (q *keyQueue) Reset() {
|
||||
q.order = q.order[:0]
|
||||
q.dirOrder = q.dirOrder[:0]
|
||||
}
|
||||
|
||||
func (k *keyHandler) ResetEvent() (event.Event, bool) {
|
||||
if k.reset {
|
||||
return nil, false
|
||||
}
|
||||
k.reset = true
|
||||
return key.FocusEvent{Focus: false}, true
|
||||
}
|
||||
|
||||
func (q *keyQueue) Frame(handlers map[event.Tag]*handler, state keyState) keyState {
|
||||
if state.focus != nil {
|
||||
if h, ok := handlers[state.focus]; !ok || !h.filter.focusable || !h.key.visible {
|
||||
// Remove focus from the handler that is no longer focusable.
|
||||
state.focus = nil
|
||||
state.state = TextInputClose
|
||||
}
|
||||
}
|
||||
q.updateFocusLayout(handlers)
|
||||
return state
|
||||
}
|
||||
|
||||
// updateFocusLayout partitions input handlers handlers into rows
|
||||
// for directional focus moves.
|
||||
//
|
||||
// The approach is greedy: pick the topmost handler and create a row
|
||||
// containing it. Then, extend the handler bounds to a horizontal beam
|
||||
// and add to the row every handler whose center intersect it. Repeat
|
||||
// until no handlers remain.
|
||||
func (q *keyQueue) updateFocusLayout(handlers map[event.Tag]*handler) {
|
||||
order := q.dirOrder
|
||||
// Sort by ascending y position.
|
||||
sort.SliceStable(order, func(i, j int) bool {
|
||||
return order[i].bounds.Min.Y < order[j].bounds.Min.Y
|
||||
})
|
||||
row := 0
|
||||
for len(order) > 0 {
|
||||
h := &order[0]
|
||||
h.row = row
|
||||
bottom := h.bounds.Max.Y
|
||||
end := 1
|
||||
for ; end < len(order); end++ {
|
||||
h := &order[end]
|
||||
center := (h.bounds.Min.Y + h.bounds.Max.Y) / 2
|
||||
if center > bottom {
|
||||
break
|
||||
}
|
||||
h.row = row
|
||||
}
|
||||
// Sort row by ascending x position.
|
||||
sort.SliceStable(order[:end], func(i, j int) bool {
|
||||
return order[i].bounds.Min.X < order[j].bounds.Min.X
|
||||
})
|
||||
order = order[end:]
|
||||
row++
|
||||
}
|
||||
for i, o := range q.dirOrder {
|
||||
handlers[o.tag].key.dirOrder = i
|
||||
}
|
||||
}
|
||||
|
||||
// MoveFocus attempts to move the focus in the direction of dir.
|
||||
func (q *keyQueue) MoveFocus(handlers map[event.Tag]*handler, state keyState, dir key.FocusDirection) (keyState, []taggedEvent) {
|
||||
if len(q.dirOrder) == 0 {
|
||||
return state, nil
|
||||
}
|
||||
order := 0
|
||||
if state.focus != nil {
|
||||
order = handlers[state.focus].key.dirOrder
|
||||
}
|
||||
focus := q.dirOrder[order]
|
||||
switch dir {
|
||||
case key.FocusForward, key.FocusBackward:
|
||||
if len(q.order) == 0 {
|
||||
break
|
||||
}
|
||||
order := 0
|
||||
if dir == key.FocusBackward {
|
||||
order = -1
|
||||
}
|
||||
if state.focus != nil {
|
||||
order = handlers[state.focus].key.orderPlusOne - 1
|
||||
if dir == key.FocusForward {
|
||||
order++
|
||||
} else {
|
||||
order--
|
||||
}
|
||||
}
|
||||
order = (order + len(q.order)) % len(q.order)
|
||||
return q.Focus(handlers, state, q.order[order])
|
||||
case key.FocusRight, key.FocusLeft:
|
||||
next := order
|
||||
if state.focus != nil {
|
||||
next = order + 1
|
||||
if dir == key.FocusLeft {
|
||||
next = order - 1
|
||||
}
|
||||
}
|
||||
if 0 <= next && next < len(q.dirOrder) {
|
||||
newFocus := q.dirOrder[next]
|
||||
if newFocus.row == focus.row {
|
||||
return q.Focus(handlers, state, newFocus.tag)
|
||||
}
|
||||
}
|
||||
case key.FocusUp, key.FocusDown:
|
||||
delta := +1
|
||||
if dir == key.FocusUp {
|
||||
delta = -1
|
||||
}
|
||||
nextRow := 0
|
||||
if state.focus != nil {
|
||||
nextRow = focus.row + delta
|
||||
}
|
||||
var closest event.Tag
|
||||
dist := int(1e6)
|
||||
center := (focus.bounds.Min.X + focus.bounds.Max.X) / 2
|
||||
loop:
|
||||
for 0 <= order && order < len(q.dirOrder) {
|
||||
next := q.dirOrder[order]
|
||||
switch next.row {
|
||||
case nextRow:
|
||||
nextCenter := (next.bounds.Min.X + next.bounds.Max.X) / 2
|
||||
d := center - nextCenter
|
||||
if d < 0 {
|
||||
d = -d
|
||||
}
|
||||
if d > dist {
|
||||
break loop
|
||||
}
|
||||
dist = d
|
||||
closest = next.tag
|
||||
case nextRow + delta:
|
||||
break loop
|
||||
}
|
||||
order += delta
|
||||
}
|
||||
if closest != nil {
|
||||
return q.Focus(handlers, state, closest)
|
||||
}
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (q *keyQueue) BoundsFor(k *keyHandler) image.Rectangle {
|
||||
order := k.dirOrder
|
||||
return q.dirOrder[order].bounds
|
||||
}
|
||||
|
||||
func (q *keyQueue) AreaFor(k *keyHandler) int {
|
||||
order := k.dirOrder
|
||||
return q.dirOrder[order].area
|
||||
}
|
||||
|
||||
func (k *keyFilter) Matches(focus event.Tag, e key.Event, system bool) bool {
|
||||
for _, f := range *k {
|
||||
if keyFilterMatch(focus, f, e, system) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func keyFilterMatch(focus event.Tag, f key.Filter, e key.Event, system bool) bool {
|
||||
if f.Focus != nil && f.Focus != focus {
|
||||
return false
|
||||
}
|
||||
if (f.Name != "" || system) && f.Name != e.Name {
|
||||
return false
|
||||
}
|
||||
if e.Modifiers&f.Required != f.Required {
|
||||
return false
|
||||
}
|
||||
if e.Modifiers&^(f.Required|f.Optional) != 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (q *keyQueue) Focus(handlers map[event.Tag]*handler, state keyState, focus event.Tag) (keyState, []taggedEvent) {
|
||||
if focus == state.focus {
|
||||
return state, nil
|
||||
}
|
||||
state.content = EditorState{}
|
||||
state.content.Selection.Transform = f32.AffineId()
|
||||
var evts []taggedEvent
|
||||
if state.focus != nil {
|
||||
evts = append(evts, taggedEvent{tag: state.focus, event: key.FocusEvent{Focus: false}})
|
||||
}
|
||||
state.focus = focus
|
||||
if state.focus != nil {
|
||||
evts = append(evts, taggedEvent{tag: state.focus, event: key.FocusEvent{Focus: true}})
|
||||
}
|
||||
if state.focus == nil || state.state == TextInputKeep {
|
||||
state.state = TextInputClose
|
||||
}
|
||||
return state, evts
|
||||
}
|
||||
|
||||
func (s keyState) softKeyboard(show bool) keyState {
|
||||
if show {
|
||||
s.state = TextInputOpen
|
||||
} else {
|
||||
s.state = TextInputClose
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (k *keyFilter) Add(f key.Filter) {
|
||||
if slices.Contains(*k, f) {
|
||||
return
|
||||
}
|
||||
*k = append(*k, f)
|
||||
}
|
||||
|
||||
func (k *keyFilter) Merge(k2 keyFilter) {
|
||||
*k = append(*k, k2...)
|
||||
}
|
||||
|
||||
func (q *keyQueue) inputOp(tag event.Tag, state *keyHandler, t f32.Affine2D, area int, bounds image.Rectangle) {
|
||||
state.visible = true
|
||||
if state.orderPlusOne == 0 {
|
||||
state.orderPlusOne = len(q.order) + 1
|
||||
q.order = append(q.order, tag)
|
||||
q.dirOrder = append(q.dirOrder, dirFocusEntry{tag: tag, area: area, bounds: bounds})
|
||||
}
|
||||
state.trans = t
|
||||
}
|
||||
|
||||
func (q *keyQueue) setSelection(state keyState, req key.SelectionCmd) keyState {
|
||||
if req.Tag != state.focus {
|
||||
return state
|
||||
}
|
||||
state.content.Selection.Range = req.Range
|
||||
state.content.Selection.Caret = req.Caret
|
||||
return state
|
||||
}
|
||||
|
||||
func (q *keyQueue) editorState(handlers map[event.Tag]*handler, state keyState) EditorState {
|
||||
s := state.content
|
||||
if f := state.focus; f != nil {
|
||||
s.Selection.Transform = handlers[f].key.trans
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (q *keyQueue) setSnippet(state keyState, req key.SnippetCmd) keyState {
|
||||
if req.Tag == state.focus {
|
||||
state.content.Snippet = req.Snippet
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
func (t TextInputState) String() string {
|
||||
switch t {
|
||||
case TextInputKeep:
|
||||
return "Keep"
|
||||
case TextInputClose:
|
||||
return "Close"
|
||||
case TextInputOpen:
|
||||
return "Open"
|
||||
default:
|
||||
panic("unexpected value")
|
||||
}
|
||||
}
|
||||
+1023
File diff suppressed because it is too large
Load Diff
+900
@@ -0,0 +1,900 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package input
|
||||
|
||||
import (
|
||||
"image"
|
||||
"io"
|
||||
"slices"
|
||||
"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
|
||||
disabled bool
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Disabled returns a copy of this source that don't deliver any events.
|
||||
func (s Source) Disabled() Source {
|
||||
s2 := s
|
||||
s2.disabled = true
|
||||
return s2
|
||||
}
|
||||
|
||||
// Enabled reports whether the source is enabled. Only enabled
|
||||
// Sources deliver events.
|
||||
func (s Source) Enabled() bool {
|
||||
return s.r != nil && !s.disabled
|
||||
}
|
||||
|
||||
// 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.
|
||||
// If the source is disabled, no events will be reported.
|
||||
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 = slices.Delete(change.events, j, 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, key.CompositionEvent:
|
||||
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.CompositionEvent:
|
||||
e = key.CompositionEvent(rangeNorm(key.Range(e)))
|
||||
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 = maxPoint(topleft, bounds.Max.Sub(viewport.Max))
|
||||
topleft = minPoint(image.Pt(0, 0), topleft)
|
||||
bottomright := bounds.Max.Sub(viewport.Max)
|
||||
bottomright = minPoint(bottomright, bounds.Min.Sub(viewport.Min))
|
||||
bottomright = maxPoint(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 maxPoint(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 minPoint(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()
|
||||
t := f32.AffineId()
|
||||
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 {
|
||||
for range extra {
|
||||
q.savedTrans = append(q.savedTrans, f32.AffineId())
|
||||
}
|
||||
}
|
||||
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() {}
|
||||
Reference in New Issue
Block a user