From af9c2b29b12898cc4b975636bab0952e835349ed Mon Sep 17 00:00:00 2001 From: TehSphinX Date: Tue, 16 Feb 2021 15:38:48 +0100 Subject: [PATCH 1/6] simple widget interface, base implementation incl. simple renderer --- widget/example/simple_widget.go | 47 +++++++++++++ widget/simple_renderer.go | 57 ++++++++++++++++ widget/simple_widget.go | 117 ++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+) create mode 100644 widget/example/simple_widget.go create mode 100644 widget/simple_renderer.go create mode 100644 widget/simple_widget.go diff --git a/widget/example/simple_widget.go b/widget/example/simple_widget.go new file mode 100644 index 00000000..154f5e19 --- /dev/null +++ b/widget/example/simple_widget.go @@ -0,0 +1,47 @@ +package example + +import ( + "sync" + + "fyne.io/fyne/v2" + "fyne.io/x/fyne/widget" +) + +// SampleWidget is a sample widget demonstrating the base structure of a +// widget implementing SimpleWidget using SimpleWidgetBase. +type SampleWidget struct { + widget.SimpleWidgetBase + propertyLock sync.RWMutex + + Property int +} + +// NewSampleWidget creates a new sample widget. +func NewSampleWidget(property int) *SampleWidget { + return &SampleWidget{Property: property} +} + +// Render renders the SampleWidget. +func (s *SampleWidget) Render() (objects []fyne.CanvasObject, layout func(size fyne.Size)) { + // create objects needed for rendering and append them to the objects slice. + // (executed in Widget.CreateRenderer function) + + return objects, func(size fyne.Size) { + // position, resize or otherwise update objects. (executed in WidgetRenderer.Layout) + } +} + +// SetStateSafe sets or changes the state of a widget in a safe way. A Refresh +// is triggered after the state changes have been applied. +func (s *SampleWidget) SetStateSafe(setState func()) { + s.SimpleWidgetBase.SetStateSafe(&s.propertyLock, setState) +} + +func (s *SampleWidget) getProperty() int { + s.propertyLock.RLock() + defer s.propertyLock.RUnlock() + + return s.Property +} + +// All other methods of a fyne.Widget can be overwritten as usual. E.g. MinSize, Resize, Refresh, etc. diff --git a/widget/simple_renderer.go b/widget/simple_renderer.go new file mode 100644 index 00000000..426f95a7 --- /dev/null +++ b/widget/simple_renderer.go @@ -0,0 +1,57 @@ +package widget + +import ( + "fyne.io/fyne/v2" +) + +// newSimpleRenderer creates a new simpleRenderer. +func newSimpleRenderer(widget SimpleWidget, objects []fyne.CanvasObject, layout func(size fyne.Size)) *simpleRenderer { + return &simpleRenderer{ + widget: widget, + objects: objects, + layout: layout, + } +} + +// simpleRenderer is a renderer providing the basic rendering functionality used +// by SimpleWidgetBase. +type simpleRenderer struct { + widget fyne.Widget + objects []fyne.CanvasObject + + layout func(size fyne.Size) +} + +// MinSize returns the minimum size of the widget that is rendered by this renderer. +func (s *simpleRenderer) MinSize() fyne.Size { + return fyne.Size{Width: 0, Height: 0} +} + +// Refresh is a hook that is called if the widget has updated and needs to be redrawn. +// This might trigger a Layout. +func (s *simpleRenderer) Refresh() { + s.Layout(s.widget.Size()) +} + +// Layout is a hook that is called if the widget needs to be laid out. +// This should not be overwritten. +func (s *simpleRenderer) Layout(size fyne.Size) { + s.layout(size) +} + +// Objects returns the objects that should be rendered. +// +// Implements: fyne.WidgetRenderer +func (s *simpleRenderer) Objects() []fyne.CanvasObject { + return s.objects +} + +// SetObjects updates the objects of the renderer. +func (s *simpleRenderer) SetObjects(objects []fyne.CanvasObject) { + s.objects = objects +} + +// Destroy does nothing in the base implementation. +// +// Implements: fyne.WidgetRenderer +func (s *simpleRenderer) Destroy() {} diff --git a/widget/simple_widget.go b/widget/simple_widget.go new file mode 100644 index 00000000..b0664711 --- /dev/null +++ b/widget/simple_widget.go @@ -0,0 +1,117 @@ +package widget + +import ( + "sync" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" +) + +// SimpleWidget defines an interface for fyne widgets that are simple +// to implement when based on SimpleWidgetBase. +// For more info on how to implement, see the documentation of the +// SimpleWidgetBase. +type SimpleWidget interface { + fyne.Widget + + Render() ([]fyne.CanvasObject, func(size fyne.Size)) +} + +// SimpleWidgetBase defines the base for a SimpleWidget implementation. +// To create a new widget base it on SimpleWidgetBase using composition. +// Create a `New` function initialising the widget and make sure to call +// ExtendBaseWidget in the New function. Always use the `New` function to +// create the widget or make sure `ExtendBaseWidget` is called elsewhere. +// +// Overwrite the `Render() (objects []fyne.CanvasObject, layout func(size fyne.Size))` +// function. It returns the (base-)objects needed to render the widgets content, +// as well as a function `layout` responsible for positioning and resizing the +// different objects based on the incoming available space for the widget. +// Try not to define new objects in the `layout` function as they would be +// recreated every time the widget is refreshed. +// +// Other functions defined by the fyne.Widget interface can be overwritten +// and will be used by the SimpleWidgetBase if overwritten. +// +// See ./example/simple_wigdet.go for a bootstraped widget implementation. +type SimpleWidgetBase struct { + widget.BaseWidget + + propertyLock sync.RWMutex + impl SimpleWidget +} + +// Render must be overwritten in a widget to create other widgets and +// canvas objects the widget is composed of. It returns a slice of the +// created objects and the layout function. +// The layout fucntion should be used to position and size the objects (widgets +// and canvas objects). New objects should be created in the Render function body +// outside the returned layout function, so they are not re-created +// every time the widget gets refreshed. +func (s *SimpleWidgetBase) Render() (objects []fyne.CanvasObject, layout func(size fyne.Size)) { + return nil, func(fyne.Size) {} +} + +// CreateRenderer implements the Widget interface. It creates a simpleRenderer +// and returns it. No renderer needs to be implemented. If the simpleRenderer +// doesn't do it, SimpleWidget is probably not suitable for the use case. +// Usually this should not be overwritten or called manually. +func (s *SimpleWidgetBase) CreateRenderer() fyne.WidgetRenderer { + wdgt := s.super() + objs, layout := wdgt.Render() + + return newSimpleRenderer(wdgt, objs, layout) +} + +// SetState sets or changes the state of a widget. A Refresh +// is triggered after the state changes have been applied. +func (s *SimpleWidgetBase) SetState(setState func()) { + setState() + s.super().Refresh() +} + +// SetStateSafe sets or changes the state of a widget in a safe way. A Refresh +// is triggered after the state changes have been applied. +// The provided sync.Locker should be the same you use for read protection of the +// widget properties. +func (s *SimpleWidgetBase) SetStateSafe(m sync.Locker, setState func()) { + m.Lock() + setState() + m.Unlock() + + s.super().Refresh() +} + +// ExtendBaseWidget is used by an extending widget to make use of BaseWidget functionality. +func (s *SimpleWidgetBase) ExtendBaseWidget(wid SimpleWidget) { + impl := s.getImpl() + if impl != nil { + return + } + + s.BaseWidget.ExtendBaseWidget(wid) + + s.propertyLock.Lock() + defer s.propertyLock.Unlock() + s.impl = wid +} + +func (s *SimpleWidgetBase) super() SimpleWidget { + impl := s.getImpl() + if impl == nil { + var x interface{} = s + return x.(SimpleWidget) + } + return impl +} + +func (s *SimpleWidgetBase) getImpl() SimpleWidget { + s.propertyLock.RLock() + impl := s.impl + s.propertyLock.RUnlock() + + if impl == nil { + return nil + } + return impl +} From f773241c921083395cb651b64fd372001db782c4 Mon Sep 17 00:00:00 2001 From: TehSphinX Date: Tue, 16 Feb 2021 15:58:12 +0100 Subject: [PATCH 2/6] rename `Renderer` to `Build` function; docs improvements --- widget/example/simple_widget.go | 4 +++- widget/simple_widget.go | 24 +++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/widget/example/simple_widget.go b/widget/example/simple_widget.go index 154f5e19..00f493ec 100644 --- a/widget/example/simple_widget.go +++ b/widget/example/simple_widget.go @@ -18,7 +18,9 @@ type SampleWidget struct { // NewSampleWidget creates a new sample widget. func NewSampleWidget(property int) *SampleWidget { - return &SampleWidget{Property: property} + wgt := &SampleWidget{Property: property} + wgt.ExtendBaseWidget(wgt) + return wgt } // Render renders the SampleWidget. diff --git a/widget/simple_widget.go b/widget/simple_widget.go index b0664711..82debcb3 100644 --- a/widget/simple_widget.go +++ b/widget/simple_widget.go @@ -14,7 +14,7 @@ import ( type SimpleWidget interface { fyne.Widget - Render() ([]fyne.CanvasObject, func(size fyne.Size)) + Build() ([]fyne.CanvasObject, func(size fyne.Size)) } // SimpleWidgetBase defines the base for a SimpleWidget implementation. @@ -23,10 +23,10 @@ type SimpleWidget interface { // ExtendBaseWidget in the New function. Always use the `New` function to // create the widget or make sure `ExtendBaseWidget` is called elsewhere. // -// Overwrite the `Render() (objects []fyne.CanvasObject, layout func(size fyne.Size))` -// function. It returns the (base-)objects needed to render the widgets content, +// Overwrite the `Build() (objects []fyne.CanvasObject, layout func(size fyne.Size))` +// function. It returns the objects needed to render the widgets content, // as well as a function `layout` responsible for positioning and resizing the -// different objects based on the incoming available space for the widget. +// different objects. // Try not to define new objects in the `layout` function as they would be // recreated every time the widget is refreshed. // @@ -41,14 +41,12 @@ type SimpleWidgetBase struct { impl SimpleWidget } -// Render must be overwritten in a widget to create other widgets and -// canvas objects the widget is composed of. It returns a slice of the -// created objects and the layout function. -// The layout fucntion should be used to position and size the objects (widgets -// and canvas objects). New objects should be created in the Render function body -// outside the returned layout function, so they are not re-created -// every time the widget gets refreshed. -func (s *SimpleWidgetBase) Render() (objects []fyne.CanvasObject, layout func(size fyne.Size)) { +// Build must be overwritten in a widget. It returns a slice of child objects +// and a layout function. +// The layout function should be used to position and size the objects (widgets +// and canvas objects). New objects should be created in the Build function body, +// so they are not re-created every time the widget gets refreshed. +func (s *SimpleWidgetBase) Build() (objects []fyne.CanvasObject, layout func(size fyne.Size)) { return nil, func(fyne.Size) {} } @@ -58,7 +56,7 @@ func (s *SimpleWidgetBase) Render() (objects []fyne.CanvasObject, layout func(si // Usually this should not be overwritten or called manually. func (s *SimpleWidgetBase) CreateRenderer() fyne.WidgetRenderer { wdgt := s.super() - objs, layout := wdgt.Render() + objs, layout := wdgt.Build() return newSimpleRenderer(wdgt, objs, layout) } From 06f19d89593dad781b7166262d5e80242aea4170 Mon Sep 17 00:00:00 2001 From: TehSphinX Date: Tue, 16 Feb 2021 16:13:48 +0100 Subject: [PATCH 3/6] adjust example to avoid linter problem --- widget/example/simple_widget.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/widget/example/simple_widget.go b/widget/example/simple_widget.go index 00f493ec..2bd5da93 100644 --- a/widget/example/simple_widget.go +++ b/widget/example/simple_widget.go @@ -29,7 +29,11 @@ func (s *SampleWidget) Render() (objects []fyne.CanvasObject, layout func(size f // (executed in Widget.CreateRenderer function) return objects, func(size fyne.Size) { - // position, resize or otherwise update objects. (executed in WidgetRenderer.Layout) + property := s.getProperty() + _ = property + + // position, resize or otherwise update objects based on changed properties. + // (executed in WidgetRenderer.Layout) } } From ef110b063376eb4925d50068683b224e8b309fb9 Mon Sep 17 00:00:00 2001 From: TehSphinX Date: Wed, 17 Feb 2021 11:11:38 +0100 Subject: [PATCH 4/6] allow Destroy and Refresh (of the renderer) to be implemented/overwritten --- widget/simple_renderer.go | 19 +++++++------------ widget/simple_widget.go | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/widget/simple_renderer.go b/widget/simple_renderer.go index 426f95a7..5650b47d 100644 --- a/widget/simple_renderer.go +++ b/widget/simple_renderer.go @@ -4,22 +4,15 @@ import ( "fyne.io/fyne/v2" ) -// newSimpleRenderer creates a new simpleRenderer. -func newSimpleRenderer(widget SimpleWidget, objects []fyne.CanvasObject, layout func(size fyne.Size)) *simpleRenderer { - return &simpleRenderer{ - widget: widget, - objects: objects, - layout: layout, - } -} - // simpleRenderer is a renderer providing the basic rendering functionality used // by SimpleWidgetBase. type simpleRenderer struct { widget fyne.Widget objects []fyne.CanvasObject - layout func(size fyne.Size) + layout func(size fyne.Size) + destroy func() + refresh func() } // MinSize returns the minimum size of the widget that is rendered by this renderer. @@ -30,7 +23,7 @@ func (s *simpleRenderer) MinSize() fyne.Size { // Refresh is a hook that is called if the widget has updated and needs to be redrawn. // This might trigger a Layout. func (s *simpleRenderer) Refresh() { - s.Layout(s.widget.Size()) + s.refresh() } // Layout is a hook that is called if the widget needs to be laid out. @@ -54,4 +47,6 @@ func (s *simpleRenderer) SetObjects(objects []fyne.CanvasObject) { // Destroy does nothing in the base implementation. // // Implements: fyne.WidgetRenderer -func (s *simpleRenderer) Destroy() {} +func (s *simpleRenderer) Destroy() { + s.destroy() +} diff --git a/widget/simple_widget.go b/widget/simple_widget.go index 82debcb3..4a5c99b0 100644 --- a/widget/simple_widget.go +++ b/widget/simple_widget.go @@ -30,8 +30,11 @@ type SimpleWidget interface { // Try not to define new objects in the `layout` function as they would be // recreated every time the widget is refreshed. // -// Other functions defined by the fyne.Widget interface can be overwritten +// Other methods defined by the fyne.Widget interface can be overwritten // and will be used by the SimpleWidgetBase if overwritten. +// Overwrite `Destroy()` to implement custom cleanup for the widget. +// Implement `Refresh()` to optimize refresh performance. By default `Refresh` +// calls the `Layout` method of the renderer, which is not necessary in most cases. // // See ./example/simple_wigdet.go for a bootstraped widget implementation. type SimpleWidgetBase struct { @@ -39,6 +42,7 @@ type SimpleWidgetBase struct { propertyLock sync.RWMutex impl SimpleWidget + layout func(fyne.Size) } // Build must be overwritten in a widget. It returns a slice of child objects @@ -58,7 +62,18 @@ func (s *SimpleWidgetBase) CreateRenderer() fyne.WidgetRenderer { wdgt := s.super() objs, layout := wdgt.Build() - return newSimpleRenderer(wdgt, objs, layout) + s.propertyLock.Lock() + s.layout = layout + s.propertyLock.Unlock() + + renderer := &simpleRenderer{ + widget: wdgt, + objects: objs, + layout: layout, + destroy: s.Destroy, + refresh: s.Refresh, + } + return renderer } // SetState sets or changes the state of a widget. A Refresh @@ -94,6 +109,23 @@ func (s *SimpleWidgetBase) ExtendBaseWidget(wid SimpleWidget) { s.impl = wid } +// Refresh is a hook that is called if the widget has updated and needs to be redrawn. +// By default it triggers a call to Layout. Overwrite to optimize for performance if +// a call to Layout is not needed. +func (s *SimpleWidgetBase) Refresh() { + s.propertyLock.RLock() + layout := s.layout + s.propertyLock.RUnlock() + + if layout == nil { + return + } + layout(s.Size()) +} + +// Destroy can be overwritten if there is extra cleanup needed. +func (s *SimpleWidgetBase) Destroy() {} + func (s *SimpleWidgetBase) super() SimpleWidget { impl := s.getImpl() if impl == nil { From c6f76f499bb6790963fe8c4283c9d83f283e3ac3 Mon Sep 17 00:00:00 2001 From: TehSphinX Date: Sat, 20 Feb 2021 16:00:37 +0100 Subject: [PATCH 5/6] passing MinSize function on to renderer as well --- widget/simple_renderer.go | 3 ++- widget/simple_widget.go | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/widget/simple_renderer.go b/widget/simple_renderer.go index 5650b47d..096e41c4 100644 --- a/widget/simple_renderer.go +++ b/widget/simple_renderer.go @@ -13,11 +13,12 @@ type simpleRenderer struct { layout func(size fyne.Size) destroy func() refresh func() + minSize func() fyne.Size } // MinSize returns the minimum size of the widget that is rendered by this renderer. func (s *simpleRenderer) MinSize() fyne.Size { - return fyne.Size{Width: 0, Height: 0} + return s.minSize() } // Refresh is a hook that is called if the widget has updated and needs to be redrawn. diff --git a/widget/simple_widget.go b/widget/simple_widget.go index 4a5c99b0..37eb2239 100644 --- a/widget/simple_widget.go +++ b/widget/simple_widget.go @@ -72,6 +72,7 @@ func (s *SimpleWidgetBase) CreateRenderer() fyne.WidgetRenderer { layout: layout, destroy: s.Destroy, refresh: s.Refresh, + minSize: s.MinSize, } return renderer } @@ -123,6 +124,13 @@ func (s *SimpleWidgetBase) Refresh() { layout(s.Size()) } +// MinSize for the widget - it should never be resized below this value. +// By default this returns (0, 0). Overwrite this to return a different +// minimum size for the widget. +func (s *SimpleWidgetBase) MinSize() fyne.Size { + return fyne.NewSize(0, 0) +} + // Destroy can be overwritten if there is extra cleanup needed. func (s *SimpleWidgetBase) Destroy() {} From 4ef37165816523d0b0dc3b04d4c94a15b643bd2a Mon Sep 17 00:00:00 2001 From: TehSphinX Date: Tue, 23 Feb 2021 14:39:04 +0100 Subject: [PATCH 6/6] added alternative with widget and renderer being split --- widget/example/s_widget.go | 65 ++++++++++++++++++++++++ widget/example/simple_widget.go | 4 +- widget/s_renderer.go | 89 +++++++++++++++++++++++++++++++++ widget/s_widget.go | 75 +++++++++++++++++++++++++++ 4 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 widget/example/s_widget.go create mode 100644 widget/s_renderer.go create mode 100644 widget/s_widget.go diff --git a/widget/example/s_widget.go b/widget/example/s_widget.go new file mode 100644 index 00000000..38568e4d --- /dev/null +++ b/widget/example/s_widget.go @@ -0,0 +1,65 @@ +package example + +import ( + "sync" + + "fyne.io/fyne/v2" + wgt "fyne.io/x/fyne/widget" +) + +// SampleSWidget implements a table with row based selection and column names. +type SampleSWidget struct { + wgt.BaseSimpleWidget + propertyLock sync.RWMutex + + Property int +} + +// NewSampleSWidget creates a new SampleSWidget. +func NewSampleSWidget(property int) *SampleSWidget { + w := &SampleSWidget{Property: property} + w.ExtendBaseWidget(w) + return w +} + +// CreateRenderer implements fyne.Widget. +func (s *SampleSWidget) CreateRenderer() fyne.WidgetRenderer { + rend := &sampleSWidgetRenderer{ + widget: s, + } + rend.BaseSimpleRenderer = *wgt.NewBaseSimpleRenderer(s, rend) + return rend +} + +// SetStateSafe sets or changes the state of a widget in a safe way. A Refresh +// is triggered after the state changes have been applied. +func (s *SampleSWidget) SetStateSafe(setState func()) { + s.BaseSimpleWidget.SetStateSafe(&s.propertyLock, setState) +} + +func (s *SampleSWidget) getProperty() int { + s.propertyLock.RLock() + defer s.propertyLock.RUnlock() + + return s.Property +} + +type sampleSWidgetRenderer struct { + wgt.BaseSimpleRenderer + + widget *SampleSWidget +} + +// Build renders the SampleSWidget. +func (s *sampleSWidgetRenderer) Build() (objects []fyne.CanvasObject, layout func(size fyne.Size)) { + // create objects needed for rendering and append them to the objects slice. + // (executed in Widget.CreateRenderer function) + + return objects, func(size fyne.Size) { + property := s.widget.getProperty() + _ = property + + // position, resize or otherwise update objects based on changed properties. + // (executed in WidgetRenderer.Layout) + } +} diff --git a/widget/example/simple_widget.go b/widget/example/simple_widget.go index 2bd5da93..b521c7f2 100644 --- a/widget/example/simple_widget.go +++ b/widget/example/simple_widget.go @@ -23,8 +23,8 @@ func NewSampleWidget(property int) *SampleWidget { return wgt } -// Render renders the SampleWidget. -func (s *SampleWidget) Render() (objects []fyne.CanvasObject, layout func(size fyne.Size)) { +// Build renders the SampleWidget. +func (s *SampleWidget) Build() (objects []fyne.CanvasObject, layout func(size fyne.Size)) { // create objects needed for rendering and append them to the objects slice. // (executed in Widget.CreateRenderer function) diff --git a/widget/s_renderer.go b/widget/s_renderer.go new file mode 100644 index 00000000..fadb658d --- /dev/null +++ b/widget/s_renderer.go @@ -0,0 +1,89 @@ +package widget + +import ( + "sync" + + "fyne.io/fyne/v2" +) + +type SimpleWidgetRenderer interface { + fyne.WidgetRenderer + + Build() (objects []fyne.CanvasObject, layout func(size fyne.Size)) +} + +func NewBaseSimpleRenderer(wgt fyne.Widget, renderer SimpleWidgetRenderer) *BaseSimpleRenderer { + rend := &BaseSimpleRenderer{ + widget: wgt, + impl: renderer, + } + objs, layout := rend.super().Build() + rend.objects = objs + rend.layout = layout + + return rend +} + +type BaseSimpleRenderer struct { + objects []fyne.CanvasObject + propertyLock sync.RWMutex + widget fyne.Widget + impl SimpleWidgetRenderer + + layout func(fyne.Size) +} + +func (s *BaseSimpleRenderer) Build() (objects []fyne.CanvasObject, layout func(size fyne.Size)) { + return nil, func(fyne.Size) {} +} + +// Destroy can be overwritten if there is extra cleanup needed. +func (s *BaseSimpleRenderer) Destroy() {} + +// Layout is a hook that is called if the widget needs to be laid out. +// This should not be overwritten. +func (s *BaseSimpleRenderer) Layout(size fyne.Size) { + s.layout(size) +} + +func (s *BaseSimpleRenderer) MinSize() fyne.Size { + return fyne.NewSize(0, 0) +} + +func (s *BaseSimpleRenderer) Objects() []fyne.CanvasObject { + return s.objects +} + +// Refresh is a hook that is called if the widget has updated and needs to be redrawn. +// By default it triggers a call to Layout. Overwrite to optimize for performance if +// a call to Layout is not needed. +func (s *BaseSimpleRenderer) Refresh() { + s.propertyLock.RLock() + layout := s.layout + s.propertyLock.RUnlock() + + if layout == nil { + return + } + layout(s.widget.Size()) +} + +func (s *BaseSimpleRenderer) getImpl() SimpleWidgetRenderer { + s.propertyLock.RLock() + impl := s.impl + s.propertyLock.RUnlock() + + if impl == nil { + return nil + } + return impl +} + +func (s *BaseSimpleRenderer) super() SimpleWidgetRenderer { + impl := s.getImpl() + if impl == nil { + var x interface{} = s + return x.(SimpleWidgetRenderer) + } + return impl +} diff --git a/widget/s_widget.go b/widget/s_widget.go new file mode 100644 index 00000000..38de3888 --- /dev/null +++ b/widget/s_widget.go @@ -0,0 +1,75 @@ +package widget + +import ( + "sync" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" +) + +// BaseSimpleWidget defines the base for a fyne.Widget implementation. +// To create a new widget base it on BaseSimpleWidget using composition. +// Create a `New` function initialising the widget and make sure to call +// ExtendBaseWidget in the New function. Always use the `New` function to +// create the widget or make sure `ExtendBaseWidget` is called elsewhere. +// +// See ./example/s_wigdet.go for a bootstraped widget implementation. +type BaseSimpleWidget struct { + widget.BaseWidget + + impl fyne.Widget + propertyLock sync.RWMutex +} + +// ExtendBaseWidget is used by an extending widget to make use of BaseSimpleWidget functionality. +func (s *BaseSimpleWidget) ExtendBaseWidget(wid fyne.Widget) { + impl := s.getImpl() + if impl != nil { + return + } + + s.BaseWidget.ExtendBaseWidget(wid) + + s.propertyLock.Lock() + defer s.propertyLock.Unlock() + s.impl = wid +} + +// SetState sets or changes the state of a widget. A Refresh +// is triggered after the state changes have been applied. +func (s *BaseSimpleWidget) SetState(setState func()) { + setState() + s.super().Refresh() +} + +// SetStateSafe sets or changes the state of a widget in a safe way. A Refresh +// is triggered after the state changes have been applied. +// The provided sync.Locker should be the same you use for read protection of the +// widget properties. +func (s *BaseSimpleWidget) SetStateSafe(m sync.Locker, setState func()) { + m.Lock() + setState() + m.Unlock() + + s.super().Refresh() +} + +func (s *BaseSimpleWidget) getImpl() fyne.Widget { + s.propertyLock.RLock() + impl := s.impl + s.propertyLock.RUnlock() + + if impl == nil { + return nil + } + return impl +} + +func (s *BaseSimpleWidget) super() fyne.Widget { + impl := s.getImpl() + if impl == nil { + var x interface{} = s + return x.(fyne.Widget) + } + return impl +}