mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 01:45:36 +00:00
all: [API] deliver key events to the first matching filter
Replace the key.Filter.Target field with a Focus field that matches only of the specified tag has focus. This has the advantage of simpler event delivery and for lower latency in delivering key events to new handlers. For example, consider a UI where a button is activated by a key press, which is turn displays a dialog with another button activated by the same key. This change allows two button press(+releases) in the same frame to arrive at the intended targets: one key press(+release) for each button. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+16
-27
@@ -49,10 +49,7 @@ type keyHandler struct {
|
|||||||
trans f32.Affine2D
|
trans f32.Affine2D
|
||||||
}
|
}
|
||||||
|
|
||||||
type keyFilter struct {
|
type keyFilter []key.Filter
|
||||||
focusable bool
|
|
||||||
filters []key.Filter
|
|
||||||
}
|
|
||||||
|
|
||||||
type dirFocusEntry struct {
|
type dirFocusEntry struct {
|
||||||
tag event.Tag
|
tag event.Tag
|
||||||
@@ -112,7 +109,7 @@ func (k *keyHandler) ResetEvent() (event.Event, bool) {
|
|||||||
|
|
||||||
func (q *keyQueue) Frame(handlers map[event.Tag]*handler, state keyState) keyState {
|
func (q *keyQueue) Frame(handlers map[event.Tag]*handler, state keyState) keyState {
|
||||||
if state.focus != nil {
|
if state.focus != nil {
|
||||||
if h, ok := handlers[state.focus]; !ok || !h.filter.key.focusable || !h.key.visible {
|
if h, ok := handlers[state.focus]; !ok || !h.filter.focusable || !h.key.visible {
|
||||||
// Remove focus from the handler that is no longer focusable.
|
// Remove focus from the handler that is no longer focusable.
|
||||||
state.focus = nil
|
state.focus = nil
|
||||||
state.state = TextInputClose
|
state.state = TextInputClose
|
||||||
@@ -253,21 +250,19 @@ func (q *keyQueue) AreaFor(k *keyHandler) int {
|
|||||||
return q.dirOrder[order].area
|
return q.dirOrder[order].area
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *keyFilter) Matches(e event.Event) bool {
|
func (k *keyFilter) Matches(focus event.Tag, e key.Event) bool {
|
||||||
switch e := e.(type) {
|
for _, f := range *k {
|
||||||
case key.Event:
|
if keyFilterMatch(focus, f, e) {
|
||||||
for _, f := range k.filters {
|
return true
|
||||||
if keyFilterMatch(f, e) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case key.FocusEvent, key.SnippetEvent, key.EditEvent, key.SelectionEvent:
|
|
||||||
return k.focusable
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyFilterMatch(f key.Filter, e key.Event) bool {
|
func keyFilterMatch(focus event.Tag, f key.Filter, e key.Event) bool {
|
||||||
|
if f.Focus != nil && f.Focus != focus {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if f.Name != e.Name {
|
if f.Name != e.Name {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -308,23 +303,17 @@ func (s keyState) softKeyboard(show bool) keyState {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *keyFilter) Add(f event.Filter) {
|
func (k *keyFilter) Add(f key.Filter) {
|
||||||
switch f := f.(type) {
|
for _, f2 := range *k {
|
||||||
case key.FocusFilter:
|
if f == f2 {
|
||||||
k.focusable = true
|
return
|
||||||
case key.Filter:
|
|
||||||
for _, f2 := range k.filters {
|
|
||||||
if f == f2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
k.filters = append(k.filters, f)
|
|
||||||
}
|
}
|
||||||
|
*k = append(*k, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *keyFilter) Merge(k2 keyFilter) {
|
func (k *keyFilter) Merge(k2 keyFilter) {
|
||||||
k.focusable = k.focusable || k2.focusable
|
*k = append(*k, k2...)
|
||||||
k.filters = append(k.filters, k2.filters...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *keyQueue) inputOp(tag event.Tag, state *keyHandler, t f32.Affine2D, area int, bounds image.Rectangle) {
|
func (q *keyQueue) inputOp(tag event.Tag, state *keyHandler, t f32.Affine2D, area int, bounds image.Rectangle) {
|
||||||
|
|||||||
@@ -69,9 +69,6 @@ func events(r *Router, h event.Tag, filters ...event.Filter) []event.Event {
|
|||||||
// Hack to facilitate transition to per-filter tags.
|
// Hack to facilitate transition to per-filter tags.
|
||||||
for i, f := range filters {
|
for i, f := range filters {
|
||||||
switch f := f.(type) {
|
switch f := f.(type) {
|
||||||
case key.Filter:
|
|
||||||
f.Target = h
|
|
||||||
filters[i] = f
|
|
||||||
case key.FocusFilter:
|
case key.FocusFilter:
|
||||||
f.Target = h
|
f.Target = h
|
||||||
filters[i] = f
|
filters[i] = f
|
||||||
|
|||||||
+66
-61
@@ -33,6 +33,12 @@ type Router struct {
|
|||||||
}
|
}
|
||||||
key struct {
|
key struct {
|
||||||
queue keyQueue
|
queue keyQueue
|
||||||
|
// The following fields have the same purpose as the fields in
|
||||||
|
// type handler, but for key.Events.
|
||||||
|
filter keyFilter
|
||||||
|
nextFilter keyFilter
|
||||||
|
processedFilter keyFilter
|
||||||
|
scratchFilter keyFilter
|
||||||
}
|
}
|
||||||
cqueue clipboardQueue
|
cqueue clipboardQueue
|
||||||
// states is the list of pending state changes resulting from
|
// states is the list of pending state changes resulting from
|
||||||
@@ -120,8 +126,8 @@ type handler struct {
|
|||||||
|
|
||||||
// filter is the union of a set of [io/event.Filters].
|
// filter is the union of a set of [io/event.Filters].
|
||||||
type filter struct {
|
type filter struct {
|
||||||
pointer pointerFilter
|
pointer pointerFilter
|
||||||
key keyFilter
|
focusable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// taggedFilter is a filter for a particular tag.
|
// taggedFilter is a filter for a particular tag.
|
||||||
@@ -183,11 +189,13 @@ func (s Source) Event(filters ...event.Filter) (event.Event, bool) {
|
|||||||
func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
|
func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
|
||||||
// Merge filters into scratch filters.
|
// Merge filters into scratch filters.
|
||||||
q.scratchFilters = q.scratchFilters[:0]
|
q.scratchFilters = q.scratchFilters[:0]
|
||||||
|
q.key.scratchFilter = q.key.scratchFilter[:0]
|
||||||
for _, f := range filters {
|
for _, f := range filters {
|
||||||
var t event.Tag
|
var t event.Tag
|
||||||
switch f := f.(type) {
|
switch f := f.(type) {
|
||||||
case key.Filter:
|
case key.Filter:
|
||||||
t = f.Target
|
q.key.scratchFilter = append(q.key.scratchFilter, f)
|
||||||
|
continue
|
||||||
case transfer.SourceFilter:
|
case transfer.SourceFilter:
|
||||||
t = f.Target
|
t = f.Target
|
||||||
case transfer.TargetFilter:
|
case transfer.TargetFilter:
|
||||||
@@ -210,8 +218,17 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
|
|||||||
}
|
}
|
||||||
if filter == nil {
|
if filter == nil {
|
||||||
n := len(q.scratchFilters)
|
n := len(q.scratchFilters)
|
||||||
q.scratchFilters = append(q.scratchFilters, taggedFilter{tag: t})
|
if n < cap(q.scratchFilters) {
|
||||||
filter = &q.scratchFilters[n].filter
|
// 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)
|
filter.Add(f)
|
||||||
}
|
}
|
||||||
@@ -220,6 +237,8 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
|
|||||||
h.filter.Merge(tf.filter)
|
h.filter.Merge(tf.filter)
|
||||||
h.nextFilter.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.
|
// Deliver reset event, if any.
|
||||||
for _, f := range filters {
|
for _, f := range filters {
|
||||||
switch f := f.(type) {
|
switch f := f.(type) {
|
||||||
@@ -245,14 +264,24 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
|
|||||||
for i := range q.changes {
|
for i := range q.changes {
|
||||||
change := &q.changes[i]
|
change := &q.changes[i]
|
||||||
for j, evt := range change.events {
|
for j, evt := range change.events {
|
||||||
for _, tf := range q.scratchFilters {
|
match := false
|
||||||
if evt.tag == tf.tag && tf.filter.Matches(evt.event) {
|
switch e := evt.event.(type) {
|
||||||
change.events = append(change.events[:j], change.events[j+1:]...)
|
case key.Event:
|
||||||
// Fast forward state to last matched.
|
match = q.key.scratchFilter.Matches(change.state.keyState.focus, e)
|
||||||
q.collapseState(i)
|
default:
|
||||||
return evt.event, true
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,6 +289,7 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
|
|||||||
h := q.stateFor(tf.tag)
|
h := q.stateFor(tf.tag)
|
||||||
h.processedFilter.Merge(tf.filter)
|
h.processedFilter.Merge(tf.filter)
|
||||||
}
|
}
|
||||||
|
q.key.processedFilter = append(q.key.processedFilter, q.key.scratchFilter...)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,6 +338,8 @@ func (q *Router) Frame(frame *op.Ops) {
|
|||||||
h.pointer.Reset()
|
h.pointer.Reset()
|
||||||
h.key.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
|
var ops *ops.Ops
|
||||||
if frame != nil {
|
if frame != nil {
|
||||||
ops = &frame.Internal
|
ops = &frame.Internal
|
||||||
@@ -348,21 +380,10 @@ func (q *Router) Queue(events ...event.Event) bool {
|
|||||||
return matched
|
return matched
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *filter) Reset() {
|
|
||||||
*f = filter{
|
|
||||||
key: keyFilter{
|
|
||||||
// Re-use filter slice storage.
|
|
||||||
filters: f.key.filters[:0],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filter) Add(flt event.Filter) {
|
func (f *filter) Add(flt event.Filter) {
|
||||||
switch flt := flt.(type) {
|
switch flt := flt.(type) {
|
||||||
case key.Filter:
|
|
||||||
f.key.Add(flt)
|
|
||||||
case key.FocusFilter:
|
case key.FocusFilter:
|
||||||
f.key.Add(flt)
|
f.focusable = true
|
||||||
case pointer.Filter:
|
case pointer.Filter:
|
||||||
f.pointer.Add(flt)
|
f.pointer.Add(flt)
|
||||||
case transfer.SourceFilter, transfer.TargetFilter:
|
case transfer.SourceFilter, transfer.TargetFilter:
|
||||||
@@ -372,12 +393,26 @@ func (f *filter) Add(flt event.Filter) {
|
|||||||
|
|
||||||
// Merge f2 into f.
|
// Merge f2 into f.
|
||||||
func (f *filter) Merge(f2 filter) {
|
func (f *filter) Merge(f2 filter) {
|
||||||
f.key.Merge(f2.key)
|
f.focusable = f.focusable || f2.focusable
|
||||||
f.pointer.Merge(f2.pointer)
|
f.pointer.Merge(f2.pointer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *filter) Matches(e event.Event) bool {
|
func (f *filter) Matches(e event.Event) bool {
|
||||||
return f.key.Matches(e) || f.pointer.Matches(e)
|
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) bool {
|
func (q *Router) processEvent(e event.Event) bool {
|
||||||
@@ -388,7 +423,11 @@ func (q *Router) processEvent(e event.Event) bool {
|
|||||||
state.pointerState = pstate
|
state.pointerState = pstate
|
||||||
return q.changeState(e, state, evts)
|
return q.changeState(e, state, evts)
|
||||||
case key.Event:
|
case key.Event:
|
||||||
return q.changeState(e, state, q.queueKeyEvent(state.keyState, e))
|
var evts []taggedEvent
|
||||||
|
if q.key.filter.Matches(state.keyState.focus, e) {
|
||||||
|
evts = append(evts, taggedEvent{event: e})
|
||||||
|
}
|
||||||
|
return q.changeState(e, state, evts)
|
||||||
case key.SnippetEvent:
|
case key.SnippetEvent:
|
||||||
// Expand existing, overlapping snippet.
|
// Expand existing, overlapping snippet.
|
||||||
if r := state.content.Snippet.Range; rangeOverlaps(r, key.Range(e)) {
|
if r := state.content.Snippet.Range; rangeOverlaps(r, key.Range(e)) {
|
||||||
@@ -549,40 +588,6 @@ func rangeNorm(r key.Range) key.Range {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Router) queueKeyEvent(state keyState, e key.Event) []taggedEvent {
|
|
||||||
f := state.focus
|
|
||||||
var evts []taggedEvent
|
|
||||||
if f != nil && q.handlers[f].filter.key.Matches(e) {
|
|
||||||
evts = append(evts, taggedEvent{tag: f, event: e})
|
|
||||||
return evts
|
|
||||||
}
|
|
||||||
pq := &q.pointer.queue
|
|
||||||
idx := len(pq.hitTree) - 1
|
|
||||||
focused := f != nil
|
|
||||||
if focused {
|
|
||||||
// If there is a focused tag, traverse its ancestry through the
|
|
||||||
// hit tree to search for handlers.
|
|
||||||
for ; pq.hitTree[idx].tag != f; idx-- {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for idx != -1 {
|
|
||||||
n := &pq.hitTree[idx]
|
|
||||||
if focused {
|
|
||||||
idx = n.next
|
|
||||||
} else {
|
|
||||||
idx--
|
|
||||||
}
|
|
||||||
if n.tag == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if q.handlers[n.tag].filter.key.Matches(e) {
|
|
||||||
evts = append(evts, taggedEvent{tag: n.tag, event: e})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return evts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Router) MoveFocus(dir key.FocusDirection) bool {
|
func (q *Router) MoveFocus(dir key.FocusDirection) bool {
|
||||||
state := q.lastState()
|
state := q.lastState()
|
||||||
kstate, evts := q.key.queue.MoveFocus(q.handlers, state.keyState, dir)
|
kstate, evts := q.key.queue.MoveFocus(q.handlers, state.keyState, dir)
|
||||||
@@ -795,7 +800,7 @@ func (q *Router) collect() {
|
|||||||
pc.inputOp(tag, &s.pointer)
|
pc.inputOp(tag, &s.pointer)
|
||||||
a := pc.currentArea()
|
a := pc.currentArea()
|
||||||
b := pc.currentAreaBounds()
|
b := pc.currentAreaBounds()
|
||||||
if s.filter.key.focusable {
|
if s.filter.focusable {
|
||||||
kq.inputOp(tag, &s.key, t, a, b)
|
kq.inputOp(tag, &s.key, t, a, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-8
@@ -1,12 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
/*
|
// Package key implements key and text events and operations.
|
||||||
Package key implements key and text events and operations.
|
|
||||||
|
|
||||||
The InputOp operations is used for declaring key input handlers. Use
|
|
||||||
an implementation of the Queue interface from package ui to receive
|
|
||||||
events.
|
|
||||||
*/
|
|
||||||
package key
|
package key
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -20,7 +14,9 @@ import (
|
|||||||
|
|
||||||
// Filter matches any [Event] that matches the parameters.
|
// Filter matches any [Event] that matches the parameters.
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
Target event.Tag
|
// Focus is the tag that must be focused for the filter to match. It has no effect
|
||||||
|
// if it is nil.
|
||||||
|
Focus event.Tag
|
||||||
// Required is the set of modifiers that must be included in events matched.
|
// Required is the set of modifiers that must be included in events matched.
|
||||||
Required Modifiers
|
Required Modifiers
|
||||||
// Optional is the set of modifiers that may be included in events matched.
|
// Optional is the set of modifiers that may be included in events matched.
|
||||||
|
|||||||
+5
-10
@@ -147,17 +147,12 @@ func (b *Clickable) Update(gtx layout.Context) (Click, bool) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filters := []event.Filter{
|
|
||||||
key.FocusFilter{Target: b},
|
|
||||||
}
|
|
||||||
if b.focused {
|
|
||||||
filters = append(filters,
|
|
||||||
key.Filter{Target: b, Name: key.NameReturn},
|
|
||||||
key.Filter{Target: b, Name: key.NameSpace},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
for {
|
for {
|
||||||
e, ok := gtx.Event(filters...)
|
e, ok := gtx.Event(
|
||||||
|
key.FocusFilter{Target: b},
|
||||||
|
key.Filter{Focus: b, Name: key.NameReturn},
|
||||||
|
key.Filter{Focus: b, Name: key.NameSpace},
|
||||||
|
)
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-44
@@ -333,57 +333,47 @@ func (e *Editor) clickDragEvents(gtx layout.Context) []event.Event {
|
|||||||
return combinedEvents
|
return combinedEvents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func condFilter(pred bool, f key.Filter) event.Filter {
|
||||||
|
if pred {
|
||||||
|
return f
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Editor) processKey(gtx layout.Context) {
|
func (e *Editor) processKey(gtx layout.Context) {
|
||||||
if e.text.Changed() {
|
if e.text.Changed() {
|
||||||
e.events = append(e.events, ChangeEvent{})
|
e.events = append(e.events, ChangeEvent{})
|
||||||
}
|
}
|
||||||
filters := []event.Filter{key.FocusFilter{Target: e}, transfer.TargetFilter{Target: e, Type: "application/text"}}
|
caret, _ := e.text.Selection()
|
||||||
if e.focused {
|
atBeginning := caret == 0
|
||||||
filters = append(filters,
|
atEnd := caret == e.text.Len()
|
||||||
key.Filter{Target: e, Name: key.NameEnter, Optional: key.ModShift},
|
if gtx.Locale.Direction.Progression() != system.FromOrigin {
|
||||||
key.Filter{Target: e, Name: key.NameReturn, Optional: key.ModShift},
|
atEnd, atBeginning = atBeginning, atEnd
|
||||||
|
}
|
||||||
|
filters := []event.Filter{
|
||||||
|
key.FocusFilter{Target: e},
|
||||||
|
transfer.TargetFilter{Target: e, Type: "application/text"},
|
||||||
|
key.Filter{Focus: e, Name: key.NameEnter, Optional: key.ModShift},
|
||||||
|
key.Filter{Focus: e, Name: key.NameReturn, Optional: key.ModShift},
|
||||||
|
|
||||||
key.Filter{Target: e, Name: "Z", Required: key.ModShortcut, Optional: key.ModShift},
|
key.Filter{Focus: e, Name: "Z", Required: key.ModShortcut, Optional: key.ModShift},
|
||||||
key.Filter{Target: e, Name: "C", Required: key.ModShortcut},
|
key.Filter{Focus: e, Name: "C", Required: key.ModShortcut},
|
||||||
key.Filter{Target: e, Name: "V", Required: key.ModShortcut},
|
key.Filter{Focus: e, Name: "V", Required: key.ModShortcut},
|
||||||
key.Filter{Target: e, Name: "X", Required: key.ModShortcut},
|
key.Filter{Focus: e, Name: "X", Required: key.ModShortcut},
|
||||||
key.Filter{Target: e, Name: "A", Required: key.ModShortcut},
|
key.Filter{Focus: e, Name: "A", Required: key.ModShortcut},
|
||||||
|
|
||||||
key.Filter{Target: e, Name: key.NameDeleteBackward, Optional: key.ModShortcutAlt | key.ModShift},
|
key.Filter{Focus: e, Name: key.NameDeleteBackward, Optional: key.ModShortcutAlt | key.ModShift},
|
||||||
key.Filter{Target: e, Name: key.NameDeleteForward, Optional: key.ModShortcutAlt | key.ModShift},
|
key.Filter{Focus: e, Name: key.NameDeleteForward, Optional: key.ModShortcutAlt | key.ModShift},
|
||||||
|
|
||||||
key.Filter{Target: e, Name: key.NameHome, Optional: key.ModShift},
|
key.Filter{Focus: e, Name: key.NameHome, Optional: key.ModShift},
|
||||||
key.Filter{Target: e, Name: key.NameEnd, Optional: key.ModShift},
|
key.Filter{Focus: e, Name: key.NameEnd, Optional: key.ModShift},
|
||||||
key.Filter{Target: e, Name: key.NamePageDown, Optional: key.ModShift},
|
key.Filter{Focus: e, Name: key.NamePageDown, Optional: key.ModShift},
|
||||||
key.Filter{Target: e, Name: key.NamePageUp, Optional: key.ModShift},
|
key.Filter{Focus: e, Name: key.NamePageUp, Optional: key.ModShift},
|
||||||
)
|
condFilter(!atBeginning, key.Filter{Focus: e, Name: key.NameLeftArrow, Optional: key.ModShortcutAlt | key.ModShift}),
|
||||||
caret, _ := e.text.Selection()
|
condFilter(!atBeginning, key.Filter{Focus: e, Name: key.NameUpArrow, Optional: key.ModShortcutAlt | key.ModShift}),
|
||||||
if caret > 0 {
|
condFilter(!atEnd, key.Filter{Focus: e, Name: key.NameRightArrow, Optional: key.ModShortcutAlt | key.ModShift}),
|
||||||
if gtx.Locale.Direction.Progression() == system.FromOrigin {
|
condFilter(!atEnd, key.Filter{Focus: e, Name: key.NameDownArrow, Optional: key.ModShortcutAlt | key.ModShift}),
|
||||||
filters = append(filters,
|
|
||||||
key.Filter{Target: e, Name: key.NameLeftArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
|
||||||
key.Filter{Target: e, Name: key.NameUpArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
filters = append(filters,
|
|
||||||
key.Filter{Target: e, Name: key.NameRightArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
|
||||||
key.Filter{Target: e, Name: key.NameDownArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if caret < e.text.Len() {
|
|
||||||
if gtx.Locale.Direction.Progression() == system.FromOrigin {
|
|
||||||
filters = append(filters,
|
|
||||||
key.Filter{Target: e, Name: key.NameRightArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
|
||||||
key.Filter{Target: e, Name: key.NameDownArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
filters = append(filters,
|
|
||||||
key.Filter{Target: e, Name: key.NameLeftArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
|
||||||
key.Filter{Target: e, Name: key.NameUpArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// adjust keeps track of runes dropped because of MaxLen.
|
// adjust keeps track of runes dropped because of MaxLen.
|
||||||
var adjust int
|
var adjust int
|
||||||
|
|||||||
@@ -1177,6 +1177,29 @@ func TestEditor_Submit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoFilterAllocs(t *testing.T) {
|
||||||
|
b := testing.Benchmark(func(b *testing.B) {
|
||||||
|
r := new(input.Router)
|
||||||
|
e := new(Editor)
|
||||||
|
gtx := layout.Context{
|
||||||
|
Ops: new(op.Ops),
|
||||||
|
Constraints: layout.Constraints{
|
||||||
|
Max: image.Pt(100, 100),
|
||||||
|
},
|
||||||
|
Locale: english,
|
||||||
|
Source: r.Source(),
|
||||||
|
}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
e.Update(gtx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if allocs := b.AllocsPerOp(); allocs != 0 {
|
||||||
|
t.Fatalf("expected 0 AllocsPerOp, got %d", allocs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// textWidth is a text helper for building simple selection events.
|
// textWidth is a text helper for building simple selection events.
|
||||||
// It assumes single-run lines, which isn't safe with non-test text
|
// It assumes single-run lines, which isn't safe with non-test text
|
||||||
// data.
|
// data.
|
||||||
|
|||||||
+5
-10
@@ -64,17 +64,12 @@ func (e *Enum) Update(gtx layout.Context) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filters := []event.Filter{
|
|
||||||
key.FocusFilter{Target: &state.tag},
|
|
||||||
}
|
|
||||||
if e.focused && e.focus == state.key {
|
|
||||||
filters = append(filters,
|
|
||||||
key.Filter{Target: &state.tag, Name: key.NameReturn},
|
|
||||||
key.Filter{Target: &state.tag, Name: key.NameSpace},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
for {
|
for {
|
||||||
ev, ok := gtx.Event(filters...)
|
ev, ok := gtx.Event(
|
||||||
|
key.FocusFilter{Target: &state.tag},
|
||||||
|
key.Filter{Focus: &state.tag, Name: key.NameReturn},
|
||||||
|
key.Filter{Focus: &state.tag, Name: key.NameSpace},
|
||||||
|
)
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-21
@@ -301,28 +301,23 @@ func (e *Selectable) clickDragEvents(gtx layout.Context) []event.Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Selectable) processKey(gtx layout.Context) {
|
func (e *Selectable) processKey(gtx layout.Context) {
|
||||||
filters := []event.Filter{
|
|
||||||
key.FocusFilter{Target: e},
|
|
||||||
}
|
|
||||||
if e.focused {
|
|
||||||
filters = append(filters,
|
|
||||||
key.Filter{Target: e, Name: key.NameLeftArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
|
||||||
key.Filter{Target: e, Name: key.NameRightArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
|
||||||
key.Filter{Target: e, Name: key.NameUpArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
|
||||||
key.Filter{Target: e, Name: key.NameDownArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
|
||||||
|
|
||||||
key.Filter{Target: e, Name: key.NamePageUp, Optional: key.ModShift},
|
|
||||||
key.Filter{Target: e, Name: key.NamePageDown, Optional: key.ModShift},
|
|
||||||
key.Filter{Target: e, Name: key.NameEnd, Optional: key.ModShift},
|
|
||||||
key.Filter{Target: e, Name: key.NameHome, Optional: key.ModShift},
|
|
||||||
|
|
||||||
key.Filter{Target: e, Name: "C", Required: key.ModShortcut},
|
|
||||||
key.Filter{Target: e, Name: "X", Required: key.ModShortcut},
|
|
||||||
key.Filter{Target: e, Name: "A", Required: key.ModShortcut},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
for {
|
for {
|
||||||
ke, ok := gtx.Event(filters...)
|
ke, ok := gtx.Event(
|
||||||
|
key.FocusFilter{Target: e},
|
||||||
|
key.Filter{Focus: e, Name: key.NameLeftArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
||||||
|
key.Filter{Focus: e, Name: key.NameRightArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
||||||
|
key.Filter{Focus: e, Name: key.NameUpArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
||||||
|
key.Filter{Focus: e, Name: key.NameDownArrow, Optional: key.ModShortcutAlt | key.ModShift},
|
||||||
|
|
||||||
|
key.Filter{Focus: e, Name: key.NamePageUp, Optional: key.ModShift},
|
||||||
|
key.Filter{Focus: e, Name: key.NamePageDown, Optional: key.ModShift},
|
||||||
|
key.Filter{Focus: e, Name: key.NameEnd, Optional: key.ModShift},
|
||||||
|
key.Filter{Focus: e, Name: key.NameHome, Optional: key.ModShift},
|
||||||
|
|
||||||
|
key.Filter{Focus: e, Name: "C", Required: key.ModShortcut},
|
||||||
|
key.Filter{Focus: e, Name: "X", Required: key.ModShortcut},
|
||||||
|
key.Filter{Focus: e, Name: "A", Required: key.ModShortcut},
|
||||||
|
)
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user