Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SimpleWidget implementation #8

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 49 additions & 0 deletions widget/example/simple_widget.go
@@ -0,0 +1,49 @@
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 {
wgt := &SampleWidget{Property: property}
wgt.ExtendBaseWidget(wgt)
return wgt
}

// 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)
}
tehsphinx marked this conversation as resolved.
Show resolved Hide resolved

func (s *SampleWidget) getProperty() int {
s.propertyLock.RLock()
defer s.propertyLock.RUnlock()

return s.Property
}
tehsphinx marked this conversation as resolved.
Show resolved Hide resolved

// All other methods of a fyne.Widget can be overwritten as usual. E.g. MinSize, Resize, Refresh, etc.
57 changes: 57 additions & 0 deletions 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}
}
tehsphinx marked this conversation as resolved.
Show resolved Hide resolved

// 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())
}
tehsphinx marked this conversation as resolved.
Show resolved Hide resolved

// 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() {}
tehsphinx marked this conversation as resolved.
Show resolved Hide resolved
115 changes: 115 additions & 0 deletions widget/simple_widget.go
@@ -0,0 +1,115 @@
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

Build() ([]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 `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.
// 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
}

// 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) {}
}
tehsphinx marked this conversation as resolved.
Show resolved Hide resolved

// 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.Build()

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()
}
tehsphinx marked this conversation as resolved.
Show resolved Hide resolved

// 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
}