Skip to content

Commit

Permalink
Merge pull request #1445 from toaster/feature/deactivateable_select
Browse files Browse the repository at this point in the history
Feature: deactivateable select
  • Loading branch information
toaster committed Oct 24, 2020
2 parents 25af403 + 7a3d6e0 commit d351e63
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 17 deletions.
66 changes: 49 additions & 17 deletions widget/select.go
Expand Up @@ -15,7 +15,7 @@ const defaultPlaceHolder string = "(Select one)"

// Select widget has a list of options, with the current one shown, and triggers an event func when clicked
type Select struct {
BaseWidget
DisableableWidget

Selected string
Options []string
Expand All @@ -32,6 +32,9 @@ var _ fyne.Widget = (*Select)(nil)
var _ desktop.Hoverable = (*Select)(nil)
var _ fyne.Tappable = (*Select)(nil)
var _ fyne.Focusable = (*Select)(nil)
var _ fyne.Disableable = (*Select)(nil)

var _ textPresenter = (*Select)(nil)

// NewSelect creates a new select widget with the set list of options and changes handler
func NewSelect(options []string, changed func(string)) *Select {
Expand All @@ -57,21 +60,15 @@ func (s *Select) CreateRenderer() fyne.WidgetRenderer {
s.propertyLock.RLock()
defer s.propertyLock.RUnlock()
icon := NewIcon(theme.MenuDropDownIcon())
text := NewLabel(s.Selected)
text.TextStyle.Bold = true
text.Wrapping = fyne.TextTruncate

if s.PlaceHolder == "" {
s.PlaceHolder = defaultPlaceHolder
}
if s.Selected == "" {
text.Text = s.PlaceHolder
}
text.Alignment = fyne.TextAlignLeading
txtProv := newTextProvider(s.Selected, s)
txtProv.ExtendBaseWidget(txtProv)

bg := canvas.NewRectangle(color.Transparent)
objects := []fyne.CanvasObject{bg, text, icon}
r := &selectRenderer{widget.NewShadowingRenderer(objects, widget.ButtonLevel), icon, text, bg, s}
objects := []fyne.CanvasObject{bg, txtProv, icon}
r := &selectRenderer{widget.NewShadowingRenderer(objects, widget.ButtonLevel), icon, txtProv, bg, s}
bg.FillColor = r.buttonColor()
r.updateLabel()
r.updateIcon()
Expand Down Expand Up @@ -188,6 +185,10 @@ func (s *Select) SetSelectedIndex(index int) {

// Tapped is called when a pointer tapped event is captured and triggers any tap handler
func (s *Select) Tapped(*fyne.PointEvent) {
if s.Disabled() {
return
}

s.tapped = true
defer func() { // TODO move to a real animation
time.Sleep(time.Millisecond * buttonTapDuration)
Expand Down Expand Up @@ -228,6 +229,14 @@ func (s *Select) TypedRune(_ rune) {
// intentionally left blank
}

func (s *Select) concealed() bool {
return false
}

func (s *Select) object() fyne.Widget {
return nil
}

func (s *Select) optionTapped(text string) {
s.SetSelected(text)
s.popUp = nil
Expand All @@ -254,6 +263,25 @@ func (s *Select) showPopUp() {
s.popUp.Resize(fyne.NewSize(s.Size().Width-theme.Padding()*2, s.popUp.MinSize().Height))
}

func (s *Select) textAlign() fyne.TextAlign {
return fyne.TextAlignLeading
}

func (s *Select) textColor() color.Color {
if s.Disabled() {
return theme.DisabledTextColor()
}
return theme.TextColor()
}

func (s *Select) textStyle() fyne.TextStyle {
return fyne.TextStyle{Bold: true}
}

func (s *Select) textWrap() fyne.TextWrap {
return fyne.TextTruncate
}

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

Expand All @@ -268,7 +296,7 @@ type selectRenderer struct {
*widget.ShadowingRenderer

icon *Icon
label *Label
label *textProvider
bg *canvas.Rectangle
combo *Select
}
Expand Down Expand Up @@ -307,7 +335,7 @@ func (s *selectRenderer) MinSize() fyne.Size {
s.combo.propertyLock.RLock()
defer s.combo.propertyLock.RUnlock()

min := fyne.MeasureText(s.combo.PlaceHolder, theme.TextSize(), s.label.TextStyle)
min := fyne.MeasureText(s.combo.PlaceHolder, theme.TextSize(), s.combo.textStyle())

min = min.Add(fyne.NewSize(theme.Padding()*6, theme.Padding()*4))
return min.Add(fyne.NewSize(theme.IconInlineSize()+theme.Padding(), 0))
Expand All @@ -329,6 +357,9 @@ func (s *selectRenderer) Refresh() {
}

func (s *selectRenderer) buttonColor() color.Color {
if s.combo.Disabled() {
return theme.ButtonColor()
}
if s.combo.focused {
return theme.FocusColor()
}
Expand All @@ -339,11 +370,12 @@ func (s *selectRenderer) buttonColor() color.Color {
}

func (s *selectRenderer) updateIcon() {
if false { // s.combo.down {
s.icon.Resource = theme.MenuDropUpIcon()
if s.combo.Disabled() {
s.icon.Resource = theme.NewDisabledResource(theme.MenuDropDownIcon())
} else {
s.icon.Resource = theme.MenuDropDownIcon()
}
s.icon.Refresh()
}

func (s *selectRenderer) updateLabel() {
Expand All @@ -352,8 +384,8 @@ func (s *selectRenderer) updateLabel() {
}

if s.combo.Selected == "" {
s.label.SetText(s.combo.PlaceHolder)
s.label.setText(s.combo.PlaceHolder)
} else {
s.label.SetText(s.combo.Selected)
s.label.setText(s.combo.Selected)
}
}
60 changes: 60 additions & 0 deletions widget/select_test.go
Expand Up @@ -11,6 +11,7 @@ import (
"fyne.io/fyne/widget"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewSelect(t *testing.T) {
Expand Down Expand Up @@ -67,6 +68,51 @@ func TestSelect_ClearSelected(t *testing.T) {
assert.Equal(t, optClear, triggeredValue)
}

func TestSelect_Disable(t *testing.T) {
app := test.NewApp()
defer test.NewApp()
app.Settings().SetTheme(theme.LightTheme())

sel := widget.NewSelect([]string{"Hi"}, func(string) {})
w := test.NewWindow(fyne.NewContainerWithLayout(layout.NewCenterLayout(), sel))
defer w.Close()
w.Resize(fyne.NewSize(200, 150))
c := fyne.CurrentApp().Driver().CanvasForObject(sel)

sel.Disable()
test.AssertImageMatches(t, "select/disabled.png", w.Canvas().Capture())
test.Tap(sel)
assert.Nil(t, c.Overlays().Top(), "no pop-up for disabled Select")
test.AssertImageMatches(t, "select/disabled.png", w.Canvas().Capture())
}

func TestSelect_Disabled(t *testing.T) {
sel := widget.NewSelect([]string{"Hi"}, func(string) {})
assert.False(t, sel.Disabled())
sel.Disable()
assert.True(t, sel.Disabled())
sel.Enable()
assert.False(t, sel.Disabled())
}

func TestSelect_Enable(t *testing.T) {
selected := ""
sel := widget.NewSelect([]string{"Hi"}, func(sel string) {
selected = sel
})
sel.Disable()
require.True(t, sel.Disabled())

sel.Enable()
test.Tap(sel)
c := fyne.CurrentApp().Driver().CanvasForObject(sel)
ovl := c.Overlays().Top()
if assert.NotNil(t, ovl, "pop-up for enabled Select") {
test.TapCanvas(c, ovl.Position().Add(fyne.NewPos(theme.Padding()*2, theme.Padding()*2)))
assert.Equal(t, "Hi", selected, "Radio should have been re-enabled.")
}
}

func TestSelect_FocusRendering(t *testing.T) {
test.NewApp()
defer test.NewApp()
Expand All @@ -93,6 +139,20 @@ func TestSelect_FocusRendering(t *testing.T) {
c.Unfocus()
test.AssertImageMatches(t, "select/focus_unfocused_b_selected.png", c.Capture())
})
t.Run("disable/enable focused", func(t *testing.T) {
sel := widget.NewSelect([]string{"Option A", "Option B", "Option C"}, nil)
w := test.NewWindow(fyne.NewContainerWithLayout(layout.NewCenterLayout(), sel))
defer w.Close()
w.Resize(fyne.NewSize(200, 150))

c := w.Canvas().(test.WindowlessCanvas)
c.FocusNext()
test.AssertImageMatches(t, "select/focus_focused_none_selected.png", c.Capture())
sel.Disable()
test.AssertImageMatches(t, "select/disabled.png", c.Capture())
sel.Enable()
test.AssertImageMatches(t, "select/focus_focused_none_selected.png", c.Capture())
})
}

func TestSelect_KeyboardControl(t *testing.T) {
Expand Down
Binary file added widget/testdata/select/disabled.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit d351e63

Please sign in to comment.