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 package ui
import ( import (
"time" "embed"
"bitbucket.org/hevanto/ui/screen"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/widget"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/nicksnyder/go-i18n/v2/i18n" "github.com/nicksnyder/go-i18n/v2/i18n"
) )
@ -26,35 +24,8 @@ type BaseView struct {
// Root CanvasObject of the view // Root CanvasObject of the view
CanvasObject fyne.CanvasObject CanvasObject fyne.CanvasObject
// The screen definition
screenDefinition *screen.Screen
// ----------- ScreenHandler -------------// // ----------- ScreenHandler -------------//
*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)
} }
// NewBaseView creates a new baseview // NewBaseView creates a new baseview
@ -80,6 +51,28 @@ func NewBaseView(
return v 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 // ID returns tie id of the view
func (v BaseView) ID() string { func (v BaseView) ID() string {
return v.id return v.id
@ -106,8 +99,8 @@ func (v *BaseView) Initialize() fyne.CanvasObject {
if err := v.Ctrl.Initialize(); err != nil { if err := v.Ctrl.Initialize(); err != nil {
LogWindowError(v, Initialize, err) LogWindowError(v, Initialize, err)
} }
if v.screenDefinition != nil { if v.ScreenHandler != nil && v.ScreenDefinition() != nil {
obj, err := v.screenDefinition.Initialize() obj, err := v.ScreenDefinition().Initialize()
if err != nil { if err != nil {
LogWindowError(v, Initialize, err) LogWindowError(v, Initialize, err)
return nil return nil
@ -133,3 +126,7 @@ func (v *BaseView) OnShow() {
func (v *BaseView) OnHide() { func (v *BaseView) OnHide() {
LogWindowEvent(v, 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 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 { type Controller interface {
ScreenController
Initialize() error Initialize() error
RefreshData() error RefreshData() error
GetBinding(string) binding.DataItem
} }
type DialogController interface { type DialogController interface {
ScreenController
Initialize(data binding.DataItem) error Initialize(data binding.DataItem) error
RefreshData() error RefreshData() error
} }

204
dialog.go
View File

@ -1,6 +1,7 @@
package ui package ui
import ( import (
"embed"
"fmt" "fmt"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
@ -8,13 +9,22 @@ import (
"fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/nicksnyder/go-i18n/v2/i18n"
) )
type DialogName string type DialogName string
type DialogKind string type DialogKind string
type OnConfirmFn func(bool, error, ...interface{}) 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 ( const (
DlgGeneric DialogKind = "Generic" DlgGeneric DialogKind = "Generic"
@ -47,12 +57,16 @@ type BaseDialog struct {
CanvasObj fyne.CanvasObject CanvasObj fyne.CanvasObject
Data binding.DataItem Data binding.DataItem
focusItem fyne.Focusable focusItem fyne.Focusable
Ctrl DialogController
*ScreenHandler
} }
func NewBaseDialog( func NewBaseDialog(
concreteDialog Dialog, concreteDialog Dialog,
name DialogName, name DialogName,
kind DialogKind, kind DialogKind,
controller DialogController,
parent fyne.Window, parent fyne.Window,
confirm OnConfirmFn, confirm OnConfirmFn,
) *BaseDialog { ) *BaseDialog {
@ -63,13 +77,40 @@ func NewBaseDialog(
dlgKind: kind, dlgKind: kind,
window: parent, window: parent,
onConfirm: confirm, 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( func NewBaseDialogWithData(
concreteDialog Dialog, concreteDialog Dialog,
name DialogName, name DialogName,
kind DialogKind, kind DialogKind,
controller DialogController,
parent fyne.Window, parent fyne.Window,
confirm OnConfirmFn, confirm OnConfirmFn,
data binding.DataItem, data binding.DataItem,
@ -82,9 +123,36 @@ func NewBaseDialogWithData(
window: parent, window: parent,
onConfirm: confirm, onConfirm: confirm,
Data: data, 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 { func (d BaseDialog) Window() fyne.Window {
return d.window return d.window
} }
@ -114,6 +182,7 @@ func (d BaseDialog) OnConfirmFn() OnConfirmFn {
} }
func (d *BaseDialog) Show(parent ...fyne.Window) { func (d *BaseDialog) Show(parent ...fyne.Window) {
d.concreteDialog.OnShow() d.concreteDialog.OnShow()
d.dlg = dialog.NewCustomConfirm( d.dlg = dialog.NewCustomConfirm(
d.concreteDialog.Title(), d.concreteDialog.Title(),
@ -146,6 +215,17 @@ func (d BaseDialog) Title() string {
} }
func (d *BaseDialog) Initialize() fyne.CanvasObject { 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 return d.CanvasObj
} }
@ -184,35 +264,80 @@ func NewFormDialog(
concreteDialog Dialog, concreteDialog Dialog,
name DialogName, name DialogName,
kind DialogKind, kind DialogKind,
controller DialogController,
parent fyne.Window, parent fyne.Window,
confirm OnConfirmFn, confirm OnConfirmFn,
itemsFn FormDialogItemsFn, itemsFn FormDialogItemsFn,
) *FormDialog { ) *FormDialog {
return &FormDialog{ return &FormDialog{
BaseDialog: NewBaseDialog(concreteDialog, name, kind, parent, confirm), BaseDialog: NewBaseDialog(
concreteDialog, name, kind, controller, parent, confirm),
itemsFn: itemsFn, itemsFn: itemsFn,
confirmLabel: "OK", confirmLabel: "OK",
dismissLabel: "Cancel", 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( func NewformDialogWithData(
concreteDialog Dialog, concreteDialog Dialog,
name DialogName, name DialogName,
kind DialogKind, kind DialogKind,
controller DialogController,
parent fyne.Window, parent fyne.Window,
confirm OnConfirmFn, confirm OnConfirmFn,
itemsFn FormDialogItemsFn, itemsFn FormDialogItemsFn,
data binding.DataItem, data binding.DataItem,
) *FormDialog { ) *FormDialog {
return &FormDialog{ return &FormDialog{
BaseDialog: NewBaseDialogWithData(concreteDialog, name, kind, parent, confirm, data), BaseDialog: NewBaseDialogWithData(
concreteDialog, name, kind, controller, parent, confirm, data),
itemsFn: itemsFn, itemsFn: itemsFn,
confirmLabel: "OK", confirmLabel: "OK",
dismissLabel: "Cancel", 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( func (d *FormDialog) SetButtonLabels(
confirm string, confirm string,
dismiss string, dismiss string,
@ -221,7 +346,25 @@ func (d *FormDialog) SetButtonLabels(
d.dismissLabel = dismiss 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) { func (d *FormDialog) Show(parent ...fyne.Window) {
d.concreteDialog.Initialize()
d.items = d.itemsFn() d.items = d.itemsFn()
d.concreteDialog.OnShow() d.concreteDialog.OnShow()
@ -263,7 +406,8 @@ func RegisterDialog(name DialogName, kind DialogKind, fn NewDialogFn) {
} }
func NewDialog( func NewDialog(
name DialogName, kind DialogKind, name DialogName,
kind DialogKind,
parent fyne.Window, parent fyne.Window,
confirm OnConfirmFn, confirm OnConfirmFn,
) Dialog { ) Dialog {
@ -272,14 +416,37 @@ func NewDialog(
} }
if kindCatalog, ok := dialogCatalog[name]; ok { if kindCatalog, ok := dialogCatalog[name]; ok {
if fn, ok := kindCatalog[kind]; 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 return nil
} }
func NewDialogWithData( func NewDialogWithData(
name DialogName, kind DialogKind, name DialogName,
kind DialogKind,
parent fyne.Window, parent fyne.Window,
confirm OnConfirmFn, confirm OnConfirmFn,
data binding.DataItem, data binding.DataItem,
@ -289,7 +456,30 @@ func NewDialogWithData(
} }
if kindCatalog, ok := dialogCatalog[name]; ok { if kindCatalog, ok := dialogCatalog[name]; ok {
if fn, ok := kindCatalog[kind]; 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 return nil

View File

@ -9,45 +9,85 @@ import (
"fyne.io/fyne/v2/container" "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 type Container string
const ( const (
// HScroll Container
//
// Available yaml options:
// - content: The content of the container
HScroll Container = "HScroll" HScroll Container = "HScroll"
// Scroll Container
//
// Available yaml options:
// - content: The content of the container
Scroll Container = "Scroll" Scroll Container = "Scroll"
// VScroll Container
//
// Available yaml options:
// - content: The content of the container
VScroll Container = "VScroll" 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 { switch cnt {
case HScroll: case HScroll:
c, err = cnt.buildHScrollContainer(e, s) container, err = cnt.buildHScrollContainer(e, s)
case Scroll: case Scroll:
c, err = cnt.buildScrollContainer(e, s) container, err = cnt.buildScrollContainer(e, s)
case VScroll: case VScroll:
c, err = cnt.buildVScrollContainer(e, s) container, err = cnt.buildVScrollContainer(e, s)
default: default:
err = errors.New("invalid container") err = errors.New("invalid container")
} }
if err != nil { if err != nil {
return return
} }
if e.Decorators != nil {
decorator = container
if e.Decorators != nil {
for _, dec := range e.Decorators { for _, dec := range e.Decorators {
switch dec { switch dec {
case "Border": case "Border":
c = uiwidget.NewWidgetBorder(c) decorator = uiwidget.NewWidgetBorder(decorator)
} }
} }
} }
if e.Hidden { if e.Hidden {
c.Hide() decorator.Hide()
} }
return 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 { if e.Content == nil {
err = errors.New("HScroll: no content defined") err = errors.New("HScroll: no content defined")
return return
@ -62,7 +102,10 @@ func (ctn Container) buildHScrollContainer(e *Element, s ScreenHandler) (c fyne.
return 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 { if e.Content == nil {
err = errors.New("Scroll: no content defined") err = errors.New("Scroll: no content defined")
return return
@ -77,7 +120,10 @@ func (ctn Container) buildScrollContainer(e *Element, s ScreenHandler) (c fyne.C
return 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 { if e.Content == nil {
err = errors.New("VScroll: no content defined") err = errors.New("VScroll: no content defined")
return return

View File

@ -7,11 +7,13 @@ import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
) )
// Size represents the yaml description of a ui size
type Size struct { type Size struct {
Width float32 `yaml:"width"` Width float32 `yaml:"width"`
Height float32 `yaml:"height"` Height float32 `yaml:"height"`
} }
// Element represents the yaml description of a ui element
type Element struct { type Element struct {
ID string `yaml:"id"` ID string `yaml:"id"`
@ -57,6 +59,12 @@ type Element struct {
Time *string `yaml:"time"` Time *string `yaml:"time"`
TimeFormat *string `yaml:"timeFormat"` TimeFormat *string `yaml:"timeFormat"`
// Entry Properties
MultiLine bool `yaml:"multiLine"`
Placeholder string `yaml:"placeholder"`
PlaceholderLocalized bool `yaml:"placeholderLocalized"`
Validator string `yaml:"validator"`
// List Properties // List Properties
ItemTemplate string `yaml:"itemTemplate"` ItemTemplate string `yaml:"itemTemplate"`
ItemRenderer string `yaml:"itemRenderer"` ItemRenderer string `yaml:"itemRenderer"`
@ -77,31 +85,37 @@ type Element struct {
OnSelected string `yaml:"onSelected"` OnSelected string `yaml:"onSelected"`
OnUnselected string `yaml:"onUnselected"` OnUnselected string `yaml:"onUnselected"`
OnDateSelected string `yaml:"onDateSelected"` 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) { func (e *Element) BuildUI(s ScreenHandler) (obj fyne.CanvasObject, err error) {
var baseObject fyne.CanvasObject
defer func() { defer func() {
if e.ID != "" && obj != nil { if e.ID != "" && baseObject != nil {
s.RegisterElement(e.ID, obj) s.RegisterElement(e.ID, baseObject, obj)
} }
}() }()
if e.Container != "" { 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) err = fmt.Errorf("failed to build container element: %w", err)
return return
} }
return return
} }
if e.Layout != "" { 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) err = fmt.Errorf("failed to build layout element: %w", err)
return return
} }
return return
} }
if e.Widget != "" { 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) err = fmt.Errorf("failed to build widget element: %w", err)
return return
} }
@ -115,4 +129,7 @@ func (e *Element) localize(s ScreenHandler) {
if e.Localized { if e.Localized {
e.Text = i18n.T(s.GetLocalizer(), e.Text) 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" "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 type Layout string
const ( const (
// 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" 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" 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" Grid Layout = "Grid"
// HBox Layout
//
// Available yaml options
// - children: The children of the HBox
HBox Layout = "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" MinSize Layout = "MinSize"
// Stack Layout
//
// Available yaml options
// - children: The children of the Stack
Stack Layout = "Stack" Stack Layout = "Stack"
// VBox Layout
//
// Available yaml options
// - children: The children of the VBox
VBox Layout = "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 { switch l {
case Border: case Border:
c, err = l.buildBorderLayout(e, s) layout, err = l.buildBorderLayout(e, s)
case Form: case Form:
c, err = l.buildFormLayout(e, s) layout, err = l.buildFormLayout(e, s)
case Grid: case Grid:
c, err = l.buildGridLayout(e, s) layout, err = l.buildGridLayout(e, s)
case HBox: case HBox:
c, err = l.buildHBoxLayout(e, s) layout, err = l.buildHBoxLayout(e, s)
case MinSize: case MinSize:
c, err = l.buildMinSizeLayout(e, s) layout, err = l.buildMinSizeLayout(e, s)
case Stack: case Stack:
c, err = l.buildStackLayout(e, s) layout, err = l.buildStackLayout(e, s)
case VBox: case VBox:
c, err = l.buildVBoxLayout(e, s) layout, err = l.buildVBoxLayout(e, s)
default: default:
err = errors.New("invalid layout") err = errors.New("invalid layout")
} }
@ -46,22 +113,26 @@ func (l Layout) Build(e *Element, s ScreenHandler) (c fyne.CanvasObject, err err
return return
} }
decorator = layout
if e.Decorators != nil { if e.Decorators != nil {
for _, dec := range e.Decorators { for _, dec := range e.Decorators {
switch dec { switch dec {
case "Border": case "Border":
c = uiwidget.NewWidgetBorder(c) decorator = uiwidget.NewWidgetBorder(decorator)
} }
} }
} }
if e.Hidden { if e.Hidden {
c.Hide() decorator.Hide()
} }
return 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 top, left, right, bottom fyne.CanvasObject
var center []fyne.CanvasObject var center []fyne.CanvasObject
@ -109,7 +180,10 @@ func (l Layout) buildBorderLayout(e *Element, s ScreenHandler) (c *fyne.Containe
return 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) children := make([]fyne.CanvasObject, 0, len(e.Children)*2)
for _, child := range e.Children { for _, child := range e.Children {
if child.Label == nil { if child.Label == nil {
@ -149,7 +223,10 @@ func (l Layout) buildFormLayout(e *Element, s ScreenHandler) (c *fyne.Container,
return 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)) children := make([]fyne.CanvasObject, 0, len(e.Children))
for _, child := range e.Children { for _, child := range e.Children {
var obj fyne.CanvasObject var obj fyne.CanvasObject
@ -179,7 +256,10 @@ func (l Layout) buildGridLayout(e *Element, s ScreenHandler) (c *fyne.Container,
return 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)) children := make([]fyne.CanvasObject, 0, len(e.Children))
for _, child := range e.Children { for _, child := range e.Children {
var obj fyne.CanvasObject var obj fyne.CanvasObject
@ -193,7 +273,10 @@ func (l Layout) buildHBoxLayout(e *Element, s ScreenHandler) (c *fyne.Container,
return 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)) children := make([]fyne.CanvasObject, 0, len(e.Children))
for _, child := range e.Children { for _, child := range e.Children {
var obj fyne.CanvasObject var obj fyne.CanvasObject
@ -216,7 +299,10 @@ func (l Layout) buildMinSizeLayout(e *Element, s ScreenHandler) (c *fyne.Contain
return 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)) children := make([]fyne.CanvasObject, 0, len(e.Children))
for _, child := range e.Children { for _, child := range e.Children {
var obj fyne.CanvasObject var obj fyne.CanvasObject
@ -230,7 +316,10 @@ func (l Layout) buildStackLayout(e *Element, s ScreenHandler) (c *fyne.Container
return 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)) children := make([]fyne.CanvasObject, 0, len(e.Children))
for _, child := range e.Children { for _, child := range e.Children {
var obj fyne.CanvasObject var obj fyne.CanvasObject

View File

@ -5,15 +5,19 @@ import (
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
) )
// ListItemTemplate is a template for a list item.
// It implements the fyne.CanvasObject interface and TemplateScreenHandler
type ListItemTemplate struct { type ListItemTemplate struct {
fyne.CanvasObject fyne.CanvasObject
*TemplateScreenHandler *TemplateScreenHandler
} }
// CreateRenderer implements the fyne.CanvasObject interface
func (i *ListItemTemplate) CreateRenderer() fyne.WidgetRenderer { func (i *ListItemTemplate) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(i.CanvasObject) return widget.NewSimpleRenderer(i.CanvasObject)
} }
// NewListItemTemplate creates a new ListItemTemplate
func NewListItemTemplate( func NewListItemTemplate(
obj fyne.CanvasObject, obj fyne.CanvasObject,
screenHandler *TemplateScreenHandler, screenHandler *TemplateScreenHandler,

View File

@ -16,22 +16,39 @@ import (
"gopkg.in/yaml.v2" "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 { type ScreenHandler interface {
RegisterElement(string, fyne.CanvasObject) RegisterElement(string, fyne.CanvasObject, fyne.CanvasObject)
GetLocalizer() *i18n.Localizer GetLocalizer() *i18n.Localizer
GetIcon(string) fyne.Resource GetIcon(string) fyne.Resource
GetBinding(string) binding.DataItem GetBinding(string) binding.DataItem
GetListItemTemplate(string) func() fyne.CanvasObject GetValidator(string) fyne.StringValidator
GetListDataItemRenderer(string) func(binding.DataItem, fyne.CanvasObject) GetListItemTemplate(string) ListItemTemplateFn
GetListItemRenderer(string) func(int, fyne.CanvasObject) GetListDataItemRenderer(string) ListDataItemRendererFn
GetListLength(string) func() int GetListItemRenderer(string) ListItemRendererFn
GetListLength(string) ListLengthFn
GetOnSelectedHandler(string) func(widget.ListItemID) GetOnListItemSelectedHandler(string) ListItemHandlerFn
GetOnUnselectedHandler(string) func(widget.ListItemID) GetOnListItemUnselectedHandler(string) ListItemHandlerFn
GetClickedHandler(string) func() GetOnClickedHandler(string) ClickHandlerFn
GetCheckChangedHandler(string) func(bool) GetOnCheckChangedHandler(string) CheckHandlerFn
GetDateSelectedHandler(string) func(time.Time) GetOnDateSelectedHandler(string) DateSelectedHandlerFn
GetOnValidationChangedHandler(string) ValidationChangedHandlerFn
} }
type Screen struct { type Screen struct {
@ -79,7 +96,7 @@ func NewTemplate(
handler *TemplateScreenHandler, handler *TemplateScreenHandler,
err error, err error,
) { ) {
handler = NewDummyScreenHandler(localizer) handler = NewTemplateScreenHandler(localizer)
scr, err = New(filesystem, name, handler) scr, err = New(filesystem, name, handler)
return return
} }
@ -94,21 +111,27 @@ func (s *Screen) Initialize() (obj fyne.CanvasObject, err error) {
type TemplateScreenHandler struct { type TemplateScreenHandler struct {
localizer *i18n.Localizer 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{ return &TemplateScreenHandler{
localizer: localizer, localizer: localizer,
elementsMap: make(map[string]fyne.CanvasObject), elementsMap: make(map[string]*UIElement),
} }
} }
func (d *TemplateScreenHandler) RegisterElement(name string, element fyne.CanvasObject) { func (d *TemplateScreenHandler) RegisterElement(
d.elementsMap[name] = element 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] elem, ok := d.elementsMap[name]
if !ok { if !ok {
return nil return nil
@ -132,38 +155,46 @@ func (*TemplateScreenHandler) GetBinding(string) binding.DataItem {
return nil 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 } 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) {} return func(binding.DataItem, fyne.CanvasObject) {}
} }
func (*TemplateScreenHandler) GetListItemRenderer(string) func(int, fyne.CanvasObject) { func (*TemplateScreenHandler) GetListItemRenderer(string) ListItemRendererFn {
return func(int, fyne.CanvasObject) {} return func(int, fyne.CanvasObject) {}
} }
func (*TemplateScreenHandler) GetListLength(string) func() int { func (*TemplateScreenHandler) GetListLength(string) ListLengthFn {
return func() int { return 0 } return func() int { return 0 }
} }
func (*TemplateScreenHandler) GetOnSelectedHandler(string) func(widget.ListItemID) { func (*TemplateScreenHandler) GetOnListItemSelectedHandler(string) ListItemHandlerFn {
return func(widget.ListItemID) {} return func(widget.ListItemID) {}
} }
func (*TemplateScreenHandler) GetOnUnselectedHandler(string) func(widget.ListItemID) { func (*TemplateScreenHandler) GetOnListItemUnselectedHandler(string) ListItemHandlerFn {
return func(widget.ListItemID) {} return func(widget.ListItemID) {}
} }
func (*TemplateScreenHandler) GetClickedHandler(string) func() { func (*TemplateScreenHandler) GetOnClickedHandler(string) ClickHandlerFn {
return func() {} return func() {}
} }
func (*TemplateScreenHandler) GetCheckChangedHandler(string) func(bool) { func (*TemplateScreenHandler) GetOnCheckChangedHandler(string) CheckHandlerFn {
return func(bool) {} return func(bool) {}
} }
func (*TemplateScreenHandler) GetDateSelectedHandler(string) func(time.Time) { func (*TemplateScreenHandler) GetOnDateSelectedHandler(string) DateSelectedHandlerFn {
return func(time.Time) {} 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" 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 type Widget string
const ( const (
// 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" 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" 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" 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" 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" 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" 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" 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" 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" 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" 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" List Widget = "List"
// Separator Widget
Separator Widget = "Separator" Separator Widget = "Separator"
// Spacer Widget
Spacer Widget = "Spacer" 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" 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) e.localize(s)
switch w { switch w {
case Button: case Button:
c, err = w.buildButtonWidget(e, s) widget, err = w.buildButtonWidget(e, s)
case Calendar: case Calendar:
c, err = w.buildCalendarWidget(e, s) widget, err = w.buildCalendarWidget(e, s)
case Check: 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: 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: case Label:
c, err = w.buildLabelWidget(e, s) widget, err = w.buildLabelWidget(e, s)
case List: case List:
c, err = w.buildListWidget(e, s) widget, err = w.buildListWidget(e, s)
case Separator: case Separator:
c, err = w.buildSeparatorWidget(e, s) widget, err = w.buildSeparatorWidget(e, s)
case Spacer: case Spacer:
c, err = w.buildSpacerWidget(e, s) widget, err = w.buildSpacerWidget(e, s)
case UpDownLabel: case UpDownLabel:
c, err = w.buildUpDownLabelWidget(e, s) widget, err = w.buildUpDownLabelWidget(e, s)
default: default:
err = errors.New("invalid widget") err = errors.New("invalid widget")
} }
@ -61,28 +230,32 @@ func (w Widget) Build(e *Element, s ScreenHandler) (c fyne.CanvasObject, err err
return return
} }
decorator = widget
if e.Decorators != nil { if e.Decorators != nil {
for _, dec := range e.Decorators { for _, dec := range e.Decorators {
switch dec { switch dec {
case "Border": case "Border":
c = uiwidget.NewWidgetBorder(c) decorator = uiwidget.NewWidgetBorder(decorator)
} }
} }
} }
if e.Hidden { if e.Hidden {
c.Hide() decorator.Hide()
} }
return 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 var btn *widget.Button
if e.Icon != "" { if e.Icon != "" {
btn = widget.NewButtonWithIcon( btn = widget.NewButtonWithIcon(
e.Text, s.GetIcon(e.Icon), s.GetClickedHandler(e.OnClicked)) e.Text, s.GetIcon(e.Icon), s.GetOnClickedHandler(e.OnClicked))
} else { } 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 { for opt, val := range e.Options {
@ -103,7 +276,10 @@ func (w Widget) buildButtonWidget(e *Element, s ScreenHandler) (c fyne.CanvasObj
return 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() t := time.Now()
if e.Time != nil { if e.Time != nil {
timeFormat := time.DateOnly 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 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 var chk *widget.Check
if e.Binding != "" { if e.Binding != "" {
chk = widget.NewCheckWithData( chk = widget.NewCheckWithData(
e.Text, s.GetBinding(e.Binding).(binding.Bool)) e.Text, s.GetBinding(e.Binding).(binding.Bool))
} else { } else {
chk = widget.NewCheck( chk = widget.NewCheck(
e.Text, s.GetCheckChangedHandler(e.OnChanged)) e.Text, s.GetOnCheckChangedHandler(e.OnChanged))
chk.SetChecked(e.Checked) chk.SetChecked(e.Checked)
} }
@ -138,7 +317,40 @@ func (w Widget) buildCheckWidget(e *Element, s ScreenHandler) (c fyne.CanvasObje
return 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 var rt *widget.RichText
level := 1 level := 1
@ -174,7 +386,18 @@ func (w Widget) buildHeaderLabelWidget(e *Element, s ScreenHandler) (c fyne.Canv
return 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 var lbl *widget.Label
if e.Binding != "" { if e.Binding != "" {
@ -201,7 +424,10 @@ func (w Widget) buildLabelWidget(e *Element, s ScreenHandler) (c fyne.CanvasObje
return 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 var lst *widget.List
if e.Binding != "" { if e.Binding != "" {
@ -217,22 +443,28 @@ func (w Widget) buildListWidget(e *Element, s ScreenHandler) (c fyne.CanvasObjec
} }
if e.OnSelected != "" { if e.OnSelected != "" {
lst.OnSelected = s.GetOnSelectedHandler(e.OnSelected) lst.OnSelected = s.GetOnListItemSelectedHandler(e.OnSelected)
} }
if e.OnUnselected != "" { if e.OnUnselected != "" {
lst.OnUnselected = s.GetOnUnselectedHandler(e.OnUnselected) lst.OnUnselected = s.GetOnListItemUnselectedHandler(e.OnUnselected)
} }
c = lst c = lst
return 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() sep := widget.NewSeparator()
c = sep c = sep
return 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{ spc := &layout.Spacer{
FixHorizontal: e.FixHorizontal, FixHorizontal: e.FixHorizontal,
FixVertical: e.FixVertical, FixVertical: e.FixVertical,
@ -241,11 +473,14 @@ func (w Widget) buildSpacerWidget(e *Element, _ ScreenHandler) (c fyne.CanvasObj
return 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( btn := uiwidget.NewUpDownLabelWithData(
s.GetBinding(e.Binding).(binding.String), s.GetBinding(e.Binding).(binding.String),
s.GetClickedHandler(e.OnUpClicked), s.GetOnClickedHandler(e.OnUpClicked),
s.GetClickedHandler(e.OnDownClicked)) s.GetOnClickedHandler(e.OnDownClicked))
c = btn c = btn
return 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" "fyne.io/fyne/v2/container"
) )
// TODO: Implement screen reading from file!
type TabbedView struct { type TabbedView struct {
*BaseView *BaseView

View File

@ -2,14 +2,26 @@ package uilayout
import "fyne.io/fyne/v2" 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 { type MinSize struct {
minSize fyne.Size 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 { func NewMinSizeLayout(minSize fyne.Size) *MinSize {
return &MinSize{minSize: 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 { func (l *MinSize) MinSize(objects []fyne.CanvasObject) fyne.Size {
size := l.minSize size := l.minSize
for _, o := range objects { for _, o := range objects {
@ -24,6 +36,7 @@ func (l *MinSize) MinSize(objects []fyne.CanvasObject) fyne.Size {
return size return size
} }
// Layout implements the Layout interface.
func (l *MinSize) Layout(objects []fyne.CanvasObject, containerSize fyne.Size) { func (l *MinSize) Layout(objects []fyne.CanvasObject, containerSize fyne.Size) {
pos := fyne.NewPos(0, 0) pos := fyne.NewPos(0, 0)
for _, o := range objects { for _, o := range objects {

View File

@ -11,6 +11,7 @@ import (
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
) )
// LabelOpts defines the options for a label.
type LabelOpts interface { type LabelOpts interface {
ApplyTo(*widget.Label) ApplyTo(*widget.Label)
} }
@ -19,10 +20,12 @@ type allignmentOpt struct {
Alignment fyne.TextAlign Alignment fyne.TextAlign
} }
// AlignmentOpt returns a LabelOpts with the specified alignment.
func AlignmentOpt(alignment fyne.TextAlign) LabelOpts { func AlignmentOpt(alignment fyne.TextAlign) LabelOpts {
return &allignmentOpt{alignment} return &allignmentOpt{alignment}
} }
// ApplyTo sets the alignment of a label to the specified alignment.
func (a *allignmentOpt) ApplyTo(lbl *widget.Label) { func (a *allignmentOpt) ApplyTo(lbl *widget.Label) {
lbl.Alignment = a.Alignment lbl.Alignment = a.Alignment
} }
@ -31,10 +34,12 @@ type wrappingOpt struct {
Wrapping fyne.TextWrap Wrapping fyne.TextWrap
} }
// WrappingOpt creates a LabelOpts with the specified text wrapping option.
func WrappingOpt(wrapping fyne.TextWrap) LabelOpts { func WrappingOpt(wrapping fyne.TextWrap) LabelOpts {
return &wrappingOpt{wrapping} return &wrappingOpt{wrapping}
} }
// ApplyTo applies the wrapping option to the label.
func (w *wrappingOpt) ApplyTo(lbl *widget.Label) { func (w *wrappingOpt) ApplyTo(lbl *widget.Label) {
lbl.Wrapping = w.Wrapping lbl.Wrapping = w.Wrapping
} }
@ -43,10 +48,12 @@ type textStyleOpt struct {
Style fyne.TextStyle Style fyne.TextStyle
} }
// TextStyleOpt returns the label options for the given text style.
func TextStyleOpt(style fyne.TextStyle) LabelOpts { func TextStyleOpt(style fyne.TextStyle) LabelOpts {
return &textStyleOpt{style} return &textStyleOpt{style}
} }
// ApplyTo applies the text style option to the label.
func (t *textStyleOpt) ApplyTo(lbl *widget.Label) { func (t *textStyleOpt) ApplyTo(lbl *widget.Label) {
lbl.TextStyle = t.Style lbl.TextStyle = t.Style
} }
@ -55,10 +62,12 @@ type truncationOpt struct {
Truncation fyne.TextTruncation Truncation fyne.TextTruncation
} }
// TruncationOpt returns the label options for the given text truncation option.
func TruncationOpt(truncation fyne.TextTruncation) LabelOpts { func TruncationOpt(truncation fyne.TextTruncation) LabelOpts {
return &truncationOpt{truncation} return &truncationOpt{truncation}
} }
// ApplyTo applies the text truncation option to the label.
func (t *truncationOpt) ApplyTo(lbl *widget.Label) { func (t *truncationOpt) ApplyTo(lbl *widget.Label) {
lbl.Truncation = t.Truncation lbl.Truncation = t.Truncation
} }
@ -67,14 +76,23 @@ type importanceOpt struct {
Importance widget.Importance Importance widget.Importance
} }
// ImportanceOpt returns the label options for the given text importance option.
func ImportanceOpt(importance widget.Importance) LabelOpts { func ImportanceOpt(importance widget.Importance) LabelOpts {
return &importanceOpt{importance} return &importanceOpt{importance}
} }
// ApplyTo applies the text importance option to the label.
func (i *importanceOpt) ApplyTo(lbl *widget.Label) { func (i *importanceOpt) ApplyTo(lbl *widget.Label) {
lbl.Importance = i.Importance 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 { func NewLabel(text string, opts ...LabelOpts) *widget.Label {
lbl := widget.NewLabel(text) lbl := widget.NewLabel(text)
for _, opt := range opts { for _, opt := range opts {
@ -83,6 +101,13 @@ func NewLabel(text string, opts ...LabelOpts) *widget.Label {
return lbl 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 { func NewLabelWithData(data binding.String, opts ...LabelOpts) *widget.Label {
lbl := widget.NewLabelWithData(data) lbl := widget.NewLabelWithData(data)
for _, opt := range opts { for _, opt := range opts {
@ -91,6 +116,14 @@ func NewLabelWithData(data binding.String, opts ...LabelOpts) *widget.Label {
return lbl 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( func NewUpDownLabelWithData(
data binding.String, data binding.String,
upHandler func(), upHandler func(),
@ -115,11 +148,25 @@ func NewUpDownLabelWithData(
return c1 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 { func NewH(level int, text string) *widget.RichText {
return widget.NewRichTextFromMarkdown( return widget.NewRichTextFromMarkdown(
fmt.Sprintf("%s %s", strings.Repeat("#", level), text)) 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 { func NewHWithData(level int, data binding.String) *widget.RichText {
txt, _ := data.Get() txt, _ := data.Get()
w := NewH(level, txt) w := NewH(level, txt)
@ -130,50 +177,62 @@ func NewHWithData(level int, data binding.String) *widget.RichText {
return w return w
} }
// NewH1 creates a new RichText widget with header level 1.
func NewH1(text string) *widget.RichText { func NewH1(text string) *widget.RichText {
return NewH(1, text) return NewH(1, text)
} }
// NewH1WithData creates a new RichText widget with header level 1.
func NewH1WithData(data binding.String) *widget.RichText { func NewH1WithData(data binding.String) *widget.RichText {
return NewHWithData(1, data) return NewHWithData(1, data)
} }
// NewH2 creates a new RichText widget with header level 2.
func NewH2(text string) *widget.RichText { func NewH2(text string) *widget.RichText {
return NewH(2, text) return NewH(2, text)
} }
// NewH2WithData creates a new RichText widget with header level 2.
func NewH2WithData(data binding.String) *widget.RichText { func NewH2WithData(data binding.String) *widget.RichText {
return NewHWithData(2, data) return NewHWithData(2, data)
} }
// NewH3 creates a new RichText widget with header level 3.
func NewH3(text string) *widget.RichText { func NewH3(text string) *widget.RichText {
return NewH(3, text) return NewH(3, text)
} }
// NewH3WithData creates a new RichText widget with header level 3.
func NewH3WithData(data binding.String) *widget.RichText { func NewH3WithData(data binding.String) *widget.RichText {
return NewHWithData(3, data) return NewHWithData(3, data)
} }
// NewH4 creates a new RichText widget with header level 4.
func NewH4(text string) *widget.RichText { func NewH4(text string) *widget.RichText {
return NewH(4, text) return NewH(4, text)
} }
// NewH4WithData creates a new RichText widget with header level 4.
func NewH4WithData(data binding.String) *widget.RichText { func NewH4WithData(data binding.String) *widget.RichText {
return NewHWithData(4, data) return NewHWithData(4, data)
} }
// NewH5 creates a new RichText widget with header level 5.
func NewH5(text string) *widget.RichText { func NewH5(text string) *widget.RichText {
return NewH(5, text) return NewH(5, text)
} }
// NewH5WithData creates a new RichText widget with header level 5.
func NewH5WithData(data binding.String) *widget.RichText { func NewH5WithData(data binding.String) *widget.RichText {
return NewHWithData(5, data) return NewHWithData(5, data)
} }
// NewH6 creates a new RichText widget with header level 6.
func NewH6(text string) *widget.RichText { func NewH6(text string) *widget.RichText {
return NewH(6, text) return NewH(6, text)
} }
// NewH6WithData creates a new RichText widget with header level 6.
func NewH6WithData(data binding.String) *widget.RichText { func NewH6WithData(data binding.String) *widget.RichText {
return NewHWithData(6, data) return NewHWithData(6, data)
} }

View File

@ -9,6 +9,9 @@ import (
"fyne.io/fyne/v2/theme" "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 { func NewWidgetBorder(widget fyne.CanvasObject) fyne.CanvasObject {
b := canvas.NewRectangle(color.Transparent) b := canvas.NewRectangle(color.Transparent)
b.StrokeColor = theme.InputBorderColor() b.StrokeColor = theme.InputBorderColor()