Implement defining screens in yaml

This commit is contained in:
Maarten Heremans 2024-04-03 16:07:24 +02:00
parent b9d2b680bc
commit c848c2332f
15 changed files with 1204 additions and 334 deletions

View File

@ -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)
}

View File

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

View File

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

204
dialog.go
View File

@ -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

View File

@ -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

View File

@ -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)
}
}

View File

@ -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

View File

@ -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,

View File

@ -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) {}
}

View File

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

351
screenhandler.go Normal file
View File

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

View File

@ -7,6 +7,8 @@ import (
"fyne.io/fyne/v2/container"
)
// TODO: Implement screen reading from file!
type TabbedView struct {
*BaseView

View File

@ -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 {

View File

@ -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)
}

View File

@ -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()