diff --git a/app/settings.go b/app/settings.go index 624d7e4686..d014d68fb2 100644 --- a/app/settings.go +++ b/app/settings.go @@ -1,31 +1,49 @@ package app -import "fyne.io/fyne" +import ( + "sync" + + "fyne.io/fyne" +) type settings struct { - theme fyne.Theme + themeLock sync.RWMutex + theme fyne.Theme + listenerLock sync.Mutex changeListeners []chan fyne.Settings } func (s *settings) Theme() fyne.Theme { + s.themeLock.RLock() + defer s.themeLock.RUnlock() return s.theme } func (s *settings) SetTheme(theme fyne.Theme) { + s.themeLock.Lock() + defer s.themeLock.Unlock() s.theme = theme s.apply() } func (s *settings) AddChangeListener(listener chan fyne.Settings) { + s.listenerLock.Lock() + defer s.listenerLock.Unlock() s.changeListeners = append(s.changeListeners, listener) } func (s *settings) apply() { + s.listenerLock.Lock() + defer s.listenerLock.Unlock() + for _, listener := range s.changeListeners { - go func(listener chan fyne.Settings) { - listener <- s - }(listener) + select { + case listener <- s: + default: + l := listener + go func() { l <- s }() + } } } diff --git a/driver/gl/font.go b/driver/gl/font.go index 856a853558..1e47cdda0d 100644 --- a/driver/gl/font.go +++ b/driver/gl/font.go @@ -101,8 +101,11 @@ type fontCacheItem struct { } var fontCache = make(map[fyne.TextStyle]*fontCacheItem) +var fontCacheLock = new(sync.Mutex) func cachedFontFace(style fyne.TextStyle, opts *truetype.Options) font.Face { + fontCacheLock.Lock() + defer fontCacheLock.Unlock() comp := fontCache[style] if comp == nil { @@ -144,6 +147,8 @@ func cachedFontFace(style fyne.TextStyle, opts *truetype.Options) font.Face { } func clearFontCache() { + fontCacheLock.Lock() + defer fontCacheLock.Unlock() for _, item := range fontCache { for _, face := range item.faces { err := face.Close() diff --git a/driver/gl/window.go b/driver/gl/window.go index ff99d4056f..7b4073dad8 100644 --- a/driver/gl/window.go +++ b/driver/gl/window.go @@ -216,6 +216,8 @@ func (w *window) SetIcon(icon fyne.Resource) { } func (w *window) fitContent() { + w.canvas.Lock() + defer w.canvas.Unlock() if w.canvas.content == nil { return } diff --git a/widget/radio.go b/widget/radio.go index 487dd60028..c6bc4e8040 100644 --- a/widget/radio.go +++ b/widget/radio.go @@ -20,6 +20,20 @@ type radioRenderer struct { radio *Radio } +func removeDuplicates(options []string) []string { + var result []string + found := make(map[string]bool) + + for _, option := range options { + if _, ok := found[option]; !ok { + found[option] = true + result = append(result, option) + } + } + + return result +} + // MinSize calculates the minimum size of a radio item. // This is based on the contained text, the radio icon and a standard amount of padding // between each item. @@ -69,6 +83,8 @@ func (r *radioRenderer) BackgroundColor() color.Color { } func (r *radioRenderer) Refresh() { + r.radio.removeDuplicateOptions() + if len(r.items) < len(r.radio.Options) { for i := len(r.items); i < len(r.radio.Options); i++ { option := r.radio.Options[i] @@ -194,10 +210,25 @@ func (r *Radio) CreateRenderer() fyne.WidgetRenderer { return &radioRenderer{items, objects, r} } +// SetSelected sets the radio option, it can be used to set a default option. +func (r *Radio) SetSelected(option string) { + if r.Selected == option { + return + } + + r.Selected = option + + Renderer(r).Refresh() +} + func (r *Radio) itemHeight() int { return r.MinSize().Height / len(r.Options) } +func (r *Radio) removeDuplicateOptions() { + r.Options = removeDuplicates(r.Options) +} + // NewRadio creates a new radio widget with the set options and change handler func NewRadio(options []string, changed func(string)) *Radio { r := &Radio{ @@ -207,6 +238,8 @@ func NewRadio(options []string, changed func(string)) *Radio { changed, } + r.removeDuplicateOptions() + Renderer(r).Layout(r.MinSize()) return r } diff --git a/widget/radio_test.go b/widget/radio_test.go index 94cfac835f..34b27c6a5b 100644 --- a/widget/radio_test.go +++ b/widget/radio_test.go @@ -16,7 +16,7 @@ func TestRadio_MinSize(t *testing.T) { assert.True(t, min.Width > theme.Padding()*2) assert.True(t, min.Height > theme.Padding()*2) - radio2 := NewRadio([]string{"Hi", "Hi"}, nil) + radio2 := NewRadio([]string{"Hi", "He"}, nil) min2 := radio2.MinSize() assert.Equal(t, min.Width, min2.Width) @@ -92,3 +92,49 @@ func TestRadio_Remove(t *testing.T) { assert.Equal(t, 1, len(radio.Options)) assert.Equal(t, 1, len(Renderer(radio).(*radioRenderer).items)) } + +func TestRadio_SetSelected(t *testing.T) { + radio := NewRadio([]string{"Hi", "Another"}, nil) + + radio.SetSelected("Another") + + assert.Equal(t, "Another", radio.Selected) +} + +func TestRadio_SetSelectedWithSameOption(t *testing.T) { + radio := NewRadio([]string{"Hi", "Another"}, nil) + + radio.Selected = "Another" + Refresh(radio) + + radio.SetSelected("Another") + + assert.Equal(t, "Another", radio.Selected) +} + +func TestRadio_SetSelectedEmpty(t *testing.T) { + radio := NewRadio([]string{"Hi", "Another"}, nil) + + radio.Selected = "Another" + Refresh(radio) + + radio.SetSelected("") + + assert.Equal(t, "", radio.Selected) +} + +func TestRadio_DuplicatedOptions(t *testing.T) { + radio := NewRadio([]string{"Hi", "Hi", "Hi", "Another", "Another"}, nil) + + assert.Equal(t, 2, len(radio.Options)) + assert.Equal(t, 2, len(Renderer(radio).(*radioRenderer).items)) +} + +func TestRadio_AppendDuplicate(t *testing.T) { + radio := NewRadio([]string{"Hi"}, nil) + + radio.Append("Hi") + + assert.Equal(t, 1, len(radio.Options)) + assert.Equal(t, 1, len(Renderer(radio).(*radioRenderer).items)) +}