From d017c722f5af8df6c2dec7e7ce38590b858d2fe7 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sun, 24 May 2020 11:51:21 +0200 Subject: [PATCH] widget,widget/material: only process events in Layout methods Before this change, events were typically processed twice or more per widget: once in the Layout method for refreshing the visual state, and once per method that queries for state changes. One example is widget.Clickable that processed events in both its Layout and Clicked method. This change establishes the convention that events are processed once, in the Layout method. There are several advantages to that approach: - Query methods such as Clickable.Clicked no longer need a layout.Context. - State updates from events only occur in Layout. - Widgets are simplified because they won't need a separate processEvents (or similar) method and won't forget to call it from methods other than Layout. - Useless calls to gtx.Events are avoided (gtx.Events only returns events for the first call each frame for a given event.Tag). The disadvantage is that state updates from input events will not appear before Layout. For example, in the call sequence var btn *widget.Clickable if btn.Clicked() {...} btn.Layout(...) the Clicked call will not detect an incoming click until the frame after it happened. This is ok because - The Gio event router automatically dispatches an extra frame after events arrive, bounding the latency from events to queries such as Clicked to at most one frame (~17 ms). - The potential extra frame of latency does not apply to Layout methods as long as they process events before drawing. In other words, the visual feedback from input events are not delayed because of this change. Signed-off-by: Elias Naur --- widget/bool.go | 21 ++++++++++++--------- widget/button.go | 28 +++++++++------------------- widget/editor.go | 3 +-- widget/enum.go | 30 ++++++++++++++++-------------- widget/material/checkbox.go | 1 - widget/material/radiobutton.go | 1 - widget/material/switch.go | 2 -- 7 files changed, 38 insertions(+), 48 deletions(-) diff --git a/widget/bool.go b/widget/bool.go index fde8c323..5afaece4 100644 --- a/widget/bool.go +++ b/widget/bool.go @@ -7,17 +7,24 @@ import ( type Bool struct { Value bool - // Last is the last registered click. Last Click + // changeVal tracks Value from the most recent call to Changed. + changeVal bool + gesture gesture.Click } -// Update the checked state according to incoming events, -// and reports whether Value changed. -func (b *Bool) Update(gtx layout.Context) bool { - was := b.Value +// Changed reports whether Value has changed since the last +// call to Changed. +func (b *Bool) Changed() bool { + changed := b.Value != b.changeVal + b.changeVal = b.Value + return changed +} + +func (b *Bool) Layout(gtx layout.Context) { for _, e := range b.gesture.Events(gtx) { switch e.Type { case gesture.TypeClick: @@ -28,9 +35,5 @@ func (b *Bool) Update(gtx layout.Context) bool { b.Value = !b.Value } } - return b.Value != was -} - -func (b *Bool) Layout(gtx layout.Context) { b.gesture.Add(gtx.Ops) } diff --git a/widget/button.go b/widget/button.go index 2f4b73f9..85c4a130 100644 --- a/widget/button.go +++ b/widget/button.go @@ -27,31 +27,24 @@ type Click struct { Time time.Time } -// Clicked calls Update and reports whether the button was -// clicked since the last call. Multiple clicks result in Clicked -// returning true once per click. -func (b *Clickable) Clicked(gtx layout.Context) bool { - b.Update(gtx) +// Clicked and reports whether the button was clicked since the last +// call to Clicked. Clicked returns true once per click. +func (b *Clickable) Clicked() bool { if b.clicks > 0 { b.clicks-- - if b.clicks > 0 { - // Ensure timely delivery of remaining clicks. - op.InvalidateOp{}.Add(gtx.Ops) - } return true } return false } -// History is the past clicks useful for drawing click markers. -// Clicks are retained for a short duration (about a second). +// History is the past pointer presses useful for drawing markers. +// History is retained for a short duration (about a second). func (b *Clickable) History() []Click { return b.history } func (b *Clickable) Layout(gtx layout.Context) layout.Dimensions { - // Flush clicks from before the previous frame. - b.Update(gtx) + b.update(gtx) var st op.StackOp st.Push(gtx.Ops) pointer.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops) @@ -68,11 +61,9 @@ func (b *Clickable) Layout(gtx layout.Context) layout.Dimensions { return layout.Dimensions{Size: gtx.Constraints.Min} } -// Update the button state by processing events. The underlying -// gesture events are returned for use beyond what Clicked offers. -func (b *Clickable) Update(gtx layout.Context) []gesture.ClickEvent { - evts := b.click.Events(gtx) - for _, e := range evts { +// update the button state by processing events. +func (b *Clickable) update(gtx layout.Context) { + for _, e := range b.click.Events(gtx) { switch e.Type { case gesture.TypeClick: b.clicks++ @@ -83,5 +74,4 @@ func (b *Clickable) Update(gtx layout.Context) []gesture.ClickEvent { }) } } - return evts } diff --git a/widget/editor.go b/widget/editor.go index 01b5ddd7..b3fe246a 100644 --- a/widget/editor.go +++ b/widget/editor.go @@ -89,8 +89,7 @@ const ( ) // Events returns available editor events. -func (e *Editor) Events(gtx layout.Context) []EditorEvent { - e.processEvents(gtx) +func (e *Editor) Events() []EditorEvent { events := e.events e.events = nil e.prevEvents = 0 diff --git a/widget/enum.go b/widget/enum.go index 7b38931d..f2d1199e 100644 --- a/widget/enum.go +++ b/widget/enum.go @@ -8,6 +8,8 @@ import ( type Enum struct { Value string + changeVal string + clicks []gesture.Click values []string } @@ -21,19 +23,12 @@ func index(vs []string, t string) int { return -1 } -// Update the Value according to incoming events, and -// reports whether Value changed. -func (e *Enum) Update(gtx layout.Context) bool { - was := e.Value - for i := range e.clicks { - for _, ev := range e.clicks[i].Events(gtx) { - switch ev.Type { - case gesture.TypeClick: - e.Value = e.values[i] - } - } - } - return e.Value != was +// Changed reports whether Value has changed since the last +// call to Changed. +func (e *Enum) Changed() bool { + changed := e.changeVal != e.Value + e.changeVal = e.Value + return changed } // Layout adds the event handler for key. @@ -44,6 +39,13 @@ func (e *Enum) Layout(gtx layout.Context, key string) { e.clicks[len(e.clicks)-1].Add(gtx.Ops) } else { idx := index(e.values, key) - e.clicks[idx].Add(gtx.Ops) + clk := &e.clicks[idx] + for _, ev := range clk.Events(gtx) { + switch ev.Type { + case gesture.TypeClick: + e.Value = e.values[idx] + } + } + clk.Add(gtx.Ops) } } diff --git a/widget/material/checkbox.go b/widget/material/checkbox.go index 25b45614..f52456c0 100644 --- a/widget/material/checkbox.go +++ b/widget/material/checkbox.go @@ -31,7 +31,6 @@ func CheckBox(th *Theme, checkBox *widget.Bool, label string) CheckBoxStyle { // Layout updates the checkBox and displays it. func (c CheckBoxStyle) Layout(gtx layout.Context) layout.Dimensions { - c.CheckBox.Update(gtx) dims := c.layout(gtx, c.CheckBox.Value) c.CheckBox.Layout(gtx) return dims diff --git a/widget/material/radiobutton.go b/widget/material/radiobutton.go index 58d95881..4b14b06e 100644 --- a/widget/material/radiobutton.go +++ b/widget/material/radiobutton.go @@ -36,7 +36,6 @@ func RadioButton(th *Theme, group *widget.Enum, key, label string) RadioButtonSt // Layout updates enum and displays the radio button. func (r RadioButtonStyle) Layout(gtx layout.Context) layout.Dimensions { - r.Group.Update(gtx) dims := r.layout(gtx, r.Group.Value == r.Key) r.Group.Layout(gtx, r.Key) return dims diff --git a/widget/material/switch.go b/widget/material/switch.go index 04ab3369..23425a06 100644 --- a/widget/material/switch.go +++ b/widget/material/switch.go @@ -30,8 +30,6 @@ func Switch(th *Theme, swtch *widget.Bool) SwitchStyle { // Layout updates the checkBox and displays it. func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions { - s.Switch.Update(gtx) - trackWidth := gtx.Px(unit.Dp(36)) trackHeight := gtx.Px(unit.Dp(16)) thumbSize := gtx.Px(unit.Dp(20))