Skip to content

Commit

Permalink
Merge pull request #5123 from jimorc/issue2836
Browse files Browse the repository at this point in the history
Add string binding to the Selected field of the Select widget
  • Loading branch information
andydotxyz authored Sep 24, 2024
2 parents 56901bb + f9d3fed commit a275574
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 0 deletions.
74 changes: 74 additions & 0 deletions widget/select.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package widget

import (
"fmt"
"image/color"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/theme"
)
Expand All @@ -24,6 +26,8 @@ type Select struct {
PlaceHolder string
OnChanged func(string) `json:"-"`

binder basicBinder

focused bool
hovered bool
popUp *PopUpMenu
Expand All @@ -47,6 +51,30 @@ func NewSelect(options []string, changed func(string)) *Select {
return s
}

// NewSelectWithData returns a new select widget connected to the specified data source.
//
// Since: 2.6
func NewSelectWithData(options []string, data binding.String) *Select {
sel := NewSelect(options, nil)
sel.Bind(data)

return sel
}

// Bind connects the specified data source to this select.
// The current value will be displayed and any changes in the data will cause the widget
// to update.
//
// Since: 2.6
func (s *Select) Bind(data binding.String) {
s.binder.SetCallback(s.updateFromData)
s.binder.Bind(data)

s.OnChanged = func(_ string) {
s.binder.CallWithData(s.writeData)
}
}

// ClearSelected clears the current option of the select widget. After
// clearing the current option, the Select widget's PlaceHolder will
// be displayed.
Expand Down Expand Up @@ -239,6 +267,15 @@ func (s *Select) TypedRune(_ rune) {
// intentionally left blank
}

// Unbind disconnects any configured data source from this Select.
// The current value will remain at the last value of the data source.
//
// Since: 2.6
func (s *Select) Unbind() {
s.OnChanged = nil
s.binder.Unbind()
}

func (s *Select) popUpPos() fyne.Position {
buttonPos := fyne.CurrentApp().Driver().AbsolutePositionForObject(s.super())
return buttonPos.Add(fyne.NewPos(0, s.Size().Height-s.Theme().Size(theme.SizeNameInputBorder)))
Expand Down Expand Up @@ -279,6 +316,23 @@ func (s *Select) tapAnimation() {
}
}

func (s *Select) updateFromData(data binding.DataItem) {
if data == nil {
return
}
stringSource, ok := data.(binding.String)
if !ok {
return
}

val, err := stringSource.Get()
if err != nil {
return
}
s.SetSelected(val)

}

func (s *Select) updateSelected(text string) {
s.Selected = text

Expand All @@ -289,6 +343,26 @@ func (s *Select) updateSelected(text string) {
s.Refresh()
}

func (s *Select) writeData(data binding.DataItem) {
if data == nil {
return
}
stringTarget, ok := data.(binding.String)
if !ok {
return
}
currentValue, err := stringTarget.Get()
if err != nil {
return
}
if currentValue != s.Selected {
err := stringTarget.Set(s.Selected)
if err != nil {
fyne.LogError(fmt.Sprintf("Failed to set binding value to %s", s.Selected), err)
}
}
}

type selectRenderer struct {
icon *Icon
label *RichText
Expand Down
60 changes: 60 additions & 0 deletions widget/select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/internal/cache"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/test"
Expand All @@ -25,6 +26,19 @@ func TestNewSelect(t *testing.T) {
assert.Equal(t, "", combo.Selected)
}

func TestNewSelectWithData(t *testing.T) {
data := binding.NewString()
combo := widget.NewSelectWithData([]string{"1", "2", "3"}, data)

assert.Equal(t, 3, len(combo.Options))
assert.Equal(t, "", combo.Selected)

err := data.Set("2")
assert.Nil(t, err)
waitForBinding()
assert.Equal(t, "2", combo.Selected)
}

func TestSelect_Align(t *testing.T) {
test.NewTempApp(t)

Expand All @@ -43,6 +57,52 @@ func TestSelect_Align(t *testing.T) {
assertRendersToPlatformMarkup(t, "select/%s/trailing.xml", c)
}

func TestSelect_Options(t *testing.T) {
s := widget.NewSelect([]string{"1", "2", "3"}, nil)
s.SetSelected("2")
assert.Equal(t, "2", s.Selected)

s.SetOptions([]string{"4", "5"})
assert.Equal(t, "2", s.Selected)
s.Selected = ""
assert.Equal(t, "", s.Selected)
}

func TestSelect_Binding(t *testing.T) {
s := widget.NewSelect([]string{"1", "2", "3"}, nil)
s.SetSelected("2")
assert.Equal(t, "2", s.Selected)
waitForBinding() // this time it is the de-echo before binding

str := binding.NewString()
s.Bind(str)
waitForBinding()
value, err := str.Get()
assert.Nil(t, err)
assert.Equal(t, "", value)
assert.Equal(t, "2", s.Selected) // no match to options, so keep previous value

err = str.Set("3")
assert.Nil(t, err)
waitForBinding()
assert.Equal(t, "3", s.Selected)

s.Unbind()
assert.Nil(t, s.OnChanged)
err = str.Set("1")
assert.Nil(t, err)
val1, err := str.Get()
assert.Nil(t, err)
assert.Equal(t, "1", val1)
assert.Equal(t, "3", s.Selected)

s.SetSelected("2")
val1, err = str.Get()
assert.Nil(t, err)
assert.Equal(t, "1", val1)
assert.Equal(t, "2", s.Selected)
}

func TestSelect_ChangeTheme(t *testing.T) {
test.NewTempApp(t)

Expand Down

0 comments on commit a275574

Please sign in to comment.