diff --git a/baseview.go b/baseview.go index 5bbe524..dacc7d8 100644 --- a/baseview.go +++ b/baseview.go @@ -1,12 +1,10 @@ package ui import ( - "time" + "embed" - "bitbucket.org/hevanto/ui/screen" "fyne.io/fyne/v2" "fyne.io/fyne/v2/data/binding" - "fyne.io/fyne/v2/widget" "github.com/google/uuid" "github.com/nicksnyder/go-i18n/v2/i18n" ) @@ -26,35 +24,8 @@ type BaseView struct { // Root CanvasObject of the view CanvasObject fyne.CanvasObject - // The screen definition - screenDefinition *screen.Screen - // ----------- ScreenHandler -------------// - - // Localizer to translate screen labels - localizer *i18n.Localizer - - // Map of screen elements that got an id assigned - screenElementMap map[string]fyne.CanvasObject - - // Map of list item template - listItemTemplates map[string]func() fyne.CanvasObject - - // Map of list data item renderers - listDataItemRenderers map[string]func(binding.DataItem, fyne.CanvasObject) - - // Map of list item renderers - listItemRenderers map[string]func(int, fyne.CanvasObject) - - // Map of list length functions - listLenghts map[string]func() int - - // Handlers - onSelectedHandlers map[string]func(widget.ListItemID) - onUnselectedHandlers map[string]func(widget.ListItemID) - onClickedHandlers map[string]func() - onCheckChangedHandlers map[string]func(bool) - onDateSelectedHandlers map[string]func(time.Time) + *ScreenHandler } // NewBaseView creates a new baseview @@ -80,6 +51,28 @@ func NewBaseView( return v } +func NewBaseViewWithScreen( + concreteView View, + controller Controller, + filesystem embed.FS, + screenName string, + localizer *i18n.Localizer, +) *BaseView { + v := NewBaseView(concreteView, controller) + + var err error + v.ScreenHandler, err = NewScreenHandler( + filesystem, + screenName, + controller, + localizer) + if err != nil { + LogWindowError(v, LoadScreen, err) + return nil + } + return v +} + // ID returns tie id of the view func (v BaseView) ID() string { return v.id @@ -106,8 +99,8 @@ func (v *BaseView) Initialize() fyne.CanvasObject { if err := v.Ctrl.Initialize(); err != nil { LogWindowError(v, Initialize, err) } - if v.screenDefinition != nil { - obj, err := v.screenDefinition.Initialize() + if v.ScreenHandler != nil && v.ScreenDefinition() != nil { + obj, err := v.ScreenDefinition().Initialize() if err != nil { LogWindowError(v, Initialize, err) return nil @@ -133,3 +126,7 @@ func (v *BaseView) OnShow() { func (v *BaseView) OnHide() { LogWindowEvent(v, OnHide) } + +func (v *BaseView) GetBinding(bindingName string) binding.DataItem { + return v.Ctrl.GetBinding(bindingName) +} diff --git a/baseview.screenhandler.go b/baseview.screenhandler.go deleted file mode 100644 index 7226174..0000000 --- a/baseview.screenhandler.go +++ /dev/null @@ -1,177 +0,0 @@ -package ui - -import ( - "embed" - "strings" - "time" - - "bitbucket.org/hevanto/ui/screen" - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/data/binding" - "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" - "github.com/nicksnyder/go-i18n/v2/i18n" -) - -func NewBaseViewWithScreen( - concreteView View, - filesystem embed.FS, - screenName string, - localizer *i18n.Localizer, - controller Controller, -) *BaseView { - v := NewBaseView(concreteView, controller) - def, err := screen.New(filesystem, screenName, v) - if err != nil { - LogWindowError(v, LoadScreen, err) - return nil - } - v.screenDefinition = def - v.localizer = localizer - v.screenElementMap = make(map[string]fyne.CanvasObject) - v.listItemTemplates = make(map[string]func() fyne.CanvasObject) - v.listDataItemRenderers = make(map[string]func(binding.DataItem, fyne.CanvasObject)) - v.listItemRenderers = make(map[string]func(int, fyne.CanvasObject)) - v.listLenghts = make(map[string]func() int) - v.onSelectedHandlers = make(map[string]func(widget.ListItemID)) - v.onUnselectedHandlers = make(map[string]func(widget.ListItemID)) - v.onClickedHandlers = make(map[string]func()) - v.onCheckChangedHandlers = make(map[string]func(bool)) - v.onDateSelectedHandlers = make(map[string]func(time.Time)) - return v -} - -func (v *BaseView) RegisterElement(id string, elem fyne.CanvasObject) { - v.screenElementMap[id] = elem -} - -func (v *BaseView) RegisterListItemTemplate(name string, fn func() fyne.CanvasObject) { - v.listItemTemplates[name] = fn -} - -func (v *BaseView) RegisterListDataItemRenderer(name string, fn func(binding.DataItem, fyne.CanvasObject)) { - v.listDataItemRenderers[name] = fn -} - -func (v *BaseView) RegisterListItemRenderer(name string, fn func(int, fyne.CanvasObject)) { - v.listItemRenderers[name] = fn -} - -func (v *BaseView) RegisterListLength(name string, fn func() int) { - v.listLenghts[name] = fn -} - -func (v *BaseView) RegisterOnSelectedHandler(name string, fn func(widget.ListItemID)) { - v.onSelectedHandlers[name] = fn -} - -func (v *BaseView) RegisterOnUnselectedHandler(name string, fn func(widget.ListItemID)) { - v.onUnselectedHandlers[name] = fn -} - -func (v *BaseView) RegisterOnClickHandler(name string, fn func()) { - v.onClickedHandlers[name] = fn -} - -func (v *BaseView) RegisterOnCheckChangedHandler(name string, fn func(bool)) { - v.onCheckChangedHandlers[name] = fn -} - -func (v *BaseView) RegisterOnDateSelectedHandler(name string, fn func(time.Time)) { - v.onDateSelectedHandlers[name] = fn -} - -func (v *BaseView) GetElement(id string) fyne.CanvasObject { - if elem, ok := v.screenElementMap[id]; ok { - return elem - } - return nil -} - -func (v *BaseView) GetLocalizer() *i18n.Localizer { - return v.localizer -} - -func (v *BaseView) GetIcon(iconName string) fyne.Resource { - if strings.HasPrefix(iconName, "theme.") { - iconName := strings.SplitN(iconName, ".", 2)[1] - return theme.DefaultTheme().Icon(fyne.ThemeIconName(iconName)) - } - return nil -} - -func (v *BaseView) GetBinding(bindingName string) binding.DataItem { - return v.Ctrl.GetBinding(bindingName) -} - -func (v *BaseView) GetListItemTemplate(name string) func() fyne.CanvasObject { - fn, ok := v.listItemTemplates[name] - if !ok { - return nil - } - return fn -} - -func (v *BaseView) GetListDataItemRenderer(name string) func(binding.DataItem, fyne.CanvasObject) { - fn, ok := v.listDataItemRenderers[name] - if !ok { - return nil - } - return fn -} - -func (v *BaseView) GetListItemRenderer(name string) func(int, fyne.CanvasObject) { - fn, ok := v.listItemRenderers[name] - if !ok { - return nil - } - return fn -} - -func (v *BaseView) GetListLength(name string) func() int { - fn, ok := v.listLenghts[name] - if !ok { - return nil - } - return fn -} - -func (v *BaseView) GetOnSelectedHandler(name string) func(widget.ListItemID) { - fn, ok := v.onSelectedHandlers[name] - if !ok { - return nil - } - return fn -} - -func (v *BaseView) GetOnUnselectedHandler(name string) func(widget.ListItemID) { - fn, ok := v.onUnselectedHandlers[name] - if !ok { - return nil - } - return fn -} - -func (v *BaseView) GetClickedHandler(name string) func() { - fn, ok := v.onClickedHandlers[name] - if !ok { - return nil - } - return fn -} - -func (v *BaseView) GetCheckChangedHandler(name string) func(bool) { - fn, ok := v.onCheckChangedHandlers[name] - if !ok { - return nil - } - return fn -} - -func (v *BaseView) GetDateSelectedHandler(name string) func(time.Time) { - fn, ok := v.onDateSelectedHandlers[name] - if !ok { - return nil - } - return fn -} diff --git a/controller.interface.go b/controller.interface.go index c961a4a..cc6c50a 100644 --- a/controller.interface.go +++ b/controller.interface.go @@ -1,15 +1,25 @@ package ui -import "fyne.io/fyne/v2/data/binding" +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/data/binding" +) + +type ScreenController interface { + GetBinding(string) binding.DataItem + GetValidator(string) fyne.StringValidator +} type Controller interface { + ScreenController + Initialize() error RefreshData() error - - GetBinding(string) binding.DataItem } type DialogController interface { + ScreenController + Initialize(data binding.DataItem) error RefreshData() error } diff --git a/dialog.go b/dialog.go index 074860e..ed943f0 100644 --- a/dialog.go +++ b/dialog.go @@ -1,6 +1,7 @@ package ui import ( + "embed" "fmt" "fyne.io/fyne/v2" @@ -8,13 +9,22 @@ import ( "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" "github.com/google/uuid" + "github.com/nicksnyder/go-i18n/v2/i18n" ) type DialogName string type DialogKind string type OnConfirmFn func(bool, error, ...interface{}) -type NewDialogFn func(DialogName, DialogKind, fyne.Window, OnConfirmFn, ...binding.DataItem) Dialog +type NewDialogFn func( + DialogName, + DialogKind, + fyne.Window, + OnConfirmFn, + binding.DataItem, + *embed.FS, + string, + *i18n.Localizer) Dialog const ( DlgGeneric DialogKind = "Generic" @@ -47,12 +57,16 @@ type BaseDialog struct { CanvasObj fyne.CanvasObject Data binding.DataItem focusItem fyne.Focusable + + Ctrl DialogController + *ScreenHandler } func NewBaseDialog( concreteDialog Dialog, name DialogName, kind DialogKind, + controller DialogController, parent fyne.Window, confirm OnConfirmFn, ) *BaseDialog { @@ -63,13 +77,40 @@ func NewBaseDialog( dlgKind: kind, window: parent, onConfirm: confirm, + Ctrl: controller, } } +func NewBaseDialogWithScreen( + concreteDialog Dialog, + name DialogName, + kind DialogKind, + controller DialogController, + parent fyne.Window, + confirm OnConfirmFn, + filesystem embed.FS, + screenName string, + localizer *i18n.Localizer, +) *BaseDialog { + d := NewBaseDialog( + concreteDialog, name, kind, controller, + parent, confirm) + + var err error + d.ScreenHandler, err = NewScreenHandler( + filesystem, screenName, controller, localizer) + if err != nil { + LogWindowError(d, LoadScreen, err) + return nil + } + return d +} + func NewBaseDialogWithData( concreteDialog Dialog, name DialogName, kind DialogKind, + controller DialogController, parent fyne.Window, confirm OnConfirmFn, data binding.DataItem, @@ -82,9 +123,36 @@ func NewBaseDialogWithData( window: parent, onConfirm: confirm, Data: data, + Ctrl: controller, } } +func NewBaseDialogWithDataAndScreen( + concreteDialog Dialog, + name DialogName, + kind DialogKind, + controller DialogController, + parent fyne.Window, + confirm OnConfirmFn, + data binding.DataItem, + filesystem embed.FS, + screenName string, + localizer *i18n.Localizer, +) *BaseDialog { + d := NewBaseDialogWithData( + concreteDialog, name, kind, controller, + parent, confirm, data) + + var err error + d.ScreenHandler, err = NewScreenHandler( + filesystem, screenName, controller, localizer) + if err != nil { + LogWindowError(d, LoadScreen, err) + return nil + } + return d +} + func (d BaseDialog) Window() fyne.Window { return d.window } @@ -114,6 +182,7 @@ func (d BaseDialog) OnConfirmFn() OnConfirmFn { } func (d *BaseDialog) Show(parent ...fyne.Window) { + d.concreteDialog.OnShow() d.dlg = dialog.NewCustomConfirm( d.concreteDialog.Title(), @@ -146,6 +215,17 @@ func (d BaseDialog) Title() string { } func (d *BaseDialog) Initialize() fyne.CanvasObject { + if err := d.Ctrl.Initialize(d.Data); err != nil { + LogWindowError(d, Initialize, err) + } + if d.ScreenHandler != nil && d.ScreenDefinition() != nil { + obj, err := d.ScreenDefinition().Initialize() + if err != nil { + LogWindowError(d, Initialize, err) + return nil + } + d.CanvasObj = obj + } return d.CanvasObj } @@ -184,35 +264,80 @@ func NewFormDialog( concreteDialog Dialog, name DialogName, kind DialogKind, + controller DialogController, parent fyne.Window, confirm OnConfirmFn, itemsFn FormDialogItemsFn, ) *FormDialog { return &FormDialog{ - BaseDialog: NewBaseDialog(concreteDialog, name, kind, parent, confirm), + BaseDialog: NewBaseDialog( + concreteDialog, name, kind, controller, parent, confirm), itemsFn: itemsFn, confirmLabel: "OK", dismissLabel: "Cancel", } } +func NewFormDialogWithScreen( + concreteDialog Dialog, + name DialogName, + kind DialogKind, + controller DialogController, + parent fyne.Window, + confirm OnConfirmFn, + filesystem embed.FS, + screenName string, + localizer *i18n.Localizer, +) *FormDialog { + return &FormDialog{ + BaseDialog: NewBaseDialogWithScreen( + concreteDialog, name, kind, controller, parent, confirm, + filesystem, screenName, localizer), + confirmLabel: "OK", + dismissLabel: "Cancel", + } +} + func NewformDialogWithData( concreteDialog Dialog, name DialogName, kind DialogKind, + controller DialogController, parent fyne.Window, confirm OnConfirmFn, itemsFn FormDialogItemsFn, data binding.DataItem, ) *FormDialog { return &FormDialog{ - BaseDialog: NewBaseDialogWithData(concreteDialog, name, kind, parent, confirm, data), + BaseDialog: NewBaseDialogWithData( + concreteDialog, name, kind, controller, parent, confirm, data), itemsFn: itemsFn, confirmLabel: "OK", dismissLabel: "Cancel", } } +func NewFormDialogWithDataAndScreen( + concreteDialog Dialog, + name DialogName, + kind DialogKind, + controller DialogController, + parent fyne.Window, + confirm OnConfirmFn, + data binding.DataItem, + filesystem embed.FS, + screenName string, + localizer *i18n.Localizer, +) *FormDialog { + return &FormDialog{ + BaseDialog: NewBaseDialogWithDataAndScreen( + concreteDialog, name, kind, controller, parent, confirm, data, + filesystem, screenName, localizer), + confirmLabel: "OK", + dismissLabel: "Cancel", + } +} + func (d *FormDialog) SetButtonLabels( confirm string, dismiss string, @@ -221,7 +346,25 @@ func (d *FormDialog) SetButtonLabels( d.dismissLabel = dismiss } +func (d *FormDialog) Initialize() fyne.CanvasObject { + if d.itemsFn == nil { + form := d.BaseDialog.Initialize().(*fyne.Container) + d.itemsFn = func() []*widget.FormItem { + res := make([]*widget.FormItem, 0, len(form.Objects)/2) + for i := 0; i < len(form.Objects); i += 2 { + res = append(res, widget.NewFormItem( + form.Objects[i].(*widget.Label).Text, + form.Objects[i+1])) + } + return res + } + return nil + } + return nil +} + func (d *FormDialog) Show(parent ...fyne.Window) { + d.concreteDialog.Initialize() d.items = d.itemsFn() d.concreteDialog.OnShow() @@ -263,7 +406,8 @@ func RegisterDialog(name DialogName, kind DialogKind, fn NewDialogFn) { } func NewDialog( - name DialogName, kind DialogKind, + name DialogName, + kind DialogKind, parent fyne.Window, confirm OnConfirmFn, ) Dialog { @@ -272,14 +416,37 @@ func NewDialog( } if kindCatalog, ok := dialogCatalog[name]; ok { if fn, ok := kindCatalog[kind]; ok { - return fn(name, kind, parent, confirm) + return fn(name, kind, parent, confirm, nil, nil, "", nil) + } + } + return nil +} + +func NewDialogWithScreen( + name DialogName, + kind DialogKind, + parent fyne.Window, + confirm OnConfirmFn, + filesystem embed.FS, + screenName string, + localizer *i18n.Localizer, +) Dialog { + if dialogCatalog == nil { + return nil + } + if kindCatalog, ok := dialogCatalog[name]; ok { + if fn, ok := kindCatalog[kind]; ok { + return fn( + name, kind, parent, confirm, nil, + &filesystem, screenName, localizer) } } return nil } func NewDialogWithData( - name DialogName, kind DialogKind, + name DialogName, + kind DialogKind, parent fyne.Window, confirm OnConfirmFn, data binding.DataItem, @@ -289,7 +456,30 @@ func NewDialogWithData( } if kindCatalog, ok := dialogCatalog[name]; ok { if fn, ok := kindCatalog[kind]; ok { - return fn(name, kind, parent, confirm, data) + return fn(name, kind, parent, confirm, data, nil, "", nil) + } + } + return nil +} + +func NewDialogWithDataAndScreen( + name DialogName, + kind DialogKind, + parent fyne.Window, + confirm OnConfirmFn, + data binding.DataItem, + filesystem embed.FS, + screenName string, + localizer *i18n.Localizer, +) Dialog { + if dialogCatalog == nil { + return nil + } + if kindCatalog, ok := dialogCatalog[name]; ok { + if fn, ok := kindCatalog[kind]; ok { + return fn( + name, kind, parent, confirm, data, + &filesystem, screenName, localizer) } } return nil diff --git a/screen/container.go b/screen/container.go index 6ea26b6..8766c7c 100644 --- a/screen/container.go +++ b/screen/container.go @@ -9,45 +9,85 @@ import ( "fyne.io/fyne/v2/container" ) +// The constants for the Containers +// +// Available yaml options for all containers: +// - id: The ID for exporting the container to code +// - decorators: The decorators to wrap the container into +// - hidden: Wether the container is hidden or not type Container string const ( + // HScroll Container + // + // Available yaml options: + // - content: The content of the container HScroll Container = "HScroll" - Scroll Container = "Scroll" + + // Scroll Container + // + // Available yaml options: + // - content: The content of the container + Scroll Container = "Scroll" + + // VScroll Container + // + // Available yaml options: + // - content: The content of the container VScroll Container = "VScroll" ) -func (cnt Container) Build(e *Element, s ScreenHandler) (c fyne.CanvasObject, err error) { +// Build builds the container and decorator for the given Container and Element, +// using the provided ScreenHandler to fetch functions, data bindings, etc. +// +// Parameters: +// - e *Element - the element to build the container for +// - s ScreenHandler - the screen handler for the container +// +// Returns the CanvasObject for the widget, the CanvasObject for the decorator +// I fno decorators are specified, the widget and decorator will be the same. +func (cnt Container) Build( + e *Element, + s ScreenHandler, +) ( + container fyne.CanvasObject, + decorator fyne.CanvasObject, + err error, +) { switch cnt { case HScroll: - c, err = cnt.buildHScrollContainer(e, s) + container, err = cnt.buildHScrollContainer(e, s) case Scroll: - c, err = cnt.buildScrollContainer(e, s) + container, err = cnt.buildScrollContainer(e, s) case VScroll: - c, err = cnt.buildVScrollContainer(e, s) + container, err = cnt.buildVScrollContainer(e, s) default: err = errors.New("invalid container") } if err != nil { return } - if e.Decorators != nil { + decorator = container + if e.Decorators != nil { for _, dec := range e.Decorators { switch dec { case "Border": - c = uiwidget.NewWidgetBorder(c) + decorator = uiwidget.NewWidgetBorder(decorator) } } } if e.Hidden { - c.Hide() + decorator.Hide() } return } -func (ctn Container) buildHScrollContainer(e *Element, s ScreenHandler) (c fyne.CanvasObject, err error) { +func (ctn Container) buildHScrollContainer( + e *Element, + s ScreenHandler, +) (c fyne.CanvasObject, err error) { if e.Content == nil { err = errors.New("HScroll: no content defined") return @@ -62,7 +102,10 @@ func (ctn Container) buildHScrollContainer(e *Element, s ScreenHandler) (c fyne. return } -func (ctn Container) buildScrollContainer(e *Element, s ScreenHandler) (c fyne.CanvasObject, err error) { +func (ctn Container) buildScrollContainer( + e *Element, + s ScreenHandler, +) (c fyne.CanvasObject, err error) { if e.Content == nil { err = errors.New("Scroll: no content defined") return @@ -77,7 +120,10 @@ func (ctn Container) buildScrollContainer(e *Element, s ScreenHandler) (c fyne.C return } -func (ctn Container) buildVScrollContainer(e *Element, s ScreenHandler) (c fyne.CanvasObject, err error) { +func (ctn Container) buildVScrollContainer( + e *Element, + s ScreenHandler, +) (c fyne.CanvasObject, err error) { if e.Content == nil { err = errors.New("VScroll: no content defined") return diff --git a/screen/element.go b/screen/element.go index c0bb57b..3ade1a9 100644 --- a/screen/element.go +++ b/screen/element.go @@ -7,11 +7,13 @@ import ( "fyne.io/fyne/v2" ) +// Size represents the yaml description of a ui size type Size struct { Width float32 `yaml:"width"` Height float32 `yaml:"height"` } +// Element represents the yaml description of a ui element type Element struct { ID string `yaml:"id"` @@ -57,6 +59,12 @@ type Element struct { Time *string `yaml:"time"` TimeFormat *string `yaml:"timeFormat"` + // Entry Properties + MultiLine bool `yaml:"multiLine"` + Placeholder string `yaml:"placeholder"` + PlaceholderLocalized bool `yaml:"placeholderLocalized"` + Validator string `yaml:"validator"` + // List Properties ItemTemplate string `yaml:"itemTemplate"` ItemRenderer string `yaml:"itemRenderer"` @@ -70,38 +78,44 @@ type Element struct { FixVertical bool `yaml:"fixVertical"` // Handlers - OnClicked string `yaml:"onClicked"` - OnUpClicked string `yaml:"onUpClicked"` - OnDownClicked string `yaml:"onDownClicked"` - OnChanged string `yaml:"onChanged"` - OnSelected string `yaml:"onSelected"` - OnUnselected string `yaml:"onUnselected"` - OnDateSelected string `yaml:"onDateSelected"` + OnClicked string `yaml:"onClicked"` + OnUpClicked string `yaml:"onUpClicked"` + OnDownClicked string `yaml:"onDownClicked"` + OnChanged string `yaml:"onChanged"` + OnSelected string `yaml:"onSelected"` + OnUnselected string `yaml:"onUnselected"` + OnDateSelected string `yaml:"onDateSelected"` + OnValidationChanged string `yaml:"onValidationChanged"` } +// BuildUI generates the UI for the Element. +// +// It takes a ScreenHandler parameter and returns a fyne.CanvasObject and an error. func (e *Element) BuildUI(s ScreenHandler) (obj fyne.CanvasObject, err error) { + var baseObject fyne.CanvasObject + defer func() { - if e.ID != "" && obj != nil { - s.RegisterElement(e.ID, obj) + if e.ID != "" && baseObject != nil { + s.RegisterElement(e.ID, baseObject, obj) } }() if e.Container != "" { - if obj, err = e.Container.Build(e, s); err != nil { + if baseObject, obj, err = e.Container.Build(e, s); err != nil { err = fmt.Errorf("failed to build container element: %w", err) return } return } if e.Layout != "" { - if obj, err = e.Layout.Build(e, s); err != nil { + if baseObject, obj, err = e.Layout.Build(e, s); err != nil { err = fmt.Errorf("failed to build layout element: %w", err) return } return } if e.Widget != "" { - if obj, err = e.Widget.Build(e, s); err != nil { + if baseObject, obj, err = e.Widget.Build(e, s); err != nil { err = fmt.Errorf("failed to build widget element: %w", err) return } @@ -115,4 +129,7 @@ func (e *Element) localize(s ScreenHandler) { if e.Localized { e.Text = i18n.T(s.GetLocalizer(), e.Text) } + if e.PlaceholderLocalized { + e.Placeholder = i18n.T(s.GetLocalizer(), e.Placeholder) + } } diff --git a/screen/layout.go b/screen/layout.go index 16b7aec..9252bdc 100644 --- a/screen/layout.go +++ b/screen/layout.go @@ -11,34 +11,101 @@ import ( "fyne.io/fyne/v2/layout" ) +// The constants for the Layouts +// +// Available yaml options for all layouts: +// - id: The ID for exporting the layout to code +// - decorators: The decorators to wrap the layout into +// - hidden: Wether the layout is hidden or not type Layout string const ( - Border Layout = "Border" - Form Layout = "Form" - Grid Layout = "Grid" - HBox Layout = "HBox" + // Border Layout + // + // Available yaml options + // - top: The top pane (contains 1 widget) + // - bottom: The bottom pane (contains 1 widget) + // - left: The left pane (contains 1 widget) + // - right: The right pane (contains 1 widget) + // - center: The center pane (contains multiple widgets) + Border Layout = "Border" + + // Form Layout + // + // Available yaml options + // - children: The children of the form + // - label: The label of the form element + // - field: The field of the form element + Form Layout = "Form" + + // Grid Layout + // + // Available yaml options + // - columns: The number of columns (GridWithColumns) + // - rows: The number of rows (GridWithRows) + // - rowColumns: The number of rows and columns (AdaptiveGrid) + // - elementSize: The size of the elements (GridWrap) + // - children: The children of the grid + Grid Layout = "Grid" + + // HBox Layout + // + // Available yaml options + // - children: The children of the HBox + HBox Layout = "HBox" + + // MinSize Layout + // + // Available yaml options + // - minSize: The minimum size of the layout + // - children: The children of the MinSize MinSize Layout = "MinSize" - Stack Layout = "Stack" - VBox Layout = "VBox" + + // Stack Layout + // + // Available yaml options + // - children: The children of the Stack + Stack Layout = "Stack" + + // VBox Layout + // + // Available yaml options + // - children: The children of the VBox + VBox Layout = "VBox" ) -func (l Layout) Build(e *Element, s ScreenHandler) (c fyne.CanvasObject, err error) { +// Build builds the layout based on the given Layout and Element +// using the provided ScreenHandler to fetch functions, data bindings, etc. +// +// Parameters: +// - e: The Element to build the Layout for +// - s: The ScreenHandler to use to fetch functions, data bindings, etc. +// Returns the CanvasObject for the Layout, the CanvasObject for the decorator +// and an error if any. +// If no decorators are specified, the widget and decorator will be the same. +func (l Layout) Build( + e *Element, + s ScreenHandler, +) ( + layout fyne.CanvasObject, + decorator fyne.CanvasObject, + err error, +) { switch l { case Border: - c, err = l.buildBorderLayout(e, s) + layout, err = l.buildBorderLayout(e, s) case Form: - c, err = l.buildFormLayout(e, s) + layout, err = l.buildFormLayout(e, s) case Grid: - c, err = l.buildGridLayout(e, s) + layout, err = l.buildGridLayout(e, s) case HBox: - c, err = l.buildHBoxLayout(e, s) + layout, err = l.buildHBoxLayout(e, s) case MinSize: - c, err = l.buildMinSizeLayout(e, s) + layout, err = l.buildMinSizeLayout(e, s) case Stack: - c, err = l.buildStackLayout(e, s) + layout, err = l.buildStackLayout(e, s) case VBox: - c, err = l.buildVBoxLayout(e, s) + layout, err = l.buildVBoxLayout(e, s) default: err = errors.New("invalid layout") } @@ -46,22 +113,26 @@ func (l Layout) Build(e *Element, s ScreenHandler) (c fyne.CanvasObject, err err return } + decorator = layout if e.Decorators != nil { for _, dec := range e.Decorators { switch dec { case "Border": - c = uiwidget.NewWidgetBorder(c) + decorator = uiwidget.NewWidgetBorder(decorator) } } } if e.Hidden { - c.Hide() + decorator.Hide() } return } -func (l Layout) buildBorderLayout(e *Element, s ScreenHandler) (c *fyne.Container, err error) { +func (l Layout) buildBorderLayout( + e *Element, + s ScreenHandler, +) (c *fyne.Container, err error) { var top, left, right, bottom fyne.CanvasObject var center []fyne.CanvasObject @@ -109,7 +180,10 @@ func (l Layout) buildBorderLayout(e *Element, s ScreenHandler) (c *fyne.Containe return } -func (l Layout) buildFormLayout(e *Element, s ScreenHandler) (c *fyne.Container, err error) { +func (l Layout) buildFormLayout( + e *Element, + s ScreenHandler, +) (c *fyne.Container, err error) { children := make([]fyne.CanvasObject, 0, len(e.Children)*2) for _, child := range e.Children { if child.Label == nil { @@ -149,7 +223,10 @@ func (l Layout) buildFormLayout(e *Element, s ScreenHandler) (c *fyne.Container, return } -func (l Layout) buildGridLayout(e *Element, s ScreenHandler) (c *fyne.Container, err error) { +func (l Layout) buildGridLayout( + e *Element, + s ScreenHandler, +) (c *fyne.Container, err error) { children := make([]fyne.CanvasObject, 0, len(e.Children)) for _, child := range e.Children { var obj fyne.CanvasObject @@ -179,7 +256,10 @@ func (l Layout) buildGridLayout(e *Element, s ScreenHandler) (c *fyne.Container, return } -func (l Layout) buildHBoxLayout(e *Element, s ScreenHandler) (c *fyne.Container, err error) { +func (l Layout) buildHBoxLayout( + e *Element, + s ScreenHandler, +) (c *fyne.Container, err error) { children := make([]fyne.CanvasObject, 0, len(e.Children)) for _, child := range e.Children { var obj fyne.CanvasObject @@ -193,7 +273,10 @@ func (l Layout) buildHBoxLayout(e *Element, s ScreenHandler) (c *fyne.Container, return } -func (l Layout) buildMinSizeLayout(e *Element, s ScreenHandler) (c *fyne.Container, err error) { +func (l Layout) buildMinSizeLayout( + e *Element, + s ScreenHandler, +) (c *fyne.Container, err error) { children := make([]fyne.CanvasObject, 0, len(e.Children)) for _, child := range e.Children { var obj fyne.CanvasObject @@ -216,7 +299,10 @@ func (l Layout) buildMinSizeLayout(e *Element, s ScreenHandler) (c *fyne.Contain return } -func (l Layout) buildStackLayout(e *Element, s ScreenHandler) (c *fyne.Container, err error) { +func (l Layout) buildStackLayout( + e *Element, + s ScreenHandler, +) (c *fyne.Container, err error) { children := make([]fyne.CanvasObject, 0, len(e.Children)) for _, child := range e.Children { var obj fyne.CanvasObject @@ -230,7 +316,10 @@ func (l Layout) buildStackLayout(e *Element, s ScreenHandler) (c *fyne.Container return } -func (l Layout) buildVBoxLayout(e *Element, s ScreenHandler) (c *fyne.Container, err error) { +func (l Layout) buildVBoxLayout( + e *Element, + s ScreenHandler, +) (c *fyne.Container, err error) { children := make([]fyne.CanvasObject, 0, len(e.Children)) for _, child := range e.Children { var obj fyne.CanvasObject diff --git a/screen/listtemplate.go b/screen/listtemplate.go index 38bb839..e31606d 100644 --- a/screen/listtemplate.go +++ b/screen/listtemplate.go @@ -5,15 +5,19 @@ import ( "fyne.io/fyne/v2/widget" ) +// ListItemTemplate is a template for a list item. +// It implements the fyne.CanvasObject interface and TemplateScreenHandler type ListItemTemplate struct { fyne.CanvasObject *TemplateScreenHandler } +// CreateRenderer implements the fyne.CanvasObject interface func (i *ListItemTemplate) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(i.CanvasObject) } +// NewListItemTemplate creates a new ListItemTemplate func NewListItemTemplate( obj fyne.CanvasObject, screenHandler *TemplateScreenHandler, diff --git a/screen/screen.go b/screen/screen.go index 8a4ba89..8138247 100644 --- a/screen/screen.go +++ b/screen/screen.go @@ -16,22 +16,39 @@ import ( "gopkg.in/yaml.v2" ) +type ListItemTemplateFn func() fyne.CanvasObject +type ListDataItemRendererFn func(binding.DataItem, fyne.CanvasObject) +type ListItemRendererFn func(int, fyne.CanvasObject) +type ListLengthFn func() int +type ListItemHandlerFn func(widget.ListItemID) +type ClickHandlerFn func() +type CheckHandlerFn func(bool) +type DateSelectedHandlerFn func(time.Time) +type ValidationChangedHandlerFn func(error) + +type UIElement struct { + Object fyne.CanvasObject + Decorator fyne.CanvasObject +} + type ScreenHandler interface { - RegisterElement(string, fyne.CanvasObject) + RegisterElement(string, fyne.CanvasObject, fyne.CanvasObject) GetLocalizer() *i18n.Localizer GetIcon(string) fyne.Resource GetBinding(string) binding.DataItem - GetListItemTemplate(string) func() fyne.CanvasObject - GetListDataItemRenderer(string) func(binding.DataItem, fyne.CanvasObject) - GetListItemRenderer(string) func(int, fyne.CanvasObject) - GetListLength(string) func() int + GetValidator(string) fyne.StringValidator + GetListItemTemplate(string) ListItemTemplateFn + GetListDataItemRenderer(string) ListDataItemRendererFn + GetListItemRenderer(string) ListItemRendererFn + GetListLength(string) ListLengthFn - GetOnSelectedHandler(string) func(widget.ListItemID) - GetOnUnselectedHandler(string) func(widget.ListItemID) - GetClickedHandler(string) func() - GetCheckChangedHandler(string) func(bool) - GetDateSelectedHandler(string) func(time.Time) + GetOnListItemSelectedHandler(string) ListItemHandlerFn + GetOnListItemUnselectedHandler(string) ListItemHandlerFn + GetOnClickedHandler(string) ClickHandlerFn + GetOnCheckChangedHandler(string) CheckHandlerFn + GetOnDateSelectedHandler(string) DateSelectedHandlerFn + GetOnValidationChangedHandler(string) ValidationChangedHandlerFn } type Screen struct { @@ -79,7 +96,7 @@ func NewTemplate( handler *TemplateScreenHandler, err error, ) { - handler = NewDummyScreenHandler(localizer) + handler = NewTemplateScreenHandler(localizer) scr, err = New(filesystem, name, handler) return } @@ -94,21 +111,27 @@ func (s *Screen) Initialize() (obj fyne.CanvasObject, err error) { type TemplateScreenHandler struct { localizer *i18n.Localizer - elementsMap map[string]fyne.CanvasObject + elementsMap map[string]*UIElement } -func NewDummyScreenHandler(localizer *i18n.Localizer) *TemplateScreenHandler { +func NewTemplateScreenHandler( + localizer *i18n.Localizer, +) *TemplateScreenHandler { return &TemplateScreenHandler{ localizer: localizer, - elementsMap: make(map[string]fyne.CanvasObject), + elementsMap: make(map[string]*UIElement), } } -func (d *TemplateScreenHandler) RegisterElement(name string, element fyne.CanvasObject) { - d.elementsMap[name] = element +func (d *TemplateScreenHandler) RegisterElement( + name string, + element fyne.CanvasObject, + decorator fyne.CanvasObject, +) { + d.elementsMap[name] = &UIElement{element, decorator} } -func (d *TemplateScreenHandler) GetElement(name string) fyne.CanvasObject { +func (d *TemplateScreenHandler) GetElement(name string) *UIElement { elem, ok := d.elementsMap[name] if !ok { return nil @@ -132,38 +155,46 @@ func (*TemplateScreenHandler) GetBinding(string) binding.DataItem { return nil } -func (*TemplateScreenHandler) GetListItemTemplate(string) func() fyne.CanvasObject { +func (*TemplateScreenHandler) GetValidator(string) fyne.StringValidator { + return nil +} + +func (*TemplateScreenHandler) GetListItemTemplate(string) ListItemTemplateFn { return func() fyne.CanvasObject { return nil } } -func (*TemplateScreenHandler) GetListDataItemRenderer(string) func(binding.DataItem, fyne.CanvasObject) { +func (*TemplateScreenHandler) GetListDataItemRenderer(string) ListDataItemRendererFn { return func(binding.DataItem, fyne.CanvasObject) {} } -func (*TemplateScreenHandler) GetListItemRenderer(string) func(int, fyne.CanvasObject) { +func (*TemplateScreenHandler) GetListItemRenderer(string) ListItemRendererFn { return func(int, fyne.CanvasObject) {} } -func (*TemplateScreenHandler) GetListLength(string) func() int { +func (*TemplateScreenHandler) GetListLength(string) ListLengthFn { return func() int { return 0 } } -func (*TemplateScreenHandler) GetOnSelectedHandler(string) func(widget.ListItemID) { +func (*TemplateScreenHandler) GetOnListItemSelectedHandler(string) ListItemHandlerFn { return func(widget.ListItemID) {} } -func (*TemplateScreenHandler) GetOnUnselectedHandler(string) func(widget.ListItemID) { +func (*TemplateScreenHandler) GetOnListItemUnselectedHandler(string) ListItemHandlerFn { return func(widget.ListItemID) {} } -func (*TemplateScreenHandler) GetClickedHandler(string) func() { +func (*TemplateScreenHandler) GetOnClickedHandler(string) ClickHandlerFn { return func() {} } -func (*TemplateScreenHandler) GetCheckChangedHandler(string) func(bool) { +func (*TemplateScreenHandler) GetOnCheckChangedHandler(string) CheckHandlerFn { return func(bool) {} } -func (*TemplateScreenHandler) GetDateSelectedHandler(string) func(time.Time) { +func (*TemplateScreenHandler) GetOnDateSelectedHandler(string) DateSelectedHandlerFn { return func(time.Time) {} } + +func (*TemplateScreenHandler) GetOnValidationChangedHandler(string) ValidationChangedHandlerFn { + return func(error) {} +} diff --git a/screen/widget.go b/screen/widget.go index 66abb46..3c22de3 100644 --- a/screen/widget.go +++ b/screen/widget.go @@ -13,47 +13,216 @@ import ( xwidget "fyne.io/x/fyne/widget" ) +// The constants for the Widgets +// +// Available yaml options for all widgets: +// - id: The ID for exporting the widget to code +// - decorators: The decorators to wrap the widget into +// - hidden: Wether the widget is hidden or not type Widget string const ( - Button Widget = "Button" - Calendar Widget = "Calendar" - Check Widget = "Check" - H1 Widget = "H1" - H2 Widget = "H2" - H3 Widget = "H3" - H4 Widget = "H4" - H5 Widget = "H5" - H6 Widget = "H6" - Label Widget = "Label" - List Widget = "List" - Separator Widget = "Separator" - Spacer Widget = "Spacer" + // Button Widget + // + // Available yaml options: + // - text: The text for the button + // - localized: If the text represents a localization key + // - icon: The icon for the button + // - disabled: Wether the button is disabled or not + // - options: Additional options for the button + // - alignment: The alignment of the button + // - iconPlacement: The icon placement of the button + // - importance: The importance of the button + // - onclicked: The function to call when the button is clicked + Button Widget = "Button" + + // Calendar Widget + // + // Available yaml options: + // - time: The current time to use for the calendar + // - timeFormat: The format to use to interpret the time field (default time.DateOnly) + // - onDateSelected: The function to call when a date is selected + Calendar Widget = "Calendar" + + // Checkbox Widget + // + // Available yaml options: + // - text: The text for the checkbox + // - localized: If the text represents a localization key + // - binding: The databinding for the checkbox + // - checked: The checked state of the checkbox + // - disabled: Wether the button is disabled or not + // - onChanged: The function to call when the checkbox is changed + Check Widget = "Check" + + // Entry Widget + // + // Available yaml options: + // - text: The text for the entry + // - localized: If the text represents a localization key + // - binding: The databinding for the entry + // - placeholder: The placeholder for the entry + // - placeholderLocalized: If the placeholder represents a localization key + // - multiLine: If the entry is multiline + // - disabled: Wether the button is disabled or not + // - validator: The validator for the entry + // - onValidationChanged: The function to call when the validation changes + Entry Widget = "Entry" + + // H1 Widget + // + // Available yaml options: + // - text: The text for the H1 + // - localized: If the text represents a localization key + // - binding: The databinding for the H1 + // - options: The options for the H1 + // - wrapping: The wrapping option for the H1 + // - truncation: The truncation option for the H1 + H1 Widget = "H1" + + // H2 Widget + // + // Available yaml options: + // - text: The text for the H2 + // - localized: If the text represents a localization key + // - binding: The databinding for the H2 + // - options: The options for the H2 + // - wrapping: The wrapping option for the H2 + // - truncation: The truncation option for the H2 + H2 Widget = "H2" + + // H3 Widget + // + // Available yaml options: + // - text: The text for the H3 + // - localized: If the text represents a localization key + // - binding: The databinding for the H3 + // - options: The options for the H3 + // - wrapping: The wrapping option for the H3 + // - truncation: The truncation option for the H3 + H3 Widget = "H3" + + // H4 Widget + // + // Available yaml options: + // - text: The text for the H4 + // - localized: If the text represents a localization key + // - binding: The databinding for the H4 + // - options: The options for the H4 + // - wrapping: The wrapping option for the H4 + // - truncation: The truncation option for the H4 + H4 Widget = "H4" + + // H5 Widget + // + // Available yaml options: + // - text: The text for the H5 + // - localized: If the text represents a localization key + // - binding: The databinding for the H5 + // - options: The options for the H5 + // - wrapping: The wrapping option for the H5 + // - truncation: The truncation option for the H5 + H5 Widget = "H5" + + // H6 Widget + // + // Available yaml options: + // - text: The text for the H6 + // - localized: If the text represents a localization key + // - binding: The databinding for the H6 + // - options: The options for the H6 + // - wrapping: The wrapping option for the H6 + // - truncation: The truncation option for the H6 + H6 Widget = "H6" + + // Icon Widget + // + // Available yaml options: + // - icon: The icon for the icon + Icon Widget = "Icon" + + // Label Widget + // + // Available yaml options: + // - text: The text for the label + // - localized: If the text represents a localization key + // - binding: The databinding for the label + // - options: The options for the label + // - allignment: The alignment option for the label + // - wrapping: The wrapping option for the label + // - textStyle: The text style option for the label + // - truncation: The truncation option for the label + Label Widget = "Label" + + // List Widget + // + // Available yaml options: + // - binding: The databinding for the list + // - listLength: The length of the list + // - itemTemplate: The item template for the list + // - itemRenderer: The item renderer for the list + // - onSelect: The on select function for the list + // - onUnselect: The on unselect function for the list + List Widget = "List" + + // Separator Widget + Separator Widget = "Separator" + + // Spacer Widget + Spacer Widget = "Spacer" + + // UpDownLabel Widget + // + // Available yaml options: + // - binding: The databinding for the up down label + // - onUpClicked: The on up clicked function for the up down label + // - onDownClicked: The on down clicked function for the up down label UpDownLabel Widget = "UpDownLabel" ) -func (w Widget) Build(e *Element, s ScreenHandler) (c fyne.CanvasObject, err error) { +// Build builds a fyne.CanvasObject for the given Widget and Element, +// using the provided ScreenHandler to fetch functions, data bindings, etc. +// +// Arguments: +// - e: The Element to build the Widget for. +// - s: The ScreenHandler to use to fetch functions, data bindings, etc. +// +// Returns the CanvasObject for the widget, the CanvasObject for the decorator, +// and an error if any. +// If no decorators are specified, the widget and decorator will be the same. +func (w Widget) Build( + e *Element, + s ScreenHandler, +) ( + widget fyne.CanvasObject, + decorator fyne.CanvasObject, + err error, +) { e.localize(s) switch w { case Button: - c, err = w.buildButtonWidget(e, s) + widget, err = w.buildButtonWidget(e, s) case Calendar: - c, err = w.buildCalendarWidget(e, s) + widget, err = w.buildCalendarWidget(e, s) case Check: - c, err = w.buildCheckWidget(e, s) + widget, err = w.buildCheckWidget(e, s) + case Entry: + widget, err = w.buildEntryWidget(e, s) case H1, H2, H3, H4, H5, H6: - c, err = w.buildHeaderLabelWidget(e, s) + widget, err = w.buildHeaderLabelWidget(e, s) + case Icon: + widget, err = w.buildIconWidget(e, s) case Label: - c, err = w.buildLabelWidget(e, s) + widget, err = w.buildLabelWidget(e, s) case List: - c, err = w.buildListWidget(e, s) + widget, err = w.buildListWidget(e, s) case Separator: - c, err = w.buildSeparatorWidget(e, s) + widget, err = w.buildSeparatorWidget(e, s) case Spacer: - c, err = w.buildSpacerWidget(e, s) + widget, err = w.buildSpacerWidget(e, s) case UpDownLabel: - c, err = w.buildUpDownLabelWidget(e, s) + widget, err = w.buildUpDownLabelWidget(e, s) default: err = errors.New("invalid widget") } @@ -61,28 +230,32 @@ func (w Widget) Build(e *Element, s ScreenHandler) (c fyne.CanvasObject, err err return } + decorator = widget if e.Decorators != nil { for _, dec := range e.Decorators { switch dec { case "Border": - c = uiwidget.NewWidgetBorder(c) + decorator = uiwidget.NewWidgetBorder(decorator) } } } if e.Hidden { - c.Hide() + decorator.Hide() } return } -func (w Widget) buildButtonWidget(e *Element, s ScreenHandler) (c fyne.CanvasObject, err error) { +func (w Widget) buildButtonWidget( + e *Element, + s ScreenHandler, +) (c fyne.CanvasObject, err error) { var btn *widget.Button if e.Icon != "" { btn = widget.NewButtonWithIcon( - e.Text, s.GetIcon(e.Icon), s.GetClickedHandler(e.OnClicked)) + e.Text, s.GetIcon(e.Icon), s.GetOnClickedHandler(e.OnClicked)) } else { - btn = widget.NewButton(e.Text, s.GetClickedHandler(e.OnClicked)) + btn = widget.NewButton(e.Text, s.GetOnClickedHandler(e.OnClicked)) } for opt, val := range e.Options { @@ -103,7 +276,10 @@ func (w Widget) buildButtonWidget(e *Element, s ScreenHandler) (c fyne.CanvasObj return } -func (w Widget) buildCalendarWidget(e *Element, s ScreenHandler) (c fyne.CanvasObject, err error) { +func (w Widget) buildCalendarWidget( + e *Element, + s ScreenHandler, +) (c fyne.CanvasObject, err error) { t := time.Now() if e.Time != nil { timeFormat := time.DateOnly @@ -116,18 +292,21 @@ func (w Widget) buildCalendarWidget(e *Element, s ScreenHandler) (c fyne.CanvasO } } - c = xwidget.NewCalendar(t, s.GetDateSelectedHandler(e.OnDateSelected)) + c = xwidget.NewCalendar(t, s.GetOnDateSelectedHandler(e.OnDateSelected)) return } -func (w Widget) buildCheckWidget(e *Element, s ScreenHandler) (c fyne.CanvasObject, err error) { +func (w Widget) buildCheckWidget( + e *Element, + s ScreenHandler, +) (c fyne.CanvasObject, err error) { var chk *widget.Check if e.Binding != "" { chk = widget.NewCheckWithData( e.Text, s.GetBinding(e.Binding).(binding.Bool)) } else { chk = widget.NewCheck( - e.Text, s.GetCheckChangedHandler(e.OnChanged)) + e.Text, s.GetOnCheckChangedHandler(e.OnChanged)) chk.SetChecked(e.Checked) } @@ -138,7 +317,40 @@ func (w Widget) buildCheckWidget(e *Element, s ScreenHandler) (c fyne.CanvasObje return } -func (w Widget) buildHeaderLabelWidget(e *Element, s ScreenHandler) (c fyne.CanvasObject, err error) { +func (w Widget) buildEntryWidget( + e *Element, + s ScreenHandler, +) (c fyne.CanvasObject, err error) { + var ent *widget.Entry + if e.MultiLine { + ent = widget.NewMultiLineEntry() + } else { + ent = widget.NewEntry() + } + if e.Binding != "" { + ent.Bind(s.GetBinding(e.Binding).(binding.String)) + } else { + ent.Text = e.Text + } + ent.PlaceHolder = e.Placeholder + if e.Validator != "" { + ent.Validator = s.GetValidator(e.Validator) + } + if e.OnValidationChanged != "" { + ent.SetOnValidationChanged(s.GetOnValidationChangedHandler(e.OnValidationChanged)) + } + + if e.Disabled { + ent.Disable() + } + c = ent + return +} + +func (w Widget) buildHeaderLabelWidget( + e *Element, + s ScreenHandler, +) (c fyne.CanvasObject, err error) { var rt *widget.RichText level := 1 @@ -174,7 +386,18 @@ func (w Widget) buildHeaderLabelWidget(e *Element, s ScreenHandler) (c fyne.Canv return } -func (w Widget) buildLabelWidget(e *Element, s ScreenHandler) (c fyne.CanvasObject, err error) { +func (w Widget) buildIconWidget( + e *Element, + s ScreenHandler, +) (c fyne.CanvasObject, err error) { + c = widget.NewIcon(s.GetIcon(e.Icon)) + return +} + +func (w Widget) buildLabelWidget( + e *Element, + s ScreenHandler, +) (c fyne.CanvasObject, err error) { var lbl *widget.Label if e.Binding != "" { @@ -201,7 +424,10 @@ func (w Widget) buildLabelWidget(e *Element, s ScreenHandler) (c fyne.CanvasObje return } -func (w Widget) buildListWidget(e *Element, s ScreenHandler) (c fyne.CanvasObject, err error) { +func (w Widget) buildListWidget( + e *Element, + s ScreenHandler, +) (c fyne.CanvasObject, err error) { var lst *widget.List if e.Binding != "" { @@ -217,22 +443,28 @@ func (w Widget) buildListWidget(e *Element, s ScreenHandler) (c fyne.CanvasObjec } if e.OnSelected != "" { - lst.OnSelected = s.GetOnSelectedHandler(e.OnSelected) + lst.OnSelected = s.GetOnListItemSelectedHandler(e.OnSelected) } if e.OnUnselected != "" { - lst.OnUnselected = s.GetOnUnselectedHandler(e.OnUnselected) + lst.OnUnselected = s.GetOnListItemUnselectedHandler(e.OnUnselected) } c = lst return } -func (w Widget) buildSeparatorWidget(_ *Element, _ ScreenHandler) (c fyne.CanvasObject, err error) { +func (w Widget) buildSeparatorWidget( + _ *Element, + _ ScreenHandler, +) (c fyne.CanvasObject, err error) { sep := widget.NewSeparator() c = sep return } -func (w Widget) buildSpacerWidget(e *Element, _ ScreenHandler) (c fyne.CanvasObject, err error) { +func (w Widget) buildSpacerWidget( + e *Element, + _ ScreenHandler, +) (c fyne.CanvasObject, err error) { spc := &layout.Spacer{ FixHorizontal: e.FixHorizontal, FixVertical: e.FixVertical, @@ -241,11 +473,14 @@ func (w Widget) buildSpacerWidget(e *Element, _ ScreenHandler) (c fyne.CanvasObj return } -func (w Widget) buildUpDownLabelWidget(e *Element, s ScreenHandler) (c fyne.CanvasObject, err error) { +func (w Widget) buildUpDownLabelWidget( + e *Element, + s ScreenHandler, +) (c fyne.CanvasObject, err error) { btn := uiwidget.NewUpDownLabelWithData( s.GetBinding(e.Binding).(binding.String), - s.GetClickedHandler(e.OnUpClicked), - s.GetClickedHandler(e.OnDownClicked)) + s.GetOnClickedHandler(e.OnUpClicked), + s.GetOnClickedHandler(e.OnDownClicked)) c = btn return } diff --git a/screenhandler.go b/screenhandler.go new file mode 100644 index 0000000..1912a39 --- /dev/null +++ b/screenhandler.go @@ -0,0 +1,351 @@ +package ui + +import ( + "embed" + "fmt" + "strings" + + "bitbucket.org/hevanto/ui/screen" + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/data/binding" + "fyne.io/fyne/v2/theme" + "github.com/nicksnyder/go-i18n/v2/i18n" +) + +// ScreenHandler represents the handler for a screen. +// +// It provides: +// - The screen definition +// - The localizer +// - The screen controller +// - Exported elements +// - List item templates and renderers +// - User interaction handlers +type ScreenHandler struct { + screenDefinition *screen.Screen + localizer *i18n.Localizer + ctrl ScreenController + + // Map of screen elements that got an id assigned + exportedElements map[string]*screen.UIElement + + listItemTemplates map[string]screen.ListItemTemplateFn + listItemRenderers map[string]screen.ListItemRendererFn + listDataItemRenderers map[string]screen.ListDataItemRendererFn + listLengths map[string]screen.ListLengthFn + + onListItemSelectedHandlers map[string]screen.ListItemHandlerFn + onListItemUnselectedHandlers map[string]screen.ListItemHandlerFn + onClickedHandlers map[string]screen.ClickHandlerFn + onCheckChangedHandlers map[string]screen.CheckHandlerFn + onDateSelectedHandlers map[string]screen.DateSelectedHandlerFn + onValidationChangedHandlers map[string]screen.ValidationChangedHandlerFn +} + +// NewScreenHandler initializes a new ScreenHandler with the provided filesystem, screen name, ScreenController, and localizer. +// +// Arguments: +// - filesystem: the embedded filesystem to access screen resources. +// - screenName: the name of the screen to load. +// - ctrl: the ScreenController for the screen. +// - localizer: the i18n.Localizer for localization. +// +// Returns: +// - h: the initialized ScreenHandler. +// - err: an error if loading the screen fails. +func NewScreenHandler( + filesystem embed.FS, + screenName string, + ctrl ScreenController, + localizer *i18n.Localizer, +) (h *ScreenHandler, err error) { + h = new(ScreenHandler) + + h.ctrl = ctrl + h.localizer = localizer + + def, err := screen.New(filesystem, screenName, h) + if err != nil { + err = fmt.Errorf("failure loading screen: %w", err) + return + } + h.screenDefinition = def + + h.exportedElements = make(map[string]*screen.UIElement) + h.listItemTemplates = make(map[string]screen.ListItemTemplateFn) + h.listItemRenderers = make(map[string]screen.ListItemRendererFn) + h.listDataItemRenderers = make(map[string]screen.ListDataItemRendererFn) + h.listLengths = make(map[string]screen.ListLengthFn) + h.onListItemSelectedHandlers = make(map[string]screen.ListItemHandlerFn) + h.onListItemUnselectedHandlers = make(map[string]screen.ListItemHandlerFn) + h.onClickedHandlers = make(map[string]screen.ClickHandlerFn) + h.onCheckChangedHandlers = make(map[string]screen.CheckHandlerFn) + h.onDateSelectedHandlers = make(map[string]screen.DateSelectedHandlerFn) + h.onValidationChangedHandlers = make(map[string]screen.ValidationChangedHandlerFn) + return +} + +// ScreenDefinition returns the screen definition for the ScreenHandler. +func (h *ScreenHandler) ScreenDefinition() *screen.Screen { + return h.screenDefinition +} + +// RegisterElement registers an element with the ScreenHandler. +// +// This function is called for every element in the screen definition fow which +// an ID is defined. +func (h *ScreenHandler) RegisterElement( + id string, + elem fyne.CanvasObject, + decorator fyne.CanvasObject, +) { + h.exportedElements[id] = &screen.UIElement{Object: elem, Decorator: decorator} +} + +// RegisterListItemTemplate registers a ListItemTemplate with the ScreenHandler. +// +// Use this function to register the templates for the lists used in the +// screen definition. +func (h *ScreenHandler) RegisterListItemTemplate( + name string, + fn screen.ListItemTemplateFn, +) { + h.listItemTemplates[name] = fn +} + +// RegisterListDataItemRenderer registers a ListDataItemRenderer with the ScreenHandler. +// +// Use this function to register the renderers for the lists used in the +// screen definition. +func (h *ScreenHandler) RegisterListDataItemRenderer( + name string, + fn screen.ListDataItemRendererFn, +) { + h.listDataItemRenderers[name] = fn +} + +// RegisterListItemRenderer registers a ListItemRenderer with the ScreenHandler. +// +// Use this function to register the renderers for the lists used in the +// screen definition. +func (h *ScreenHandler) RegisterListItemRenderer( + name string, + fn screen.ListItemRendererFn, +) { + h.listItemRenderers[name] = fn +} + +// RegisterListLength registers a ListLength with the ScreenHandler. +// +// Use this function to register the lengths for the lists used in the +// screen definition. +func (h *ScreenHandler) RegisterListLength( + name string, + fn screen.ListLengthFn, +) { + h.listLengths[name] = fn +} + +// RegisterOnListItemSelectedHandler registers a OnListItemSelectedHandler with the ScreenHandler. +// +// Use this function to register the handler functions used in the screen +// definition. +func (h *ScreenHandler) RegisterOnListItemSelectedHandler( + name string, + fn screen.ListItemHandlerFn, +) { + h.onListItemSelectedHandlers[name] = fn +} + +// RegisterOnListItemUnselectedHandler registers a OnListItemUnselectedHandler with the ScreenHandler. +// +// Use this function to register the handler functions used in the screen +// definition. +func (h *ScreenHandler) RegisterOnListItemUnselectedHandler( + name string, + fn screen.ListItemHandlerFn, +) { + h.onListItemUnselectedHandlers[name] = fn +} + +// RegisterOnClickHandler registers a OnClickHandler with the ScreenHandler. +// +// Use this function to register the handler functions used in the screen +// definition. +func (h *ScreenHandler) RegisterOnClickHandler( + name string, + fn screen.ClickHandlerFn, +) { + h.onClickedHandlers[name] = fn +} + +// RegisterOnCheckChangedHandler registers a OnCheckChangedHandler with the ScreenHandler. +// +// Use this function to register the handler functions used in the screen +// definition. +func (h *ScreenHandler) RegisterOnCheckChangedHandler( + name string, + fn screen.CheckHandlerFn, +) { + h.onCheckChangedHandlers[name] = fn +} + +// RegisterOnDateSelectedHandler registers a OnDateSelectedHandler with the ScreenHandler. +// +// Use this function to register the handler functions used in the screen +// definition. +func (h *ScreenHandler) RegisterOnDateSelectedHandler( + name string, + fn screen.DateSelectedHandlerFn, +) { + h.onDateSelectedHandlers[name] = fn +} + +// RegisterOnValidationChangedHandler registers a OnValidationChangedHandler with the ScreenHandler. +// +// Use this function to register the handler functions used in the screen +// definition. +func (h *ScreenHandler) RegisterOnValidationChangedHandler( + name string, + fn screen.ValidationChangedHandlerFn, +) { + h.onValidationChangedHandlers[name] = fn +} + +// GetBinding returns the binding with the given name. +// +// This function forwards the call to the controller. In the controller the +// required bindings should be mapped to the screen elements. +func (h *ScreenHandler) GetBinding(name string) binding.DataItem { + return h.ctrl.GetBinding(name) +} + +// GetValidator returns the validator with the given name. +// +// This function forwards the call to the controller. In the controller the +// required validators should be mapped to the screen elements. +func (h *ScreenHandler) GetValidator(name string) fyne.StringValidator { + return h.ctrl.GetValidator(name) +} + +// GetElement returns the UIElement with the given ID. +// +// This function allows you to retrieve the exported elements (given an ID in +// the screen definition). The UIElement contains the object and the decorator. +// Should no decorator be present, both point to the same widget. If a decorator +// is present it's advices to do hide and show operations on the decorator, and +// all other operators on the object. +func (h *ScreenHandler) GetElement(id string) *screen.UIElement { + if elem, ok := h.exportedElements[id]; ok { + return elem + } + return nil +} + +// GetLocalizer returns the i18n.Localizer used for localization. +func (h *ScreenHandler) GetLocalizer() *i18n.Localizer { + return h.localizer +} + +// GetIcon returns the icon with the given name. +// +// This handler only implements the default theme icons. If you wish to support +// other items, wrap this ScreenHandler and implement the loading of the icons +// yourself. +func (h *ScreenHandler) GetIcon(iconName string) fyne.Resource { + if strings.HasPrefix(iconName, "theme.") { + iconName := strings.SplitN(iconName, ".", 2)[1] + return theme.DefaultTheme().Icon(fyne.ThemeIconName(iconName)) + } + return nil +} + +// GetListItemTemplate returns the ListItemTemplate with the given name. +func (h *ScreenHandler) GetListItemTemplate(name string) screen.ListItemTemplateFn { + fn, ok := h.listItemTemplates[name] + if !ok { + return nil + } + return fn +} + +// GetListDataItemRenderer returns the ListDataItemRenderer with the given name. +func (h *ScreenHandler) GetListDataItemRenderer(name string) screen.ListDataItemRendererFn { + fn, ok := h.listDataItemRenderers[name] + if !ok { + return nil + } + return fn +} + +// GetListItemRenderer returns the ListItemRenderer with the given name. +func (h *ScreenHandler) GetListItemRenderer(name string) screen.ListItemRendererFn { + fn, ok := h.listItemRenderers[name] + if !ok { + return nil + } + return fn +} + +// GetListLength returns the ListLength with the given name. +func (h *ScreenHandler) GetListLength(name string) screen.ListLengthFn { + fn, ok := h.listLengths[name] + if !ok { + return nil + } + return fn +} + +// GetOnListItemSelectedHandler returns the OnListItemSelectedHandler with the given name. +func (h *ScreenHandler) GetOnListItemSelectedHandler(name string) screen.ListItemHandlerFn { + fn, ok := h.onListItemSelectedHandlers[name] + if !ok { + return nil + } + return fn +} + +// GetOnListItemUnselectedHandler returns the OnListItemUnselectedHandler with the given name. +func (h *ScreenHandler) GetOnListItemUnselectedHandler(name string) screen.ListItemHandlerFn { + fn, ok := h.onListItemUnselectedHandlers[name] + if !ok { + return nil + } + return fn +} + +// GetOnClickedHandler returns the OnClickedHandler with the given name. +func (h *ScreenHandler) GetOnClickedHandler(name string) screen.ClickHandlerFn { + fn, ok := h.onClickedHandlers[name] + if !ok { + return nil + } + return fn +} + +// GetOnCheckChangedHandler returns the OnCheckChangedHandler with the given name. +func (h *ScreenHandler) GetOnCheckChangedHandler(name string) screen.CheckHandlerFn { + fn, ok := h.onCheckChangedHandlers[name] + if !ok { + return nil + } + return fn +} + +// GetOnDateSelectedHandler returns the OnDateSelectedHandler with the given name. +func (h *ScreenHandler) GetOnDateSelectedHandler(name string) screen.DateSelectedHandlerFn { + fn, ok := h.onDateSelectedHandlers[name] + if !ok { + return nil + } + return fn +} + +// GetOnValidationChangedHandler returns the OnValidationChangedHandler with the given name. +func (h *ScreenHandler) GetOnValidationChangedHandler(name string) screen.ValidationChangedHandlerFn { + fn, ok := h.onValidationChangedHandlers[name] + if !ok { + return nil + } + return fn +} diff --git a/tabbedview.go b/tabbedview.go index 4163cfb..f940586 100644 --- a/tabbedview.go +++ b/tabbedview.go @@ -7,6 +7,8 @@ import ( "fyne.io/fyne/v2/container" ) +// TODO: Implement screen reading from file! + type TabbedView struct { *BaseView diff --git a/uilayout/minsize.go b/uilayout/minsize.go index e570d76..658a842 100644 --- a/uilayout/minsize.go +++ b/uilayout/minsize.go @@ -2,14 +2,26 @@ package uilayout import "fyne.io/fyne/v2" +// MinSize implements a min size layout. +// +// The MinSize layout will ensure that the container is rendered at a size that +// is at least as large as the minimum size provided. type MinSize struct { minSize fyne.Size } +// NewMinSizeLayout creates a new MinSize layout with the specified minimum size. +// +// Arguments: +// - minSize: the minimum size for the layout. +// +// Returns *MinSize: a pointer to the newly created MinSize layout. func NewMinSizeLayout(minSize fyne.Size) *MinSize { return &MinSize{minSize: minSize} } +// MinSize calculates the minimum size of the layout on the current screen. The +// reported size will be at least as large as the configured minimum size. func (l *MinSize) MinSize(objects []fyne.CanvasObject) fyne.Size { size := l.minSize for _, o := range objects { @@ -24,6 +36,7 @@ func (l *MinSize) MinSize(objects []fyne.CanvasObject) fyne.Size { return size } +// Layout implements the Layout interface. func (l *MinSize) Layout(objects []fyne.CanvasObject, containerSize fyne.Size) { pos := fyne.NewPos(0, 0) for _, o := range objects { diff --git a/uiwidget/label.go b/uiwidget/label.go index f65312a..cd00685 100644 --- a/uiwidget/label.go +++ b/uiwidget/label.go @@ -11,6 +11,7 @@ import ( "fyne.io/fyne/v2/widget" ) +// LabelOpts defines the options for a label. type LabelOpts interface { ApplyTo(*widget.Label) } @@ -19,10 +20,12 @@ type allignmentOpt struct { Alignment fyne.TextAlign } +// AlignmentOpt returns a LabelOpts with the specified alignment. func AlignmentOpt(alignment fyne.TextAlign) LabelOpts { return &allignmentOpt{alignment} } +// ApplyTo sets the alignment of a label to the specified alignment. func (a *allignmentOpt) ApplyTo(lbl *widget.Label) { lbl.Alignment = a.Alignment } @@ -31,10 +34,12 @@ type wrappingOpt struct { Wrapping fyne.TextWrap } +// WrappingOpt creates a LabelOpts with the specified text wrapping option. func WrappingOpt(wrapping fyne.TextWrap) LabelOpts { return &wrappingOpt{wrapping} } +// ApplyTo applies the wrapping option to the label. func (w *wrappingOpt) ApplyTo(lbl *widget.Label) { lbl.Wrapping = w.Wrapping } @@ -43,10 +48,12 @@ type textStyleOpt struct { Style fyne.TextStyle } +// TextStyleOpt returns the label options for the given text style. func TextStyleOpt(style fyne.TextStyle) LabelOpts { return &textStyleOpt{style} } +// ApplyTo applies the text style option to the label. func (t *textStyleOpt) ApplyTo(lbl *widget.Label) { lbl.TextStyle = t.Style } @@ -55,10 +62,12 @@ type truncationOpt struct { Truncation fyne.TextTruncation } +// TruncationOpt returns the label options for the given text truncation option. func TruncationOpt(truncation fyne.TextTruncation) LabelOpts { return &truncationOpt{truncation} } +// ApplyTo applies the text truncation option to the label. func (t *truncationOpt) ApplyTo(lbl *widget.Label) { lbl.Truncation = t.Truncation } @@ -67,14 +76,23 @@ type importanceOpt struct { Importance widget.Importance } +// ImportanceOpt returns the label options for the given text importance option. func ImportanceOpt(importance widget.Importance) LabelOpts { return &importanceOpt{importance} } +// ApplyTo applies the text importance option to the label. func (i *importanceOpt) ApplyTo(lbl *widget.Label) { lbl.Importance = i.Importance } +// NewLabel creates a new label widget with the given text and options. +// +// Arguments +// - text: the text to be displayed on the label. +// - opts: optional configuration options for the label. +// +// Returns *widget.Label: a pointer to the newly created label widget. func NewLabel(text string, opts ...LabelOpts) *widget.Label { lbl := widget.NewLabel(text) for _, opt := range opts { @@ -83,6 +101,13 @@ func NewLabel(text string, opts ...LabelOpts) *widget.Label { return lbl } +// NewLabelWithData creates a new Label widget with the provided data and options. +// +// Arguments +// - data: The string data to be displayed on the label. +// - opts: Additional options to customize the label. +// +// Returns *widget.Label: a pointer to the newly created Label widget. func NewLabelWithData(data binding.String, opts ...LabelOpts) *widget.Label { lbl := widget.NewLabelWithData(data) for _, opt := range opts { @@ -91,6 +116,14 @@ func NewLabelWithData(data binding.String, opts ...LabelOpts) *widget.Label { return lbl } +// NewUpDownLabelWithData creates a new label widget with up and down buttons. +// +// Arguments +// - data: the string data to display in the label. +// - upHandler: the function to call when the up button is clicked. +// - downHandler: the function to call when the down button is clicked. +// +// Returns fyne.CanvasObject: the created widget. func NewUpDownLabelWithData( data binding.String, upHandler func(), @@ -115,11 +148,25 @@ func NewUpDownLabelWithData( return c1 } +// NewH creates a RichText widget formatted as a header of the provided level. +// +// Parameters: +// - level: an integer representing the level of the text. +// - text: a string containing the text to display. +// +// Returns a pointer to a RichText widget. func NewH(level int, text string) *widget.RichText { return widget.NewRichTextFromMarkdown( fmt.Sprintf("%s %s", strings.Repeat("#", level), text)) } +// NewHWithData creates a new RichText widget formatted as a header of the provided level. +// +// Parameters: +// - level: an integer representing the level of the text. +// - data: a string binding containing the text to display. +// +// Returns a pointer to a RichText widget. func NewHWithData(level int, data binding.String) *widget.RichText { txt, _ := data.Get() w := NewH(level, txt) @@ -130,50 +177,62 @@ func NewHWithData(level int, data binding.String) *widget.RichText { return w } +// NewH1 creates a new RichText widget with header level 1. func NewH1(text string) *widget.RichText { return NewH(1, text) } +// NewH1WithData creates a new RichText widget with header level 1. func NewH1WithData(data binding.String) *widget.RichText { return NewHWithData(1, data) } +// NewH2 creates a new RichText widget with header level 2. func NewH2(text string) *widget.RichText { return NewH(2, text) } +// NewH2WithData creates a new RichText widget with header level 2. func NewH2WithData(data binding.String) *widget.RichText { return NewHWithData(2, data) } +// NewH3 creates a new RichText widget with header level 3. func NewH3(text string) *widget.RichText { return NewH(3, text) } +// NewH3WithData creates a new RichText widget with header level 3. func NewH3WithData(data binding.String) *widget.RichText { return NewHWithData(3, data) } +// NewH4 creates a new RichText widget with header level 4. func NewH4(text string) *widget.RichText { return NewH(4, text) } +// NewH4WithData creates a new RichText widget with header level 4. func NewH4WithData(data binding.String) *widget.RichText { return NewHWithData(4, data) } +// NewH5 creates a new RichText widget with header level 5. func NewH5(text string) *widget.RichText { return NewH(5, text) } +// NewH5WithData creates a new RichText widget with header level 5. func NewH5WithData(data binding.String) *widget.RichText { return NewHWithData(5, data) } +// NewH6 creates a new RichText widget with header level 6. func NewH6(text string) *widget.RichText { return NewH(6, text) } +// NewH6WithData creates a new RichText widget with header level 6. func NewH6WithData(data binding.String) *widget.RichText { return NewHWithData(6, data) } diff --git a/uiwidget/widgetborder.go b/uiwidget/widgetborder.go index b431d3d..2336ed1 100644 --- a/uiwidget/widgetborder.go +++ b/uiwidget/widgetborder.go @@ -9,6 +9,9 @@ import ( "fyne.io/fyne/v2/theme" ) +// NewWidgetBorder creates a new widget border for the given fyne.CanvasObject. +// It does so by wrapping the object in a Stack layout with a border drawn +// above the widget. func NewWidgetBorder(widget fyne.CanvasObject) fyne.CanvasObject { b := canvas.NewRectangle(color.Transparent) b.StrokeColor = theme.InputBorderColor()