ui: change Queue to return all events at once

The Queue interface was changed from

	type Queue interface {
		Events(k Key) []Event
	}

to the more complex single-step protocol

	type Queue interface {
		Next(k Key) (Event, bool)
	}

to cater for a particular use case: Editor's SubmitEvent. When a
SubmitEvent is passed to a caller of Editor.Next, the Editor state,
in particular the current text, must not have changed by edits
later in the command stream. For example, pressing the keys <E>,
<Enter>, <E> should result in a SubmitEvent where the Editor has
a single 'e' in Text(), not two.

However, there is no reason to push the more complex Queue to every user.
Rather, store remaining input events inside Editor and process them as
Editor.Event (or Layout) is called.

Finally, revert the Queue interface to the simpler Events method.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-09-26 15:50:51 +02:00
parent 578b169279
commit dc6fedc163
6 changed files with 38 additions and 28 deletions
+7 -9
View File
@@ -37,8 +37,8 @@ type handlerEvents struct {
updated bool updated bool
} }
func (q *Router) Next(k ui.Key) (ui.Event, bool) { func (q *Router) Events(k ui.Key) []ui.Event {
return q.handlers.Next(k) return q.handlers.Events(k)
} }
func (q *Router) Frame(ops *ui.Ops) { func (q *Router) Frame(ops *ui.Ops) {
@@ -124,14 +124,12 @@ func (h *handlerEvents) Updated() bool {
return u return u
} }
func (h *handlerEvents) Next(k ui.Key) (ui.Event, bool) { func (h *handlerEvents) Events(k ui.Key) []ui.Event {
events := h.handlers[k] if events, ok := h.handlers[k]; ok {
if len(events) == 0 { h.handlers[k] = h.handlers[k][:0]
return nil, false return events
} }
e := events[0] return nil
h.handlers[k] = events[1:]
return e, true
} }
func (h *handlerEvents) Clear() { func (h *handlerEvents) Clear() {
+2 -2
View File
@@ -323,8 +323,8 @@ func (w *Window) run(opts *windowOptions) {
} }
} }
func (q *Queue) Next(k ui.Key) (ui.Event, bool) { func (q *Queue) Events(k ui.Key) []ui.Event {
return q.q.Next(k) return q.q.Events(k)
} }
// WithTitle returns an option that sets the window title. // WithTitle returns an option that sets the window title.
+1 -1
View File
@@ -89,7 +89,7 @@ For example:
var queue ui.Queue = ... var queue ui.Queue = ...
for e, ok := queue.Next(h); ok; e, ok = queue.Next(h) { for _, e := range queue.Events(h) {
switch e.(type) { switch e.(type) {
... ...
} }
+7 -6
View File
@@ -105,8 +105,9 @@ func (c *Click) State() ClickState {
} }
// Next returns the next click event, if any. // Next returns the next click event, if any.
func (c *Click) Next(q ui.Queue) (ClickEvent, bool) { func (c *Click) Events(q ui.Queue) []ClickEvent {
for evt, ok := q.Next(c); ok; evt, ok = q.Next(c) { var events []ClickEvent
for _, evt := range q.Events(c) {
e, ok := evt.(pointer.Event) e, ok := evt.(pointer.Event)
if !ok { if !ok {
continue continue
@@ -116,7 +117,7 @@ func (c *Click) Next(q ui.Queue) (ClickEvent, bool) {
wasPressed := c.state == StatePressed wasPressed := c.state == StatePressed
c.state = StateNormal c.state = StateNormal
if wasPressed { if wasPressed {
return ClickEvent{Type: TypeClick, Position: e.Position, Source: e.Source}, true events = append(events, ClickEvent{Type: TypeClick, Position: e.Position, Source: e.Source})
} }
case pointer.Cancel: case pointer.Cancel:
c.state = StateNormal c.state = StateNormal
@@ -125,7 +126,7 @@ func (c *Click) Next(q ui.Queue) (ClickEvent, bool) {
break break
} }
c.state = StatePressed c.state = StatePressed
return ClickEvent{Type: TypePress, Position: e.Position, Source: e.Source}, true events = append(events, ClickEvent{Type: TypePress, Position: e.Position, Source: e.Source})
case pointer.Move: case pointer.Move:
if c.state == StatePressed && !e.Hit { if c.state == StatePressed && !e.Hit {
c.state = StateNormal c.state = StateNormal
@@ -134,7 +135,7 @@ func (c *Click) Next(q ui.Queue) (ClickEvent, bool) {
} }
} }
} }
return ClickEvent{}, false return events
} }
// Add the handler to the operation list to receive scroll events. // Add the handler to the operation list to receive scroll events.
@@ -159,7 +160,7 @@ func (s *Scroll) Scroll(cfg ui.Config, q ui.Queue, axis Axis) int {
return 0 return 0
} }
total := 0 total := 0
for evt, ok := q.Next(s); ok; evt, ok = q.Next(s) { for _, evt := range q.Events(s) {
e, ok := evt.(pointer.Event) e, ok := evt.(pointer.Event)
if !ok { if !ok {
continue continue
+3 -3
View File
@@ -5,9 +5,9 @@ package ui
// Queue maps an event handler key to the events // Queue maps an event handler key to the events
// available to the handler. // available to the handler.
type Queue interface { type Queue interface {
// Next returns the next available event, or // Events returns the available events for a
// false if none are available. // Key.
Next(k Key) (Event, bool) Events(k Key) []Event
} }
// Key is the stable identifier for an event handler. // Key is the stable identifier for an event handler.
+18 -7
View File
@@ -62,6 +62,9 @@ type Editor struct {
scrollOff image.Point scrollOff image.Point
clicker gesture.Click clicker gesture.Click
// events is the list of events not yet processed.
events []ui.Event
} }
type EditorEvent interface { type EditorEvent interface {
@@ -71,7 +74,7 @@ type EditorEvent interface {
// A ChangeEvent is generated for every user change to the text. // A ChangeEvent is generated for every user change to the text.
type ChangeEvent struct{} type ChangeEvent struct{}
// A SubmitEvent is generated when and Editor's Submit is set // A SubmitEvent is generated when Submit is set
// and a carriage return key is pressed. // and a carriage return key is pressed.
type SubmitEvent struct{} type SubmitEvent struct{}
@@ -80,8 +83,8 @@ const (
maxBlinkDuration = 10 * time.Second maxBlinkDuration = 10 * time.Second
) )
// Next returns the next available editor event, or false if none are available. // Event returns the next available editor event, or false if none are available.
func (e *Editor) Next(gtx *layout.Context) (EditorEvent, bool) { func (e *Editor) Event(gtx *layout.Context) (EditorEvent, bool) {
// Crude configuration change detection. // Crude configuration change detection.
if scale := gtx.Px(ui.Sp(100)); scale != e.oldScale { if scale := gtx.Px(ui.Sp(100)); scale != e.oldScale {
e.invalidate() e.invalidate()
@@ -106,7 +109,7 @@ func (e *Editor) Next(gtx *layout.Context) (EditorEvent, bool) {
e.scrollOff.Y += sdist e.scrollOff.Y += sdist
soff = e.scrollOff.Y soff = e.scrollOff.Y
} }
for evt, ok := e.clicker.Next(gtx.Queue); ok; evt, ok = e.clicker.Next(gtx.Queue) { for _, evt := range e.clicker.Events(gtx.Queue) {
switch { switch {
case evt.Type == gesture.TypePress && evt.Source == pointer.Mouse, case evt.Type == gesture.TypePress && evt.Source == pointer.Mouse,
evt.Type == gesture.TypeClick && evt.Source == pointer.Touch: evt.Type == gesture.TypeClick && evt.Source == pointer.Touch:
@@ -124,7 +127,15 @@ func (e *Editor) Next(gtx *layout.Context) (EditorEvent, bool) {
if (sdist > 0 && soff >= smax) || (sdist < 0 && soff <= smin) { if (sdist > 0 && soff >= smax) || (sdist < 0 && soff <= smin) {
e.scroller.Stop() e.scroller.Stop()
} }
for ke, ok := gtx.Queue.Next(e); ok; ke, ok = gtx.Queue.Next(e) { e.events = append(e.events, gtx.Queue.Events(e)...)
return e.editorEvent(gtx)
}
func (e *Editor) editorEvent(gtx *layout.Context) (EditorEvent, bool) {
for len(e.events) > 0 {
ke := e.events[0]
copy(e.events, e.events[1:])
e.events = e.events[:len(e.events)-1]
e.blinkStart = gtx.Now() e.blinkStart = gtx.Now()
switch ke := ke.(type) { switch ke := ke.(type) {
case key.FocusEvent: case key.FocusEvent:
@@ -133,7 +144,7 @@ func (e *Editor) Next(gtx *layout.Context) (EditorEvent, bool) {
if !e.focused { if !e.focused {
break break
} }
if e.Submit && (ke.Name == key.NameReturn || ke.Name == key.NameEnter) { if e.Submit && ke.Name == key.NameReturn || ke.Name == key.NameEnter {
if !ke.Modifiers.Contain(key.ModShift) { if !ke.Modifiers.Contain(key.ModShift) {
return SubmitEvent{}, true return SubmitEvent{}, true
} }
@@ -166,7 +177,7 @@ func (e *Editor) Focus() {
func (e *Editor) Layout(gtx *layout.Context) { func (e *Editor) Layout(gtx *layout.Context) {
cs := gtx.Constraints cs := gtx.Constraints
for _, ok := e.Next(gtx); ok; _, ok = e.Next(gtx) { for _, ok := e.Event(gtx); ok; _, ok = e.Event(gtx) {
} }
twoDp := gtx.Px(ui.Dp(2)) twoDp := gtx.Px(ui.Dp(2))
e.padLeft, e.padRight = twoDp, twoDp e.padLeft, e.padRight = twoDp, twoDp