all: [API] replace tag parameter of Source.Event with per-filter tags

Until now, every event has had a particular target. We're about to simplify
key event delivery to match the first matching filter, so there is no
longer a global meaning to the tag argument to Source.Event.

Add fields to filters to specify their target tags.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2023-11-26 12:58:10 -06:00
parent 496fc3cc82
commit d9a007586c
16 changed files with 195 additions and 115 deletions
+5 -4
View File
@@ -44,7 +44,7 @@ func (h *Hover) Add(ops *op.Ops) {
// Update state and report whether a pointer is inside the area. // Update state and report whether a pointer is inside the area.
func (h *Hover) Update(q input.Source) bool { func (h *Hover) Update(q input.Source) bool {
for { for {
ev, ok := q.Event(h, pointer.Filter{Kinds: pointer.Enter | pointer.Leave}) ev, ok := q.Event(pointer.Filter{Target: h, Kinds: pointer.Enter | pointer.Leave})
if !ok { if !ok {
break break
} }
@@ -176,7 +176,7 @@ func (c *Click) Pressed() bool {
// Update state and return the next click events, if any. // Update state and return the next click events, if any.
func (c *Click) Update(q input.Source) (ClickEvent, bool) { func (c *Click) Update(q input.Source) (ClickEvent, bool) {
for { for {
evt, ok := q.Event(c, pointer.Filter{Kinds: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave}) evt, ok := q.Event(pointer.Filter{Target: c, Kinds: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave})
if !ok { if !ok {
break break
} }
@@ -268,11 +268,12 @@ func (s *Scroll) Stop() {
func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, bounds image.Rectangle) int { func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, bounds image.Rectangle) int {
total := 0 total := 0
f := pointer.Filter{ f := pointer.Filter{
Target: s,
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll, Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll,
ScrollBounds: bounds, ScrollBounds: bounds,
} }
for { for {
evt, ok := q.Event(s, f) evt, ok := q.Event(f)
if !ok { if !ok {
break break
} }
@@ -372,7 +373,7 @@ func (d *Drag) Add(ops *op.Ops) {
// Update state and return the next drag event, if any. // Update state and return the next drag event, if any.
func (d *Drag) Update(cfg unit.Metric, q input.Source, axis Axis) (pointer.Event, bool) { func (d *Drag) Update(cfg unit.Metric, q input.Source, axis Axis) (pointer.Event, bool) {
for { for {
ev, ok := q.Event(d, pointer.Filter{Kinds: pointer.Press | pointer.Drag | pointer.Release}) ev, ok := q.Event(pointer.Filter{Target: d, Kinds: pointer.Press | pointer.Drag | pointer.Release})
if !ok { if !ok {
break break
} }
+3 -1
View File
@@ -294,6 +294,7 @@ func TestFocusScroll(t *testing.T) {
filters := []event.Filter{ filters := []event.Filter{
key.FocusFilter{}, key.FocusFilter{},
pointer.Filter{ pointer.Filter{
Target: h,
Kinds: pointer.Scroll, Kinds: pointer.Scroll,
ScrollBounds: image.Rect(-100, -100, 100, 100), ScrollBounds: image.Rect(-100, -100, 100, 100),
}, },
@@ -322,7 +323,8 @@ func TestFocusClick(t *testing.T) {
filters := []event.Filter{ filters := []event.Filter{
key.FocusFilter{}, key.FocusFilter{},
pointer.Filter{ pointer.Filter{
Kinds: pointer.Press | pointer.Release, Target: h,
Kinds: pointer.Press | pointer.Release,
}, },
} }
assertEventPointerTypeSequence(t, events(r, h, filters...), pointer.Cancel) assertEventPointerTypeSequence(t, events(r, h, filters...), pointer.Cancel)
+32 -1
View File
@@ -19,6 +19,17 @@ import (
"gioui.org/op/clip" "gioui.org/op/clip"
) )
func TestPointerNilTarget(t *testing.T) {
r := new(Router)
r.Event(pointer.Filter{Kinds: pointer.Press})
r.Frame(new(op.Ops))
r.Queue(pointer.Event{Kind: pointer.Press})
// Nil Targets should not receive events.
if _, ok := r.Event(pointer.Filter{Kinds: pointer.Press}); ok {
t.Errorf("nil target received event")
}
}
func TestPointerWakeup(t *testing.T) { func TestPointerWakeup(t *testing.T) {
handler := new(int) handler := new(int)
var ops op.Ops var ops op.Ops
@@ -55,9 +66,29 @@ func TestPointerDrag(t *testing.T) {
} }
func events(r *Router, h event.Tag, filters ...event.Filter) []event.Event { func events(r *Router, h event.Tag, filters ...event.Filter) []event.Event {
// Hack to facilitate transition to per-filter tags.
for i, f := range filters {
switch f := f.(type) {
case key.Filter:
f.Target = h
filters[i] = f
case key.FocusFilter:
f.Target = h
filters[i] = f
case transfer.SourceFilter:
f.Target = h
filters[i] = f
case transfer.TargetFilter:
f.Target = h
filters[i] = f
case pointer.Filter:
f.Target = h
filters[i] = f
}
}
var events []event.Event var events []event.Event
for { for {
e, ok := r.Event(h, filters...) e, ok := r.Event(filters...)
if !ok { if !ok {
break break
} }
+77 -32
View File
@@ -35,30 +35,23 @@ type Router struct {
queue keyQueue queue keyQueue
} }
cqueue clipboardQueue cqueue clipboardQueue
// states is the list of pending state changes resulting from // states is the list of pending state changes resulting from
// incoming events. The first element, if present, contains the state // incoming events. The first element, if present, contains the state
// and events for the current frame. // and events for the current frame.
changes []stateChange changes []stateChange
reader ops.Reader
reader ops.Reader
// InvalidateCmd summary. // InvalidateCmd summary.
wakeup bool wakeup bool
wakeupTime time.Time wakeupTime time.Time
// Changes queued for next call to Frame. // Changes queued for next call to Frame.
commands []Command commands []Command
// transfers is the pending transfer.DataEvent.Open functions. // transfers is the pending transfer.DataEvent.Open functions.
transfers []io.ReadCloser transfers []io.ReadCloser
// deferring is set if command execution and event delivery is deferred // deferring is set if command execution and event delivery is deferred
// to the next frame. // to the next frame.
deferring bool deferring bool
// scratchFilters is for garbage-free construction of ephemeral filters.
// scratchFilter is for garbage-free construction of ephemeral filters. scratchFilters []taggedFilter
scratchFilter filter
} }
// Source implements the interface between a Router and user interface widgets. // Source implements the interface between a Router and user interface widgets.
@@ -131,6 +124,12 @@ type filter struct {
key keyFilter key keyFilter
} }
// taggedFilter is a filter for a particular tag.
type taggedFilter struct {
tag event.Tag
filter filter
}
// stateChange represents the new state and outgoing events // stateChange represents the new state and outgoing events
// resulting from an incoming event. // resulting from an incoming event.
type stateChange struct { type stateChange struct {
@@ -173,51 +172,94 @@ func (s Source) Enabled() bool {
return s.r != nil return s.r != nil
} }
// Event returns the next event for the handler tag that matches one // Event returns the next event that matches at least one of filters.
// or more of filters. func (s Source) Event(filters ...event.Filter) (event.Event, bool) {
func (s Source) Event(k event.Tag, filters ...event.Filter) (event.Event, bool) {
if !s.Enabled() { if !s.Enabled() {
return nil, false return nil, false
} }
return s.r.Event(k, filters...) return s.r.Event(filters...)
} }
func (q *Router) Event(k event.Tag, filters ...event.Filter) (event.Event, bool) { func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
h := q.stateFor(k) // Merge filters into scratch filters.
q.scratchFilter.Reset() q.scratchFilters = q.scratchFilters[:0]
// Record handler filters and add reset events.
for _, f := range filters { for _, f := range filters {
q.scratchFilter.Add(f) var t event.Tag
switch f.(type) { switch f := f.(type) {
case key.Filter:
t = f.Target
case transfer.SourceFilter:
t = f.Target
case transfer.TargetFilter:
t = f.Target
case key.FocusFilter: 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)
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)
}
// 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 { if reset, ok := h.key.ResetEvent(); ok {
return reset, true return reset, true
} }
case pointer.Filter: case pointer.Filter:
if f.Target == nil {
break
}
h := q.stateFor(f.Target)
if reset, ok := h.pointer.ResetEvent(); ok { if reset, ok := h.pointer.ResetEvent(); ok {
return reset, true return reset, true
} }
} }
} }
h.nextFilter.Merge(q.scratchFilter)
if !q.deferring { if !q.deferring {
for i := range q.changes { for i := range q.changes {
change := &q.changes[i] change := &q.changes[i]
j := 0 for j, evt := range change.events {
for j < len(change.events) { for _, tf := range q.scratchFilters {
evt := change.events[j] if evt.tag == tf.tag && tf.filter.Matches(evt.event) {
if evt.tag != k || !q.scratchFilter.Matches(evt.event) { change.events = append(change.events[:j], change.events[j+1:]...)
j++ // Fast forward state to last matched.
continue q.collapseState(i)
return evt.event, true
}
} }
change.events = append(change.events[:j], change.events[j+1:]...)
// Fast forward state to last matched.
q.collapseState(i)
return evt.event, true
} }
} }
} }
h.processedFilter.Merge(q.scratchFilter) for _, tf := range q.scratchFilters {
h := q.stateFor(tf.tag)
h.processedFilter.Merge(tf.filter)
}
return nil, false return nil, false
} }
@@ -691,6 +733,9 @@ func (q *Router) EditorState() EditorState {
} }
func (q *Router) stateFor(tag event.Tag) *handler { func (q *Router) stateFor(tag event.Tag) *handler {
if tag == nil {
panic("internal error: nil tag")
}
s, ok := q.handlers[tag] s, ok := q.handlers[tag]
if !ok { if !ok {
s = new(handler) s = new(handler)
+1 -1
View File
@@ -16,7 +16,7 @@ func TestNoFilterAllocs(t *testing.T) {
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
s.Event(nil, pointer.Filter{}) s.Event(pointer.Filter{})
} }
}) })
if allocs := b.AllocsPerOp(); allocs != 0 { if allocs := b.AllocsPerOp(); allocs != 0 {
+8 -3
View File
@@ -18,8 +18,9 @@ import (
"gioui.org/op" "gioui.org/op"
) )
// Filter matches [Event]s. // Filter matches any [Event] that matches the parameters.
type Filter struct { type Filter struct {
Target 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.
@@ -108,8 +109,12 @@ type EditEvent struct {
Text string Text string
} }
// FocusFilter matches [FocusEvent]s. // FocusFilter matches any [FocusEvent], [EditEvent], [SnippetEvent],
type FocusFilter struct{} // or [SelectionEvent] with the specified target.
type FocusFilter struct {
// Target is a tag specified in a previous event.Op.
Target event.Tag
}
// InputHint changes the on-screen-keyboard type. That hints the // InputHint changes the on-screen-keyboard type. That hints the
// type of data that might be entered by the user. // type of data that might be entered by the user.
+7 -24
View File
@@ -5,36 +5,19 @@ Package pointer implements pointer events and operations.
A pointer is either a mouse controlled cursor or a touch A pointer is either a mouse controlled cursor or a touch
object such as a finger. object such as a finger.
The InputOp operation is used to declare a handler ready for pointer The [event.Op] operation is used to declare a handler ready for pointer
events. Use an event.Queue to receive events. events.
# Kinds
Only events that match a specified list of types are delivered to a handler.
For example, to receive Press, Drag, and Release events (but not Move, Enter,
Leave, or Scroll):
var ops op.Ops
var h *Handler = ...
pointer.InputOp{
Tag: h,
Kinds: pointer.Press | pointer.Drag | pointer.Release,
}.Add(ops)
Cancel events are always delivered.
# Hit areas # Hit areas
Clip operations from package op/clip are used for specifying Clip operations from package [op/clip] are used for specifying
hit areas where subsequent InputOps are active. hit areas where handlers may receive events.
For example, to set up a handler with a rectangular hit area: For example, to set up a handler with a rectangular hit area:
r := image.Rectangle{...} r := image.Rectangle{...}
area := clip.Rect(r).Push(ops) area := clip.Rect(r).Push(ops)
pointer.InputOp{Tag: h}.Add(ops) event.Op{Tag: h}.Add(ops)
area.Pop() area.Pop()
Note that hit areas behave similar to painting: the effective area of a stack Note that hit areas behave similar to painting: the effective area of a stack
@@ -54,11 +37,11 @@ For example:
var h1, h2 *Handler var h1, h2 *Handler
area := clip.Rect(...).Push(ops) area := clip.Rect(...).Push(ops)
pointer.InputOp{Tag: h1}.Add(Ops) event.Op{Tag: h1}.Add(Ops)
area.Pop() area.Pop()
area := clip.Rect(...).Push(ops) area := clip.Rect(...).Push(ops)
pointer.InputOp{Tag: h2}.Add(ops) event.Op{Tag: h2}.Add(ops)
area.Pop() area.Pop()
implies a tree of two inner nodes, each with one pointer handler attached. implies a tree of two inner nodes, each with one pointer handler attached.
+4 -1
View File
@@ -54,8 +54,11 @@ type PassStack struct {
macroID uint32 macroID uint32
} }
// Filter matches [Event]s. // Filter matches every [Event] that target the Tag and whose kind is
// included in Kinds. Note that only tags specified in [event.Op] can
// be targeted by pointer events.
type Filter struct { type Filter struct {
Target event.Tag
// Kinds is a bitwise-or of event types to match. // Kinds is a bitwise-or of event types to match.
Kinds Kind Kinds Kind
// ScrollBounds describe the maximum scrollable distances in both // ScrollBounds describe the maximum scrollable distances in both
+4
View File
@@ -42,6 +42,8 @@ func (OfferCmd) ImplementsCommand() {}
// as well as [InitiateEvent] and [CancelEvent]. // as well as [InitiateEvent] and [CancelEvent].
// Use multiple filters to offer multiple types. // Use multiple filters to offer multiple types.
type SourceFilter struct { type SourceFilter struct {
// Target is a tag included in a previous event.Op.
Target event.Tag
// Type is the MIME type supported by this source. // Type is the MIME type supported by this source.
Type string Type string
} }
@@ -49,6 +51,8 @@ type SourceFilter struct {
// TargetFilter filters for any [DataEvent] whose type matches a MIME type // TargetFilter filters for any [DataEvent] whose type matches a MIME type
// as well as [CancelEvent]. Use multiple filters to accept multiple types. // as well as [CancelEvent]. Use multiple filters to accept multiple types.
type TargetFilter struct { type TargetFilter struct {
// Target is a tag included in a previous event.Op.
Target event.Tag
// Type is the MIME type accepted by this target. // Type is the MIME type accepted by this target.
Type string Type string
} }
+6 -3
View File
@@ -148,13 +148,16 @@ func (b *Clickable) Update(gtx layout.Context) (Click, bool) {
} }
} }
filters := []event.Filter{ filters := []event.Filter{
key.FocusFilter{}, key.FocusFilter{Target: b},
} }
if b.focused { if b.focused {
filters = append(filters, key.Filter{Name: key.NameReturn}, key.Filter{Name: key.NameSpace}) filters = append(filters,
key.Filter{Target: b, Name: key.NameReturn},
key.Filter{Target: b, Name: key.NameSpace},
)
} }
for { for {
e, ok := gtx.Event(b, filters...) e, ok := gtx.Event(filters...)
if !ok { if !ok {
break break
} }
+1 -1
View File
@@ -69,7 +69,7 @@ func (d *Draggable) Update(gtx layout.Context) (mime string, requested bool) {
d.pos = pos d.pos = pos
for { for {
e, ok := gtx.Event(d, transfer.SourceFilter{Type: d.Type}) e, ok := gtx.Event(transfer.SourceFilter{Target: d, Type: d.Type})
if !ok { if !ok {
break break
} }
+4 -4
View File
@@ -35,7 +35,7 @@ func TestDraggable(t *testing.T) {
stack.Pop() stack.Pop()
drag.Update(gtx) drag.Update(gtx)
r.Event(tgt, transfer.TargetFilter{Type: drag.Type}) r.Event(transfer.TargetFilter{Target: tgt, Type: drag.Type})
r.Frame(gtx.Ops) r.Frame(gtx.Ops)
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
@@ -53,10 +53,10 @@ func TestDraggable(t *testing.T) {
) )
ofr := &offer{data: "hello"} ofr := &offer{data: "hello"}
drag.Update(gtx) drag.Update(gtx)
r.Event(tgt, transfer.TargetFilter{Type: drag.Type}) r.Event(transfer.TargetFilter{Target: tgt, Type: drag.Type})
drag.Offer(gtx, "file", ofr) drag.Offer(gtx, "file", ofr)
e, ok := r.Event(tgt, transfer.TargetFilter{Type: drag.Type}) e, ok := r.Event(transfer.TargetFilter{Target: tgt, Type: drag.Type})
if !ok { if !ok {
t.Fatalf("expected event") t.Fatalf("expected event")
} }
@@ -67,7 +67,7 @@ func TestDraggable(t *testing.T) {
if ofr.closed { if ofr.closed {
t.Error("offer closed prematurely") t.Error("offer closed prematurely")
} }
e, ok = r.Event(tgt, transfer.TargetFilter{Type: drag.Type}) e, ok = r.Event(transfer.TargetFilter{Target: tgt, Type: drag.Type})
if !ok { if !ok {
t.Fatalf("expected event") t.Fatalf("expected event")
} }
+23 -23
View File
@@ -337,50 +337,50 @@ 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{}, transfer.TargetFilter{Type: "application/text"}} filters := []event.Filter{key.FocusFilter{Target: e}, transfer.TargetFilter{Target: e, Type: "application/text"}}
if e.focused { if e.focused {
filters = append(filters, filters = append(filters,
key.Filter{Name: key.NameEnter, Optional: key.ModShift}, key.Filter{Target: e, Name: key.NameEnter, Optional: key.ModShift},
key.Filter{Name: key.NameReturn, Optional: key.ModShift}, key.Filter{Target: e, Name: key.NameReturn, Optional: key.ModShift},
key.Filter{Name: "Z", Required: key.ModShortcut, Optional: key.ModShift}, key.Filter{Target: e, Name: "Z", Required: key.ModShortcut, Optional: key.ModShift},
key.Filter{Name: "C", Required: key.ModShortcut}, key.Filter{Target: e, Name: "C", Required: key.ModShortcut},
key.Filter{Name: "V", Required: key.ModShortcut}, key.Filter{Target: e, Name: "V", Required: key.ModShortcut},
key.Filter{Name: "X", Required: key.ModShortcut}, key.Filter{Target: e, Name: "X", Required: key.ModShortcut},
key.Filter{Name: "A", Required: key.ModShortcut}, key.Filter{Target: e, Name: "A", Required: key.ModShortcut},
key.Filter{Name: key.NameDeleteBackward, Optional: key.ModShortcutAlt | key.ModShift}, key.Filter{Target: e, Name: key.NameDeleteBackward, Optional: key.ModShortcutAlt | key.ModShift},
key.Filter{Name: key.NameDeleteForward, Optional: key.ModShortcutAlt | key.ModShift}, key.Filter{Target: e, Name: key.NameDeleteForward, Optional: key.ModShortcutAlt | key.ModShift},
key.Filter{Name: key.NameHome, Optional: key.ModShift}, key.Filter{Target: e, Name: key.NameHome, Optional: key.ModShift},
key.Filter{Name: key.NameEnd, Optional: key.ModShift}, key.Filter{Target: e, Name: key.NameEnd, Optional: key.ModShift},
key.Filter{Name: key.NamePageDown, Optional: key.ModShift}, key.Filter{Target: e, Name: key.NamePageDown, Optional: key.ModShift},
key.Filter{Name: key.NamePageUp, Optional: key.ModShift}, key.Filter{Target: e, Name: key.NamePageUp, Optional: key.ModShift},
) )
caret, _ := e.text.Selection() caret, _ := e.text.Selection()
if caret > 0 { if caret > 0 {
if gtx.Locale.Direction.Progression() == system.FromOrigin { if gtx.Locale.Direction.Progression() == system.FromOrigin {
filters = append(filters, filters = append(filters,
key.Filter{Name: key.NameLeftArrow, Optional: key.ModShortcutAlt | key.ModShift}, key.Filter{Target: e, Name: key.NameLeftArrow, Optional: key.ModShortcutAlt | key.ModShift},
key.Filter{Name: key.NameUpArrow, Optional: key.ModShortcutAlt | key.ModShift}, key.Filter{Target: e, Name: key.NameUpArrow, Optional: key.ModShortcutAlt | key.ModShift},
) )
} else { } else {
filters = append(filters, filters = append(filters,
key.Filter{Name: key.NameRightArrow, Optional: key.ModShortcutAlt | key.ModShift}, key.Filter{Target: e, Name: key.NameRightArrow, Optional: key.ModShortcutAlt | key.ModShift},
key.Filter{Name: key.NameDownArrow, Optional: key.ModShortcutAlt | key.ModShift}, key.Filter{Target: e, Name: key.NameDownArrow, Optional: key.ModShortcutAlt | key.ModShift},
) )
} }
} }
if caret < e.text.Len() { if caret < e.text.Len() {
if gtx.Locale.Direction.Progression() == system.FromOrigin { if gtx.Locale.Direction.Progression() == system.FromOrigin {
filters = append(filters, filters = append(filters,
key.Filter{Name: key.NameRightArrow, Optional: key.ModShortcutAlt | key.ModShift}, key.Filter{Target: e, Name: key.NameRightArrow, Optional: key.ModShortcutAlt | key.ModShift},
key.Filter{Name: key.NameDownArrow, Optional: key.ModShortcutAlt | key.ModShift}, key.Filter{Target: e, Name: key.NameDownArrow, Optional: key.ModShortcutAlt | key.ModShift},
) )
} else { } else {
filters = append(filters, filters = append(filters,
key.Filter{Name: key.NameLeftArrow, Optional: key.ModShortcutAlt | key.ModShift}, key.Filter{Target: e, Name: key.NameLeftArrow, Optional: key.ModShortcutAlt | key.ModShift},
key.Filter{Name: key.NameUpArrow, Optional: key.ModShortcutAlt | key.ModShift}, key.Filter{Target: e, Name: key.NameUpArrow, Optional: key.ModShortcutAlt | key.ModShift},
) )
} }
} }
@@ -388,7 +388,7 @@ func (e *Editor) processKey(gtx layout.Context) {
// adjust keeps track of runes dropped because of MaxLen. // adjust keeps track of runes dropped because of MaxLen.
var adjust int var adjust int
for { for {
ke, ok := gtx.Event(e, filters...) ke, ok := gtx.Event(filters...)
if !ok { if !ok {
break break
} }
+6 -3
View File
@@ -65,13 +65,16 @@ func (e *Enum) Update(gtx layout.Context) bool {
} }
} }
filters := []event.Filter{ filters := []event.Filter{
key.FocusFilter{}, key.FocusFilter{Target: &state.tag},
} }
if e.focused && e.focus == state.key { if e.focused && e.focus == state.key {
filters = append(filters, key.Filter{Name: key.NameReturn}, key.Filter{Name: key.NameSpace}) 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(&state.tag, filters...) ev, ok := gtx.Event(filters...)
if !ok { if !ok {
break break
} }
+1 -1
View File
@@ -110,7 +110,7 @@ func ExampleDraggable_Layout() {
// Check for the received data. // Check for the received data.
for { for {
ev, ok := gtx.Event(&drop, transfer.TargetFilter{Type: mime}) ev, ok := gtx.Event(transfer.TargetFilter{Target: &drop, Type: mime})
if !ok { if !ok {
break break
} }
+13 -13
View File
@@ -302,27 +302,27 @@ 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{ filters := []event.Filter{
key.FocusFilter{}, key.FocusFilter{Target: e},
} }
if e.focused { if e.focused {
filters = append(filters, filters = append(filters,
key.Filter{Name: key.NameLeftArrow, Optional: key.ModShortcutAlt | key.ModShift}, key.Filter{Target: e, Name: key.NameLeftArrow, Optional: key.ModShortcutAlt | key.ModShift},
key.Filter{Name: key.NameRightArrow, Optional: key.ModShortcutAlt | key.ModShift}, key.Filter{Target: e, Name: key.NameRightArrow, Optional: key.ModShortcutAlt | key.ModShift},
key.Filter{Name: key.NameUpArrow, Optional: key.ModShortcutAlt | key.ModShift}, key.Filter{Target: e, Name: key.NameUpArrow, Optional: key.ModShortcutAlt | key.ModShift},
key.Filter{Name: key.NameDownArrow, Optional: key.ModShortcutAlt | key.ModShift}, key.Filter{Target: e, Name: key.NameDownArrow, Optional: key.ModShortcutAlt | key.ModShift},
key.Filter{Name: key.NamePageUp, Optional: key.ModShift}, key.Filter{Target: e, Name: key.NamePageUp, Optional: key.ModShift},
key.Filter{Name: key.NamePageDown, Optional: key.ModShift}, key.Filter{Target: e, Name: key.NamePageDown, Optional: key.ModShift},
key.Filter{Name: key.NameEnd, Optional: key.ModShift}, key.Filter{Target: e, Name: key.NameEnd, Optional: key.ModShift},
key.Filter{Name: key.NameHome, Optional: key.ModShift}, key.Filter{Target: e, Name: key.NameHome, Optional: key.ModShift},
key.Filter{Name: "C", Required: key.ModShortcut}, key.Filter{Target: e, Name: "C", Required: key.ModShortcut},
key.Filter{Name: "X", Required: key.ModShortcut}, key.Filter{Target: e, Name: "X", Required: key.ModShortcut},
key.Filter{Name: "A", Required: key.ModShortcut}, key.Filter{Target: e, Name: "A", Required: key.ModShortcut},
) )
} }
for { for {
ke, ok := gtx.Event(e, filters...) ke, ok := gtx.Event(filters...)
if !ok { if !ok {
break break
} }