io/input: [API] introduce Source, the interface between a Router and widgets

This change gets rid of the event.Queue interface by replacing it with
input.Source values. Source provides the interface to Router necessary
to implement interface widgets.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2023-10-08 17:56:03 -05:00
parent c319f3c214
commit 6027517949
31 changed files with 106 additions and 127 deletions
+3 -3
View File
@@ -9,7 +9,7 @@ import (
"strings" "strings"
"time" "time"
"gioui.org/io/event" "gioui.org/io/input"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op" "gioui.org/op"
"gioui.org/unit" "gioui.org/unit"
@@ -53,8 +53,8 @@ type FrameEvent struct {
// Frame completes the FrameEvent by drawing the graphical operations // Frame completes the FrameEvent by drawing the graphical operations
// from ops into the window. // from ops into the window.
Frame func(frame *op.Ops) Frame func(frame *op.Ops)
// Source supplies the events for event handlers. // Source is the interface between the interface state and widgets.
Source event.Queue Source input.Source
} }
// Insets is the space taken up by // Insets is the space taken up by
+1 -1
View File
@@ -34,7 +34,7 @@ func FuzzIME(f *testing.F) {
e.Focus() e.Focus()
var r input.Router var r input.Router
gtx := layout.Context{Ops: new(op.Ops), Source: &r} gtx := layout.Context{Ops: new(op.Ops), Source: r.Source()}
// Layout once to register focus. // Layout once to register focus.
e.Layout(gtx, cache, font.Font{}, unit.Sp(10), op.CallOp{}, op.CallOp{}) e.Layout(gtx, cache, font.Font{}, unit.Sp(10), op.CallOp{}, op.CallOp{})
r.Frame(gtx.Ops) r.Frame(gtx.Ops)
+1 -1
View File
@@ -832,7 +832,7 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
w.metric = e2.Metric w.metric = e2.Metric
w.hasNextFrame = false w.hasNextFrame = false
e2.Frame = w.update e2.Frame = w.update
e2.Source = &w.queue e2.Source = w.queue.Source()
// Prepare the decorations and update the frame insets. // Prepare the decorations and update the frame insets.
wrapper := &w.decorations.Ops wrapper := &w.decorations.Ops
+5 -5
View File
@@ -17,7 +17,7 @@ import (
"gioui.org/f32" "gioui.org/f32"
"gioui.org/internal/fling" "gioui.org/internal/fling"
"gioui.org/io/event" "gioui.org/io/input"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/io/pointer" "gioui.org/io/pointer"
"gioui.org/op" "gioui.org/op"
@@ -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 event.Queue) bool { func (h *Hover) Update(q input.Source) bool {
for _, ev := range q.Events(h) { for _, ev := range q.Events(h) {
e, ok := ev.(pointer.Event) e, ok := ev.(pointer.Event)
if !ok { if !ok {
@@ -178,7 +178,7 @@ func (c *Click) Pressed() bool {
} }
// Update state and return the click events. // Update state and return the click events.
func (c *Click) Update(q event.Queue) []ClickEvent { func (c *Click) Update(q input.Source) []ClickEvent {
var events []ClickEvent var events []ClickEvent
for _, evt := range q.Events(c) { for _, evt := range q.Events(c) {
e, ok := evt.(pointer.Event) e, ok := evt.(pointer.Event)
@@ -269,7 +269,7 @@ func (s *Scroll) Stop() {
} }
// Update state and report the scroll distance along axis. // Update state and report the scroll distance along axis.
func (s *Scroll) Update(cfg unit.Metric, q event.Queue, t time.Time, axis Axis) int { func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis) int {
if s.axis != axis { if s.axis != axis {
s.axis = axis s.axis = axis
return 0 return 0
@@ -372,7 +372,7 @@ func (d *Drag) Add(ops *op.Ops) {
} }
// Update state and return the drag events. // Update state and return the drag events.
func (d *Drag) Update(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event { func (d *Drag) Update(cfg unit.Metric, q input.Source, axis Axis) []pointer.Event {
var events []pointer.Event var events []pointer.Event
for _, e := range q.Events(d) { for _, e := range q.Events(d) {
e, ok := e.(pointer.Event) e, ok := e.(pointer.Event)
+3 -3
View File
@@ -28,14 +28,14 @@ func TestHover(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{Kind: pointer.Move, Position: f32.Pt(30, 30)}, pointer.Event{Kind: pointer.Move, Position: f32.Pt(30, 30)},
) )
if !h.Update(r) { if !h.Update(r.Source()) {
t.Fatal("expected hovered") t.Fatal("expected hovered")
} }
r.Queue( r.Queue(
pointer.Event{Kind: pointer.Move, Position: f32.Pt(50, 50)}, pointer.Event{Kind: pointer.Move, Position: f32.Pt(50, 50)},
) )
if h.Update(r) { if h.Update(r.Source()) {
t.Fatal("expected not hovered") t.Fatal("expected not hovered")
} }
} }
@@ -75,7 +75,7 @@ func TestMouseClicks(t *testing.T) {
r.Frame(&ops) r.Frame(&ops)
r.Queue(tc.events...) r.Queue(tc.events...)
events := click.Update(&r) events := click.Update(r.Source())
clicks := filterMouseClicks(events) clicks := filterMouseClicks(events)
if got, want := len(clicks), len(tc.clicks); got != want { if got, want := len(clicks), len(tc.clicks); got != want {
t.Fatalf("got %d mouse clicks, expected %d", got, want) t.Fatalf("got %d mouse clicks, expected %d", got, want)
+1 -35
View File
@@ -1,42 +1,8 @@
// SPDX-License-Identifier: Unlicense OR MIT // SPDX-License-Identifier: Unlicense OR MIT
/* // Package event contains types for event handling.
Package event contains the types for event handling.
The Queue interface is the protocol for receiving external events.
For example:
var queue event.Queue = ...
for _, e := range queue.Events(h) {
switch e.(type) {
...
}
}
In general, handlers must be declared before events become
available. Other packages such as pointer and key provide
the means for declaring handlers for specific event types.
The following example declares a handler ready for key input:
import gioui.org/io/key
ops := new(op.Ops)
var h *Handler = ...
key.InputOp{Tag: h, Filter: ...}.Add(ops)
*/
package event package event
// Queue maps an event handler key to the events
// available to the handler.
type Queue interface {
// Events returns the available events for an
// event handler tag.
Events(t Tag) []Event
}
// Tag is the stable identifier for an event handler. // Tag is the stable identifier for an event handler.
// For a handler h, the tag is typically &h. // For a handler h, the tag is typically &h.
type Tag interface{} type Tag interface{}
+15
View File
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package input implements input routing and tracking of interface
state for a window.
The [Source] is the interface between the window and the widgets
of a user interface and is exposed by [gioui.org/app.FrameEvent]
received from windows.
The [Router] is used by [gioui.org/app.Window] to track window state and route
events from the platform to event handlers. It is otherwise only
useful for using Gio with external window implementations.
*/
package input
+27 -11
View File
@@ -1,13 +1,5 @@
// SPDX-License-Identifier: Unlicense OR MIT // SPDX-License-Identifier: Unlicense OR MIT
/*
Package input implements Router, an event.Queue implementation
that disambiguates and routes events to handlers declared
in operation lists.
Router is used by app.Window and is otherwise only useful for
using Gio with external window implementations.
*/
package input package input
import ( import (
@@ -31,8 +23,8 @@ import (
"gioui.org/op" "gioui.org/op"
) )
// Router is a Queue implementation that routes events // Router tracks the [io/event.Tag] identifiers of user interface widgets
// to handlers declared in operation lists. // and routes events to them. [Source] is its interface exposed to widgets.
type Router struct { type Router struct {
savedTrans []f32.Affine2D savedTrans []f32.Affine2D
transStack []f32.Affine2D transStack []f32.Affine2D
@@ -55,6 +47,12 @@ type Router struct {
wakeupTime time.Time wakeupTime time.Time
} }
// Source implements the interface between a Router and user interface widgets.
// The value Source is disabled.
type Source struct {
r *Router
}
// SemanticNode represents a node in the tree describing the components // SemanticNode represents a node in the tree describing the components
// contained in a frame. // contained in a frame.
type SemanticNode struct { type SemanticNode struct {
@@ -95,7 +93,25 @@ type handlerEvents struct {
hadEvents bool hadEvents bool
} }
// Events returns the available events for the handler key. // Source returns a Source backed by this Router.
func (q *Router) Source() Source {
return Source{r: q}
}
// Enabled reports whether the source is enabled. Only enabled
// Sources deliver events and respond to commands.
func (s Source) Enabled() bool {
return s.r != nil
}
// Events returns the available events for the handler tag.
func (s Source) Events(k event.Tag) []event.Event {
if !s.Enabled() {
return nil
}
return s.r.Events(k)
}
func (q *Router) Events(k event.Tag) []event.Event { func (q *Router) Events(k event.Tag) []event.Event {
events := q.handlers.Events(k) events := q.handlers.Events(k)
return events return events
+5 -19
View File
@@ -5,7 +5,7 @@ package layout
import ( import (
"time" "time"
"gioui.org/io/event" "gioui.org/io/input"
"gioui.org/io/system" "gioui.org/io/system"
"gioui.org/op" "gioui.org/op"
"gioui.org/unit" "gioui.org/unit"
@@ -20,9 +20,6 @@ type Context struct {
Constraints Constraints Constraints Constraints
Metric unit.Metric Metric unit.Metric
// By convention, a nil Source is a signal to widgets to draw themselves
// in a disabled state.
Source event.Queue
// Now is the animation time. // Now is the animation time.
Now time.Time Now time.Time
@@ -31,6 +28,7 @@ type Context struct {
// Interested users must look up and populate these values manually. // Interested users must look up and populate these values manually.
Locale system.Locale Locale system.Locale
input.Source
*op.Ops *op.Ops
} }
@@ -44,21 +42,9 @@ func (c Context) Sp(v unit.Sp) int {
return c.Metric.Sp(v) return c.Metric.Sp(v)
} }
// Events returns the events available for the key. If no // Disabled returns a copy of this context with a disabled Source,
// queue is configured, Events returns nil. // blocking widgets from changing its state and receiving events.
func (c Context) Events(k event.Tag) []event.Event {
if c.Source == nil {
return nil
}
return c.Source.Events(k)
}
// Disabled returns a copy of this context with a nil Queue,
// blocking events to widgets using it.
//
// By convention, a nil Queue is a signal to widgets to draw themselves
// in a disabled state.
func (c Context) Disabled() Context { func (c Context) Disabled() Context {
c.Source = nil c.Source = input.Source{}
return c return c
} }
+1 -1
View File
@@ -144,7 +144,7 @@ func (l *List) Dragging() bool {
} }
func (l *List) update(gtx Context) { func (l *List) update(gtx Context) {
d := l.scroll.Update(gtx.Metric, gtx, gtx.Now, gesture.Axis(l.Axis)) d := l.scroll.Update(gtx.Metric, gtx.Source, gtx.Now, gesture.Axis(l.Axis))
l.scrollDelta = d l.scrollDelta = d
l.Position.Offset += d l.Position.Offset += d
} }
+1 -1
View File
@@ -70,7 +70,7 @@ func TestListPosition(t *testing.T) {
Constraints: Constraints{ Constraints: Constraints{
Max: image.Pt(20, 10), Max: image.Pt(20, 10),
}, },
Source: r, Source: r.Source(),
} }
el := func(gtx Context, idx int) Dimensions { el := func(gtx Context, idx int) Dimensions {
return Dimensions{Size: image.Pt(10, 10)} return Dimensions{Size: image.Pt(10, 10)}
+1 -1
View File
@@ -46,7 +46,7 @@ func (b *Bool) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
b.Update(gtx) b.Update(gtx)
dims := b.clk.Layout(gtx, func(gtx layout.Context) layout.Dimensions { dims := b.clk.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
semantic.SelectedOp(b.Value).Add(gtx.Ops) semantic.SelectedOp(b.Value).Add(gtx.Ops)
semantic.EnabledOp(gtx.Source != nil).Add(gtx.Ops) semantic.EnabledOp(gtx.Enabled()).Add(gtx.Ops)
return w(gtx) return w(gtx)
}) })
return dims return dims
+4 -5
View File
@@ -101,10 +101,9 @@ func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimension
dims := w(gtx) dims := w(gtx)
c := m.Stop() c := m.Stop()
defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop() defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop()
enabled := gtx.Source != nil semantic.EnabledOp(gtx.Enabled()).Add(gtx.Ops)
semantic.EnabledOp(enabled).Add(gtx.Ops)
b.click.Add(gtx.Ops) b.click.Add(gtx.Ops)
if enabled { if gtx.Enabled() {
keys := key.Set("⏎|Space") keys := key.Set("⏎|Space")
if !b.focused { if !b.focused {
keys = "" keys = ""
@@ -119,7 +118,7 @@ func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimension
// clicks, if any. // clicks, if any.
func (b *Clickable) Update(gtx layout.Context) []Click { func (b *Clickable) Update(gtx layout.Context) []Click {
b.clicks = nil b.clicks = nil
if gtx.Source == nil { if !gtx.Enabled() {
b.focused = false b.focused = false
} }
if b.requestFocus { if b.requestFocus {
@@ -141,7 +140,7 @@ func (b *Clickable) Update(gtx layout.Context) []Click {
NumClicks: c, NumClicks: c,
}) })
} }
for _, e := range b.click.Update(gtx) { for _, e := range b.click.Update(gtx.Source) {
switch e.Kind { switch e.Kind {
case gesture.KindClick: case gesture.KindClick:
if l := len(b.history); l > 0 { if l := len(b.history); l > 0 {
+1 -1
View File
@@ -21,7 +21,7 @@ func TestClickable(t *testing.T) {
b1 widget.Clickable b1 widget.Clickable
b2 widget.Clickable b2 widget.Clickable
) )
gtx := app.NewContext(&ops, app.FrameEvent{Source: &r}) gtx := app.NewContext(&ops, app.FrameEvent{Source: r.Source()})
layout := func() { layout := func() {
b1.Layout(gtx, func(gtx layout.Context) layout.Dimensions { b1.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Dimensions{Size: image.Pt(100, 100)} return layout.Dimensions{Size: image.Pt(100, 100)}
+1 -1
View File
@@ -24,7 +24,7 @@ type Draggable struct {
} }
func (d *Draggable) Layout(gtx layout.Context, w, drag layout.Widget) layout.Dimensions { func (d *Draggable) Layout(gtx layout.Context, w, drag layout.Widget) layout.Dimensions {
if gtx.Source == nil { if !gtx.Enabled() {
return w(gtx) return w(gtx)
} }
dims := w(gtx) dims := w(gtx)
+1 -1
View File
@@ -17,7 +17,7 @@ func TestDraggable(t *testing.T) {
var r input.Router var r input.Router
gtx := layout.Context{ gtx := layout.Context{
Constraints: layout.Exact(image.Pt(100, 100)), Constraints: layout.Exact(image.Pt(100, 100)),
Source: &r, Source: r.Source(),
Ops: new(op.Ops), Ops: new(op.Ops),
} }
+4 -6
View File
@@ -225,7 +225,7 @@ func (e *Editor) processPointer(gtx layout.Context) {
axis = gesture.Vertical axis = gesture.Vertical
smin, smax = sbounds.Min.Y, sbounds.Max.Y smin, smax = sbounds.Min.Y, sbounds.Max.Y
} }
sdist := e.scroller.Update(gtx.Metric, gtx, gtx.Now, axis) sdist := e.scroller.Update(gtx.Metric, gtx.Source, gtx.Now, axis)
var soff int var soff int
if e.SingleLine { if e.SingleLine {
e.text.ScrollRel(sdist, 0) e.text.ScrollRel(sdist, 0)
@@ -305,10 +305,10 @@ func (e *Editor) processPointer(gtx layout.Context) {
func (e *Editor) clickDragEvents(gtx layout.Context) []event.Event { func (e *Editor) clickDragEvents(gtx layout.Context) []event.Event {
var combinedEvents []event.Event var combinedEvents []event.Event
for _, evt := range e.clicker.Update(gtx) { for _, evt := range e.clicker.Update(gtx.Source) {
combinedEvents = append(combinedEvents, evt) combinedEvents = append(combinedEvents, evt)
} }
for _, evt := range e.dragger.Update(gtx.Metric, gtx, gesture.Both) { for _, evt := range e.dragger.Update(gtx.Metric, gtx.Source, gesture.Both) {
combinedEvents = append(combinedEvents, evt) combinedEvents = append(combinedEvents, evt)
} }
return combinedEvents return combinedEvents
@@ -677,14 +677,12 @@ func (e *Editor) layout(gtx layout.Context, textMaterial, selectMaterial op.Call
} }
e.showCaret = e.focused && (!blinking || dt%timePerBlink < timePerBlink/2) e.showCaret = e.focused && (!blinking || dt%timePerBlink < timePerBlink/2)
} }
disabled := gtx.Source == nil
semantic.Editor.Add(gtx.Ops) semantic.Editor.Add(gtx.Ops)
if e.Len() > 0 { if e.Len() > 0 {
e.paintSelection(gtx, selectMaterial) e.paintSelection(gtx, selectMaterial)
e.paintText(gtx, textMaterial) e.paintText(gtx, textMaterial)
} }
if !disabled { if gtx.Enabled() {
e.paintCaret(gtx, textMaterial) e.paintCaret(gtx, textMaterial)
} }
return visibleDims return visibleDims
+10 -10
View File
@@ -103,7 +103,7 @@ func TestEditorReadOnly(t *testing.T) {
Max: image.Pt(100, 100), Max: image.Pt(100, 100),
}, },
Locale: english, Locale: english,
Source: r, Source: r.Source(),
} }
cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection())) cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection()))
fontSize := unit.Sp(10) fontSize := unit.Sp(10)
@@ -505,7 +505,7 @@ func TestEditorDimensions(t *testing.T) {
gtx := layout.Context{ gtx := layout.Context{
Ops: new(op.Ops), Ops: new(op.Ops),
Constraints: layout.Constraints{Max: image.Pt(100, 100)}, Constraints: layout.Constraints{Max: image.Pt(100, 100)},
Source: r, Source: r.Source(),
Locale: english, Locale: english,
} }
cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection())) cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection()))
@@ -891,7 +891,7 @@ g 2 4 6 8 g
gtx := layout.Context{ gtx := layout.Context{
Ops: new(op.Ops), Ops: new(op.Ops),
Locale: english, Locale: english,
Source: r, Source: r.Source(),
} }
cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection())) cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection()))
font := font.Font{} font := font.Font{}
@@ -900,13 +900,13 @@ g 2 4 6 8 g
var tim time.Duration var tim time.Duration
selected := func(start, end int) string { selected := func(start, end int) string {
// Layout once with no events; populate e.lines. // Layout once with no events; populate e.lines.
gtx.Source = nil gtx = gtx.Disabled()
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{}) e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
e.Events() // throw away any events from this layout e.Events() // throw away any events from this layout
e.Focus() e.Focus()
r.Frame(gtx.Ops) r.Frame(gtx.Ops)
gtx.Source = r gtx.Source = r.Source()
// Build the selection events // Build the selection events
startPos := e.text.closestToRune(start) startPos := e.text.closestToRune(start)
endPos := e.text.closestToRune(end) endPos := e.text.closestToRune(end)
@@ -972,7 +972,7 @@ g 2 4 6 8 g
// Constrain the editor to roughly 6 columns wide and redraw // Constrain the editor to roughly 6 columns wide and redraw
gtx.Constraints = layout.Exact(image.Pt(36, 36)) gtx.Constraints = layout.Exact(image.Pt(36, 36))
// Keep existing selection // Keep existing selection
gtx.Source = nil gtx = gtx.Disabled()
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{}) e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
caretStart := e.text.closestToRune(e.text.caret.start) caretStart := e.text.closestToRune(e.text.caret.start)
@@ -991,7 +991,7 @@ func TestSelectMove(t *testing.T) {
gtx := layout.Context{ gtx := layout.Context{
Ops: new(op.Ops), Ops: new(op.Ops),
Locale: english, Locale: english,
Source: r, Source: r.Source(),
} }
cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection())) cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection()))
font := font.Font{} font := font.Font{}
@@ -1080,7 +1080,7 @@ func TestEditor_MaxLen(t *testing.T) {
gtx := layout.Context{ gtx := layout.Context{
Ops: new(op.Ops), Ops: new(op.Ops),
Constraints: layout.Exact(image.Pt(100, 100)), Constraints: layout.Exact(image.Pt(100, 100)),
Source: r, Source: r.Source(),
} }
cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection())) cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection()))
fontSize := unit.Sp(10) fontSize := unit.Sp(10)
@@ -1116,7 +1116,7 @@ func TestEditor_Filter(t *testing.T) {
gtx := layout.Context{ gtx := layout.Context{
Ops: new(op.Ops), Ops: new(op.Ops),
Constraints: layout.Exact(image.Pt(100, 100)), Constraints: layout.Exact(image.Pt(100, 100)),
Source: r, Source: r.Source(),
} }
cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection())) cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection()))
fontSize := unit.Sp(10) fontSize := unit.Sp(10)
@@ -1146,7 +1146,7 @@ func TestEditor_Submit(t *testing.T) {
gtx := layout.Context{ gtx := layout.Context{
Ops: new(op.Ops), Ops: new(op.Ops),
Constraints: layout.Exact(image.Pt(100, 100)), Constraints: layout.Exact(image.Pt(100, 100)),
Source: r, Source: r.Source(),
} }
cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection())) cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection()))
fontSize := unit.Sp(10) fontSize := unit.Sp(10)
+4 -5
View File
@@ -40,13 +40,13 @@ func (e *Enum) index(k string) *enumKey {
// Update the state and report whether Value has changed by user interaction. // Update the state and report whether Value has changed by user interaction.
func (e *Enum) Update(gtx layout.Context) bool { func (e *Enum) Update(gtx layout.Context) bool {
if gtx.Source == nil { if !gtx.Enabled() {
e.focused = false e.focused = false
} }
e.hovering = false e.hovering = false
changed := false changed := false
for _, state := range e.keys { for _, state := range e.keys {
for _, ev := range state.click.Update(gtx) { for _, ev := range state.click.Update(gtx.Source) {
switch ev.Kind { switch ev.Kind {
case gesture.KindPress: case gesture.KindPress:
if ev.Source == pointer.Mouse { if ev.Source == pointer.Mouse {
@@ -117,12 +117,11 @@ func (e *Enum) Layout(gtx layout.Context, k string, content layout.Widget) layou
} }
clk := &state.click clk := &state.click
clk.Add(gtx.Ops) clk.Add(gtx.Ops)
enabled := gtx.Source != nil if gtx.Enabled() {
if enabled {
key.InputOp{Tag: &state.tag, Keys: "⏎|Space"}.Add(gtx.Ops) key.InputOp{Tag: &state.tag, Keys: "⏎|Space"}.Add(gtx.Ops)
} }
semantic.SelectedOp(k == e.Value).Add(gtx.Ops) semantic.SelectedOp(k == e.Value).Add(gtx.Ops)
semantic.EnabledOp(enabled).Add(gtx.Ops) semantic.EnabledOp(gtx.Enabled()).Add(gtx.Ops)
c.Add(gtx.Ops) c.Add(gtx.Ops)
return dims return dims
+2 -2
View File
@@ -25,7 +25,7 @@ func ExampleClickable_passthrough() {
gtx := layout.Context{ gtx := layout.Context{
Ops: new(op.Ops), Ops: new(op.Ops),
Constraints: layout.Exact(image.Pt(100, 100)), Constraints: layout.Exact(image.Pt(100, 100)),
Source: &r, Source: r.Source(),
} }
// widget lays out two buttons on top of each other. // widget lays out two buttons on top of each other.
@@ -75,7 +75,7 @@ func ExampleDraggable_Layout() {
gtx := layout.Context{ gtx := layout.Context{
Ops: new(op.Ops), Ops: new(op.Ops),
Constraints: layout.Exact(image.Pt(100, 100)), Constraints: layout.Exact(image.Pt(100, 100)),
Source: &r, Source: r.Source(),
} }
// mime is the type used to match drag and drop operations. // mime is the type used to match drag and drop operations.
// It could be left empty in this example. // It could be left empty in this example.
+1 -1
View File
@@ -48,7 +48,7 @@ func (f *Float) Layout(gtx layout.Context, axis layout.Axis, pointerMargin unit.
// The range of f is set by the minimum constraints main axis value. // The range of f is set by the minimum constraints main axis value.
func (f *Float) Update(gtx layout.Context) bool { func (f *Float) Update(gtx layout.Context) bool {
changed := false changed := false
for _, e := range f.drag.Update(gtx.Metric, gtx, gesture.Axis(f.axis)) { for _, e := range f.drag.Update(gtx.Metric, gtx.Source, gesture.Axis(f.axis)) {
if f.length > 0 && (e.Kind == pointer.Press || e.Kind == pointer.Drag) { if f.length > 0 && (e.Kind == pointer.Press || e.Kind == pointer.Drag) {
pos := e.Position.X pos := e.Position.X
if f.axis == layout.Vertical { if f.axis == layout.Vertical {
+3 -3
View File
@@ -61,7 +61,7 @@ func (s *Scrollbar) Update(gtx layout.Context, axis layout.Axis, viewportStart,
} }
// Jump to a click in the track. // Jump to a click in the track.
for _, event := range s.track.Update(gtx) { for _, event := range s.track.Update(gtx.Source) {
if event.Kind != gesture.KindClick || if event.Kind != gesture.KindClick ||
event.Modifiers != key.Modifiers(0) || event.Modifiers != key.Modifiers(0) ||
event.NumClicks > 1 { event.NumClicks > 1 {
@@ -80,7 +80,7 @@ func (s *Scrollbar) Update(gtx layout.Context, axis layout.Axis, viewportStart,
} }
// Offset to account for any drags. // Offset to account for any drags.
for _, event := range s.drag.Update(gtx.Metric, gtx, gesture.Axis(axis)) { for _, event := range s.drag.Update(gtx.Metric, gtx.Source, gesture.Axis(axis)) {
switch event.Kind { switch event.Kind {
case pointer.Drag: case pointer.Drag:
case pointer.Release, pointer.Cancel: case pointer.Release, pointer.Cancel:
@@ -136,7 +136,7 @@ func (s *Scrollbar) Update(gtx layout.Context, axis layout.Axis, viewportStart,
// Process events from the indicator so that hover is // Process events from the indicator so that hover is
// detected properly. // detected properly.
_ = s.indicator.Update(gtx) _ = s.indicator.Update(gtx.Source)
} }
// AddTrack configures the track click listener for the scrollbar to use // AddTrack configures the track click listener for the scrollbar to use
+2 -2
View File
@@ -133,7 +133,7 @@ func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Di
defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop() defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop()
background := b.Background background := b.Background
switch { switch {
case gtx.Source == nil: case !gtx.Enabled():
background = f32color.Disabled(b.Background) background = f32color.Disabled(b.Background)
case b.Button.Hovered() || b.Button.Focused(): case b.Button.Hovered() || b.Button.Focused():
background = f32color.Hovered(b.Background) background = f32color.Hovered(b.Background)
@@ -165,7 +165,7 @@ func (b IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop() defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop()
background := b.Background background := b.Background
switch { switch {
case gtx.Source == nil: case !gtx.Enabled():
background = f32color.Disabled(b.Background) background = f32color.Disabled(b.Background)
case b.Button.Hovered() || b.Button.Focused(): case b.Button.Hovered() || b.Button.Focused():
background = f32color.Hovered(b.Background) background = f32color.Hovered(b.Background)
+1 -1
View File
@@ -60,7 +60,7 @@ func (c *checkable) layout(gtx layout.Context, checked, hovered bool) layout.Dim
return layout.UniformInset(2).Layout(gtx, func(gtx layout.Context) layout.Dimensions { return layout.UniformInset(2).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
size := gtx.Dp(c.Size) size := gtx.Dp(c.Size)
col := c.IconColor col := c.IconColor
if gtx.Source == nil { if !gtx.Enabled() {
col = f32color.Disabled(col) col = f32color.Disabled(col)
} }
gtx.Constraints.Min = image.Point{X: size} gtx.Constraints.Min = image.Point{X: size}
+1 -1
View File
@@ -61,7 +61,7 @@ func (e EditorStyle) Layout(gtx layout.Context) layout.Dimensions {
paint.ColorOp{Color: e.HintColor}.Add(gtx.Ops) paint.ColorOp{Color: e.HintColor}.Add(gtx.Ops)
hintColor := hintColorMacro.Stop() hintColor := hintColorMacro.Stop()
selectionColorMacro := op.Record(gtx.Ops) selectionColorMacro := op.Record(gtx.Ops)
paint.ColorOp{Color: blendDisabledColor(gtx.Source == nil, e.SelectionColor)}.Add(gtx.Ops) paint.ColorOp{Color: blendDisabledColor(!gtx.Enabled(), e.SelectionColor)}.Add(gtx.Ops)
selectionColor := selectionColorMacro.Stop() selectionColor := selectionColorMacro.Stop()
var maxlines int var maxlines int
+1 -1
View File
@@ -51,7 +51,7 @@ func (p ProgressBarStyle) Layout(gtx layout.Context) layout.Dimensions {
layout.Stacked(func(gtx layout.Context) layout.Dimensions { layout.Stacked(func(gtx layout.Context) layout.Dimensions {
fillWidth := int(float32(progressBarWidth) * clamp1(p.Progress)) fillWidth := int(float32(progressBarWidth) * clamp1(p.Progress))
fillColor := p.Color fillColor := p.Color
if gtx.Source == nil { if !gtx.Enabled() {
fillColor = f32color.Disabled(fillColor) fillColor = f32color.Disabled(fillColor)
} }
return shader(fillWidth, fillColor) return shader(fillWidth, fillColor)
+1 -1
View File
@@ -56,7 +56,7 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
trans.Pop() trans.Pop()
color := s.Color color := s.Color
if gtx.Source == nil { if !gtx.Enabled() {
color = f32color.Disabled(color) color = f32color.Disabled(color)
} }
+1 -1
View File
@@ -55,7 +55,7 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
if s.Switch.Value { if s.Switch.Value {
col = s.Color.Enabled col = s.Color.Enabled
} }
if gtx.Source == nil { if !gtx.Enabled() {
col = f32color.Disabled(col) col = f32color.Disabled(col)
} }
trackColor := s.Color.Track trackColor := s.Color.Track
+2 -2
View File
@@ -298,10 +298,10 @@ func (e *Selectable) processPointer(gtx layout.Context) {
func (e *Selectable) clickDragEvents(gtx layout.Context) []event.Event { func (e *Selectable) clickDragEvents(gtx layout.Context) []event.Event {
var combinedEvents []event.Event var combinedEvents []event.Event
for _, evt := range e.clicker.Update(gtx) { for _, evt := range e.clicker.Update(gtx.Source) {
combinedEvents = append(combinedEvents, evt) combinedEvents = append(combinedEvents, evt)
} }
for _, evt := range e.dragger.Update(gtx.Metric, gtx, gesture.Both) { for _, evt := range e.dragger.Update(gtx.Metric, gtx.Source, gesture.Both) {
combinedEvents = append(combinedEvents, evt) combinedEvents = append(combinedEvents, evt)
} }
return combinedEvents return combinedEvents
+1 -1
View File
@@ -38,7 +38,7 @@ func TestSelectableMove(t *testing.T) {
gtx := layout.Context{ gtx := layout.Context{
Ops: new(op.Ops), Ops: new(op.Ops),
Locale: english, Locale: english,
Source: r, Source: r.Source(),
} }
cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection())) cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection()))
fnt := font.Font{} fnt := font.Font{}
+1 -1
View File
@@ -22,7 +22,7 @@ func TestBool(t *testing.T) {
r input.Router r input.Router
b widget.Bool b widget.Bool
) )
gtx := app.NewContext(&ops, app.FrameEvent{Source: &r}) gtx := app.NewContext(&ops, app.FrameEvent{Source: r.Source()})
layout := func() { layout := func() {
b.Layout(gtx, func(gtx layout.Context) layout.Dimensions { b.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
semantic.CheckBox.Add(gtx.Ops) semantic.CheckBox.Add(gtx.Ops)