mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-05 17:35:36 +00:00
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 <mail@eliasnaur.com>
This commit is contained in:
+12
-9
@@ -7,17 +7,24 @@ import (
|
|||||||
|
|
||||||
type Bool struct {
|
type Bool struct {
|
||||||
Value bool
|
Value bool
|
||||||
|
|
||||||
// Last is the last registered click.
|
// Last is the last registered click.
|
||||||
Last Click
|
Last Click
|
||||||
|
|
||||||
|
// changeVal tracks Value from the most recent call to Changed.
|
||||||
|
changeVal bool
|
||||||
|
|
||||||
gesture gesture.Click
|
gesture gesture.Click
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the checked state according to incoming events,
|
// Changed reports whether Value has changed since the last
|
||||||
// and reports whether Value changed.
|
// call to Changed.
|
||||||
func (b *Bool) Update(gtx layout.Context) bool {
|
func (b *Bool) Changed() bool {
|
||||||
was := b.Value
|
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) {
|
for _, e := range b.gesture.Events(gtx) {
|
||||||
switch e.Type {
|
switch e.Type {
|
||||||
case gesture.TypeClick:
|
case gesture.TypeClick:
|
||||||
@@ -28,9 +35,5 @@ func (b *Bool) Update(gtx layout.Context) bool {
|
|||||||
b.Value = !b.Value
|
b.Value = !b.Value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return b.Value != was
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bool) Layout(gtx layout.Context) {
|
|
||||||
b.gesture.Add(gtx.Ops)
|
b.gesture.Add(gtx.Ops)
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-19
@@ -27,31 +27,24 @@ type Click struct {
|
|||||||
Time time.Time
|
Time time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clicked calls Update and reports whether the button was
|
// Clicked and reports whether the button was clicked since the last
|
||||||
// clicked since the last call. Multiple clicks result in Clicked
|
// call to Clicked. Clicked returns true once per click.
|
||||||
// returning true once per click.
|
func (b *Clickable) Clicked() bool {
|
||||||
func (b *Clickable) Clicked(gtx layout.Context) bool {
|
|
||||||
b.Update(gtx)
|
|
||||||
if b.clicks > 0 {
|
if b.clicks > 0 {
|
||||||
b.clicks--
|
b.clicks--
|
||||||
if b.clicks > 0 {
|
|
||||||
// Ensure timely delivery of remaining clicks.
|
|
||||||
op.InvalidateOp{}.Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// History is the past clicks useful for drawing click markers.
|
// History is the past pointer presses useful for drawing markers.
|
||||||
// Clicks are retained for a short duration (about a second).
|
// History is retained for a short duration (about a second).
|
||||||
func (b *Clickable) History() []Click {
|
func (b *Clickable) History() []Click {
|
||||||
return b.history
|
return b.history
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Clickable) Layout(gtx layout.Context) layout.Dimensions {
|
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
|
var st op.StackOp
|
||||||
st.Push(gtx.Ops)
|
st.Push(gtx.Ops)
|
||||||
pointer.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Add(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}
|
return layout.Dimensions{Size: gtx.Constraints.Min}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the button state by processing events. The underlying
|
// update the button state by processing events.
|
||||||
// gesture events are returned for use beyond what Clicked offers.
|
func (b *Clickable) update(gtx layout.Context) {
|
||||||
func (b *Clickable) Update(gtx layout.Context) []gesture.ClickEvent {
|
for _, e := range b.click.Events(gtx) {
|
||||||
evts := b.click.Events(gtx)
|
|
||||||
for _, e := range evts {
|
|
||||||
switch e.Type {
|
switch e.Type {
|
||||||
case gesture.TypeClick:
|
case gesture.TypeClick:
|
||||||
b.clicks++
|
b.clicks++
|
||||||
@@ -83,5 +74,4 @@ func (b *Clickable) Update(gtx layout.Context) []gesture.ClickEvent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return evts
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -89,8 +89,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Events returns available editor events.
|
// Events returns available editor events.
|
||||||
func (e *Editor) Events(gtx layout.Context) []EditorEvent {
|
func (e *Editor) Events() []EditorEvent {
|
||||||
e.processEvents(gtx)
|
|
||||||
events := e.events
|
events := e.events
|
||||||
e.events = nil
|
e.events = nil
|
||||||
e.prevEvents = 0
|
e.prevEvents = 0
|
||||||
|
|||||||
+16
-14
@@ -8,6 +8,8 @@ import (
|
|||||||
type Enum struct {
|
type Enum struct {
|
||||||
Value string
|
Value string
|
||||||
|
|
||||||
|
changeVal string
|
||||||
|
|
||||||
clicks []gesture.Click
|
clicks []gesture.Click
|
||||||
values []string
|
values []string
|
||||||
}
|
}
|
||||||
@@ -21,19 +23,12 @@ func index(vs []string, t string) int {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the Value according to incoming events, and
|
// Changed reports whether Value has changed since the last
|
||||||
// reports whether Value changed.
|
// call to Changed.
|
||||||
func (e *Enum) Update(gtx layout.Context) bool {
|
func (e *Enum) Changed() bool {
|
||||||
was := e.Value
|
changed := e.changeVal != e.Value
|
||||||
for i := range e.clicks {
|
e.changeVal = e.Value
|
||||||
for _, ev := range e.clicks[i].Events(gtx) {
|
return changed
|
||||||
switch ev.Type {
|
|
||||||
case gesture.TypeClick:
|
|
||||||
e.Value = e.values[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return e.Value != was
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layout adds the event handler for key.
|
// 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)
|
e.clicks[len(e.clicks)-1].Add(gtx.Ops)
|
||||||
} else {
|
} else {
|
||||||
idx := index(e.values, key)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ func CheckBox(th *Theme, checkBox *widget.Bool, label string) CheckBoxStyle {
|
|||||||
|
|
||||||
// Layout updates the checkBox and displays it.
|
// Layout updates the checkBox and displays it.
|
||||||
func (c CheckBoxStyle) Layout(gtx layout.Context) layout.Dimensions {
|
func (c CheckBoxStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||||
c.CheckBox.Update(gtx)
|
|
||||||
dims := c.layout(gtx, c.CheckBox.Value)
|
dims := c.layout(gtx, c.CheckBox.Value)
|
||||||
c.CheckBox.Layout(gtx)
|
c.CheckBox.Layout(gtx)
|
||||||
return dims
|
return dims
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ func RadioButton(th *Theme, group *widget.Enum, key, label string) RadioButtonSt
|
|||||||
|
|
||||||
// Layout updates enum and displays the radio button.
|
// Layout updates enum and displays the radio button.
|
||||||
func (r RadioButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
|
func (r RadioButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||||
r.Group.Update(gtx)
|
|
||||||
dims := r.layout(gtx, r.Group.Value == r.Key)
|
dims := r.layout(gtx, r.Group.Value == r.Key)
|
||||||
r.Group.Layout(gtx, r.Key)
|
r.Group.Layout(gtx, r.Key)
|
||||||
return dims
|
return dims
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ func Switch(th *Theme, swtch *widget.Bool) SwitchStyle {
|
|||||||
|
|
||||||
// Layout updates the checkBox and displays it.
|
// Layout updates the checkBox and displays it.
|
||||||
func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
|
func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||||
s.Switch.Update(gtx)
|
|
||||||
|
|
||||||
trackWidth := gtx.Px(unit.Dp(36))
|
trackWidth := gtx.Px(unit.Dp(36))
|
||||||
trackHeight := gtx.Px(unit.Dp(16))
|
trackHeight := gtx.Px(unit.Dp(16))
|
||||||
thumbSize := gtx.Px(unit.Dp(20))
|
thumbSize := gtx.Px(unit.Dp(20))
|
||||||
|
|||||||
Reference in New Issue
Block a user