forked from joejulian/gio
io/input,widget: [API] replace per-widget Focused with Source.Focused
Widgets have themselves as tags, by convention, and so it's possible to replace the per-widget Focused methods with a general-purpose Source. Focused query. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -318,8 +318,8 @@ func TestKeyRouting(t *testing.T) {
|
|||||||
|
|
||||||
func assertFocus(t *testing.T, router *Router, expected event.Tag) {
|
func assertFocus(t *testing.T, router *Router, expected event.Tag) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if got := router.state().focus; got != expected {
|
if !router.Source().Focused(expected) {
|
||||||
t.Errorf("expected %v to be focused, got %v", expected, got)
|
t.Errorf("expected %v to be focused", expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,15 @@ func (s Source) Enabled() bool {
|
|||||||
return s.r != nil
|
return s.r != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Focused reports whether tag is focused, according to the most recent
|
||||||
|
// [key.FocusEvent] delivered.
|
||||||
|
func (s Source) Focused(tag event.Tag) bool {
|
||||||
|
if !s.Enabled() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s.r.state().keyState.focus == tag
|
||||||
|
}
|
||||||
|
|
||||||
// Event returns the next event that matches at least one of filters.
|
// Event returns the next event that matches at least one of filters.
|
||||||
func (s Source) Event(filters ...event.Filter) (event.Event, bool) {
|
func (s Source) Event(filters ...event.Filter) (event.Event, bool) {
|
||||||
if !s.Enabled() {
|
if !s.Enabled() {
|
||||||
|
|||||||
+2
-7
@@ -16,7 +16,7 @@ type Bool struct {
|
|||||||
// Update the widget state and report whether Value was changed.
|
// Update the widget state and report whether Value was changed.
|
||||||
func (b *Bool) Update(gtx layout.Context) bool {
|
func (b *Bool) Update(gtx layout.Context) bool {
|
||||||
changed := false
|
changed := false
|
||||||
for b.clk.Clicked(gtx) {
|
for b.clk.clicked(b, gtx) {
|
||||||
b.Value = !b.Value
|
b.Value = !b.Value
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
@@ -33,18 +33,13 @@ func (b *Bool) Pressed() bool {
|
|||||||
return b.clk.Pressed()
|
return b.clk.Pressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focused reports whether b has focus.
|
|
||||||
func (b *Bool) Focused() bool {
|
|
||||||
return b.clk.Focused()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bool) History() []Press {
|
func (b *Bool) History() []Press {
|
||||||
return b.clk.History()
|
return b.clk.History()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bool) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
|
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(b, gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
semantic.SelectedOp(b.Value).Add(gtx.Ops)
|
semantic.SelectedOp(b.Value).Add(gtx.Ops)
|
||||||
semantic.EnabledOp(gtx.Enabled()).Add(gtx.Ops)
|
semantic.EnabledOp(gtx.Enabled()).Add(gtx.Ops)
|
||||||
return w(gtx)
|
return w(gtx)
|
||||||
|
|||||||
+21
-19
@@ -22,7 +22,6 @@ type Clickable struct {
|
|||||||
history []Press
|
history []Press
|
||||||
|
|
||||||
requestClicks int
|
requestClicks int
|
||||||
focused bool
|
|
||||||
pressedKey key.Name
|
pressedKey key.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +51,11 @@ func (b *Clickable) Click() {
|
|||||||
|
|
||||||
// Clicked calls Update and reports whether a click was registered.
|
// Clicked calls Update and reports whether a click was registered.
|
||||||
func (b *Clickable) Clicked(gtx layout.Context) bool {
|
func (b *Clickable) Clicked(gtx layout.Context) bool {
|
||||||
_, clicked := b.Update(gtx)
|
return b.clicked(b, gtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Clickable) clicked(t event.Tag, gtx layout.Context) bool {
|
||||||
|
_, clicked := b.update(t, gtx)
|
||||||
return clicked
|
return clicked
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,11 +69,6 @@ func (b *Clickable) Pressed() bool {
|
|||||||
return b.click.Pressed()
|
return b.click.Pressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focused reports whether b has focus.
|
|
||||||
func (b *Clickable) Focused() bool {
|
|
||||||
return b.focused
|
|
||||||
}
|
|
||||||
|
|
||||||
// History is the past pointer presses useful for drawing markers.
|
// History is the past pointer presses useful for drawing markers.
|
||||||
// History is retained for a short duration (about a second).
|
// History is retained for a short duration (about a second).
|
||||||
func (b *Clickable) History() []Press {
|
func (b *Clickable) History() []Press {
|
||||||
@@ -79,8 +77,12 @@ func (b *Clickable) History() []Press {
|
|||||||
|
|
||||||
// Layout and update the button state.
|
// Layout and update the button state.
|
||||||
func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
|
func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
|
||||||
|
return b.layout(b, gtx, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Clickable) layout(t event.Tag, gtx layout.Context, w layout.Widget) layout.Dimensions {
|
||||||
for {
|
for {
|
||||||
_, ok := b.Update(gtx)
|
_, ok := b.update(t, gtx)
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -91,7 +93,7 @@ func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimension
|
|||||||
defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop()
|
defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop()
|
||||||
semantic.EnabledOp(gtx.Enabled()).Add(gtx.Ops)
|
semantic.EnabledOp(gtx.Enabled()).Add(gtx.Ops)
|
||||||
b.click.Add(gtx.Ops)
|
b.click.Add(gtx.Ops)
|
||||||
event.InputOp(gtx.Ops, b)
|
event.InputOp(gtx.Ops, t)
|
||||||
c.Add(gtx.Ops)
|
c.Add(gtx.Ops)
|
||||||
return dims
|
return dims
|
||||||
}
|
}
|
||||||
@@ -99,9 +101,10 @@ func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimension
|
|||||||
// Update the button state by processing events, and return the next
|
// Update the button state by processing events, and return the next
|
||||||
// click, if any.
|
// click, if any.
|
||||||
func (b *Clickable) Update(gtx layout.Context) (Click, bool) {
|
func (b *Clickable) Update(gtx layout.Context) (Click, bool) {
|
||||||
if !gtx.Enabled() {
|
return b.update(b, gtx)
|
||||||
b.focused = false
|
}
|
||||||
}
|
|
||||||
|
func (b *Clickable) update(t event.Tag, gtx layout.Context) (Click, bool) {
|
||||||
for len(b.history) > 0 {
|
for len(b.history) > 0 {
|
||||||
c := b.history[0]
|
c := b.history[0]
|
||||||
if c.End.IsZero() || gtx.Now.Sub(c.End) < 1*time.Second {
|
if c.End.IsZero() || gtx.Now.Sub(c.End) < 1*time.Second {
|
||||||
@@ -139,7 +142,7 @@ func (b *Clickable) Update(gtx layout.Context) (Click, bool) {
|
|||||||
}
|
}
|
||||||
case gesture.KindPress:
|
case gesture.KindPress:
|
||||||
if e.Source == pointer.Mouse {
|
if e.Source == pointer.Mouse {
|
||||||
gtx.Execute(key.FocusCmd{Tag: b})
|
gtx.Execute(key.FocusCmd{Tag: t})
|
||||||
}
|
}
|
||||||
b.history = append(b.history, Press{
|
b.history = append(b.history, Press{
|
||||||
Position: e.Position,
|
Position: e.Position,
|
||||||
@@ -149,21 +152,20 @@ func (b *Clickable) Update(gtx layout.Context) (Click, bool) {
|
|||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
e, ok := gtx.Event(
|
e, ok := gtx.Event(
|
||||||
key.FocusFilter{Target: b},
|
key.FocusFilter{Target: t},
|
||||||
key.Filter{Focus: b, Name: key.NameReturn},
|
key.Filter{Focus: t, Name: key.NameReturn},
|
||||||
key.Filter{Focus: b, Name: key.NameSpace},
|
key.Filter{Focus: t, Name: key.NameSpace},
|
||||||
)
|
)
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
switch e := e.(type) {
|
switch e := e.(type) {
|
||||||
case key.FocusEvent:
|
case key.FocusEvent:
|
||||||
b.focused = e.Focus
|
if e.Focus {
|
||||||
if !b.focused {
|
|
||||||
b.pressedKey = ""
|
b.pressedKey = ""
|
||||||
}
|
}
|
||||||
case key.Event:
|
case key.Event:
|
||||||
if !b.focused {
|
if !gtx.Focused(t) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if e.Name != key.NameReturn && e.Name != key.NameSpace {
|
if e.Name != key.NameReturn && e.Name != key.NameSpace {
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ func TestClickable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
gtx.Execute(key.FocusCmd{Tag: &b1})
|
gtx.Execute(key.FocusCmd{Tag: &b1})
|
||||||
frame()
|
frame()
|
||||||
if !b1.Focused() {
|
if !gtx.Focused(&b1) {
|
||||||
t.Error("button 1 did not gain focus")
|
t.Error("button 1 did not gain focus")
|
||||||
}
|
}
|
||||||
if b2.Focused() {
|
if gtx.Focused(&b2) {
|
||||||
t.Error("button 2 should not have focus")
|
t.Error("button 2 should not have focus")
|
||||||
}
|
}
|
||||||
r.Queue(
|
r.Queue(
|
||||||
@@ -73,10 +73,10 @@ func TestClickable(t *testing.T) {
|
|||||||
frame()
|
frame()
|
||||||
gtx.Execute(key.FocusCmd{Tag: &b2})
|
gtx.Execute(key.FocusCmd{Tag: &b2})
|
||||||
frame()
|
frame()
|
||||||
if b1.Focused() {
|
if gtx.Focused(&b1) {
|
||||||
t.Error("button 1 should not have focus")
|
t.Error("button 1 should not have focus")
|
||||||
}
|
}
|
||||||
if !b2.Focused() {
|
if !gtx.Focused(&b2) {
|
||||||
t.Error("button 2 did not gain focus")
|
t.Error("button 2 did not gain focus")
|
||||||
}
|
}
|
||||||
r.Queue(
|
r.Queue(
|
||||||
|
|||||||
+22
-31
@@ -72,7 +72,6 @@ type Editor struct {
|
|||||||
// from the textView.
|
// from the textView.
|
||||||
scratch []byte
|
scratch []byte
|
||||||
blinkStart time.Time
|
blinkStart time.Time
|
||||||
focused bool
|
|
||||||
|
|
||||||
// ime tracks the state relevant to input methods.
|
// ime tracks the state relevant to input methods.
|
||||||
ime struct {
|
ime struct {
|
||||||
@@ -385,14 +384,13 @@ func (e *Editor) processKey(gtx layout.Context) {
|
|||||||
e.blinkStart = gtx.Now
|
e.blinkStart = gtx.Now
|
||||||
switch ke := ke.(type) {
|
switch ke := ke.(type) {
|
||||||
case key.FocusEvent:
|
case key.FocusEvent:
|
||||||
e.focused = ke.Focus
|
|
||||||
// Reset IME state.
|
// Reset IME state.
|
||||||
e.ime.imeState = imeState{}
|
e.ime.imeState = imeState{}
|
||||||
if ke.Focus {
|
if ke.Focus {
|
||||||
gtx.Execute(key.SoftKeyboardCmd{Show: true})
|
gtx.Execute(key.SoftKeyboardCmd{Show: true})
|
||||||
}
|
}
|
||||||
case key.Event:
|
case key.Event:
|
||||||
if !e.focused || ke.State != key.Press {
|
if !gtx.Focused(e) || ke.State != key.Press {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !e.ReadOnly && e.Submit && (ke.Name == key.NameReturn || ke.Name == key.NameEnter) {
|
if !e.ReadOnly && e.Submit && (ke.Name == key.NameReturn || ke.Name == key.NameEnter) {
|
||||||
@@ -558,11 +556,6 @@ func (e *Editor) command(gtx layout.Context, k key.Event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focused returns whether the editor is focused or not.
|
|
||||||
func (e *Editor) Focused() bool {
|
|
||||||
return e.focused
|
|
||||||
}
|
|
||||||
|
|
||||||
// initBuffer should be invoked first in every exported function that accesses
|
// initBuffer should be invoked first in every exported function that accesses
|
||||||
// text state. It ensures that the underlying text widget is both ready to use
|
// text state. It ensures that the underlying text widget is both ready to use
|
||||||
// and has its fields synced with the editor.
|
// and has its fields synced with the editor.
|
||||||
@@ -583,27 +576,25 @@ func (e *Editor) initBuffer() {
|
|||||||
func (e *Editor) Update(gtx layout.Context) {
|
func (e *Editor) Update(gtx layout.Context) {
|
||||||
e.initBuffer()
|
e.initBuffer()
|
||||||
e.processEvents(gtx)
|
e.processEvents(gtx)
|
||||||
if e.focused {
|
// Notify IME of selection if it changed.
|
||||||
// Notify IME of selection if it changed.
|
newSel := e.ime.selection
|
||||||
newSel := e.ime.selection
|
start, end := e.text.Selection()
|
||||||
start, end := e.text.Selection()
|
newSel.rng = key.Range{
|
||||||
newSel.rng = key.Range{
|
Start: start,
|
||||||
Start: start,
|
End: end,
|
||||||
End: end,
|
|
||||||
}
|
|
||||||
caretPos, carAsc, carDesc := e.text.CaretInfo()
|
|
||||||
newSel.caret = key.Caret{
|
|
||||||
Pos: layout.FPt(caretPos),
|
|
||||||
Ascent: float32(carAsc),
|
|
||||||
Descent: float32(carDesc),
|
|
||||||
}
|
|
||||||
if newSel != e.ime.selection {
|
|
||||||
e.ime.selection = newSel
|
|
||||||
gtx.Execute(key.SelectionCmd{Tag: e, Range: newSel.rng, Caret: newSel.caret})
|
|
||||||
}
|
|
||||||
|
|
||||||
e.updateSnippet(gtx, e.ime.start, e.ime.end)
|
|
||||||
}
|
}
|
||||||
|
caretPos, carAsc, carDesc := e.text.CaretInfo()
|
||||||
|
newSel.caret = key.Caret{
|
||||||
|
Pos: layout.FPt(caretPos),
|
||||||
|
Ascent: float32(carAsc),
|
||||||
|
Descent: float32(carDesc),
|
||||||
|
}
|
||||||
|
if newSel != e.ime.selection {
|
||||||
|
e.ime.selection = newSel
|
||||||
|
gtx.Execute(key.SelectionCmd{Tag: e, Range: newSel.rng, Caret: newSel.caret})
|
||||||
|
}
|
||||||
|
|
||||||
|
e.updateSnippet(gtx, e.ime.start, e.ime.end)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layout lays out the editor using the provided textMaterial as the paint material
|
// Layout lays out the editor using the provided textMaterial as the paint material
|
||||||
@@ -679,7 +670,7 @@ func (e *Editor) layout(gtx layout.Context, textMaterial, selectMaterial op.Call
|
|||||||
e.clicker.Add(gtx.Ops)
|
e.clicker.Add(gtx.Ops)
|
||||||
e.dragger.Add(gtx.Ops)
|
e.dragger.Add(gtx.Ops)
|
||||||
e.showCaret = false
|
e.showCaret = false
|
||||||
if e.focused {
|
if gtx.Focused(e) {
|
||||||
now := gtx.Now
|
now := gtx.Now
|
||||||
dt := now.Sub(e.blinkStart)
|
dt := now.Sub(e.blinkStart)
|
||||||
blinking := dt < maxBlinkDuration
|
blinking := dt < maxBlinkDuration
|
||||||
@@ -688,7 +679,7 @@ func (e *Editor) layout(gtx layout.Context, textMaterial, selectMaterial op.Call
|
|||||||
if blinking {
|
if blinking {
|
||||||
gtx.Execute(op.InvalidateCmd{At: nextBlink})
|
gtx.Execute(op.InvalidateCmd{At: nextBlink})
|
||||||
}
|
}
|
||||||
e.showCaret = e.focused && (!blinking || dt%timePerBlink < timePerBlink/2)
|
e.showCaret = !blinking || dt%timePerBlink < timePerBlink/2
|
||||||
}
|
}
|
||||||
semantic.Editor.Add(gtx.Ops)
|
semantic.Editor.Add(gtx.Ops)
|
||||||
if e.Len() > 0 {
|
if e.Len() > 0 {
|
||||||
@@ -705,7 +696,7 @@ func (e *Editor) layout(gtx layout.Context, textMaterial, selectMaterial op.Call
|
|||||||
// material to set the painting material for the selection.
|
// material to set the painting material for the selection.
|
||||||
func (e *Editor) paintSelection(gtx layout.Context, material op.CallOp) {
|
func (e *Editor) paintSelection(gtx layout.Context, material op.CallOp) {
|
||||||
e.initBuffer()
|
e.initBuffer()
|
||||||
if !e.focused {
|
if !gtx.Focused(e) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e.text.PaintSelection(gtx, material)
|
e.text.PaintSelection(gtx, material)
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ func Clickable(gtx layout.Context, button *widget.Clickable, w layout.Widget) la
|
|||||||
return layout.Background{}.Layout(gtx,
|
return layout.Background{}.Layout(gtx,
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop()
|
defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop()
|
||||||
if button.Hovered() || button.Focused() {
|
if button.Hovered() || gtx.Focused(button) {
|
||||||
paint.Fill(gtx.Ops, f32color.Hovered(color.NRGBA{}))
|
paint.Fill(gtx.Ops, f32color.Hovered(color.NRGBA{}))
|
||||||
}
|
}
|
||||||
for _, c := range button.History() {
|
for _, c := range button.History() {
|
||||||
@@ -135,7 +135,7 @@ func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Di
|
|||||||
switch {
|
switch {
|
||||||
case !gtx.Enabled():
|
case !gtx.Enabled():
|
||||||
background = f32color.Disabled(b.Background)
|
background = f32color.Disabled(b.Background)
|
||||||
case b.Button.Hovered() || b.Button.Focused():
|
case b.Button.Hovered() || gtx.Focused(b.Button):
|
||||||
background = f32color.Hovered(b.Background)
|
background = f32color.Hovered(b.Background)
|
||||||
}
|
}
|
||||||
paint.Fill(gtx.Ops, background)
|
paint.Fill(gtx.Ops, background)
|
||||||
@@ -167,7 +167,7 @@ func (b IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
|
|||||||
switch {
|
switch {
|
||||||
case !gtx.Enabled():
|
case !gtx.Enabled():
|
||||||
background = f32color.Disabled(b.Background)
|
background = f32color.Disabled(b.Background)
|
||||||
case b.Button.Hovered() || b.Button.Focused():
|
case b.Button.Hovered() || gtx.Focused(b.Button):
|
||||||
background = f32color.Hovered(b.Background)
|
background = f32color.Hovered(b.Background)
|
||||||
}
|
}
|
||||||
paint.Fill(gtx.Ops, background)
|
paint.Fill(gtx.Ops, background)
|
||||||
|
|||||||
@@ -35,6 +35,6 @@ func CheckBox(th *Theme, checkBox *widget.Bool, label string) CheckBoxStyle {
|
|||||||
func (c CheckBoxStyle) Layout(gtx layout.Context) layout.Dimensions {
|
func (c CheckBoxStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||||
return c.CheckBox.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
return c.CheckBox.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
semantic.CheckBox.Add(gtx.Ops)
|
semantic.CheckBox.Add(gtx.Ops)
|
||||||
return c.layout(gtx, c.CheckBox.Value, c.CheckBox.Hovered() || c.CheckBox.Focused())
|
return c.layout(gtx, c.CheckBox.Value, c.CheckBox.Hovered() || gtx.Focused(c.CheckBox))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
|
|||||||
return clip.Ellipse(b).Op(gtx.Ops)
|
return clip.Ellipse(b).Op(gtx.Ops)
|
||||||
}
|
}
|
||||||
// Draw hover.
|
// Draw hover.
|
||||||
if s.Switch.Hovered() || s.Switch.Focused() {
|
if s.Switch.Hovered() || gtx.Focused(s.Switch) {
|
||||||
r := thumbRadius * 10 / 17
|
r := thumbRadius * 10 / 17
|
||||||
background := f32color.MulAlpha(s.Color.Enabled, 70)
|
background := f32color.MulAlpha(s.Color.Enabled, 70)
|
||||||
paint.FillShape(gtx.Ops, background, circle(thumbRadius, thumbRadius, r))
|
paint.FillShape(gtx.Ops, background, circle(thumbRadius, thumbRadius, r))
|
||||||
|
|||||||
Reference in New Issue
Block a user