package ui import ( "embed" "fmt" "io/fs" "fyne.io/fyne/v2" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" "gitea.hevanto-it.com/hevanto/ui/screen" "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, *embed.FS, string, *i18n.Localizer) Dialog const ( DlgGeneric DialogKind = "Generic" ) type Dialog interface { View Window() fyne.Window View() View SetInitialFocusItem(focusItem fyne.Focusable) Show(parent ...fyne.Window) OnClose(bool) DialogName() DialogName DialogKind() DialogKind OnConfirmFn() OnConfirmFn } type BaseDialog struct { id string concreteDialog Dialog dlgName DialogName dlgKind DialogKind dlg dialog.Dialog window fyne.Window onConfirm OnConfirmFn CanvasObj fyne.CanvasObject Data binding.DataItem focusItem fyne.Focusable ViewModel DialogViewModel *ScreenHandler } func NewBaseDialog( concreteDialog Dialog, name DialogName, kind DialogKind, viewModel DialogViewModel, parent fyne.Window, confirm OnConfirmFn, ) *BaseDialog { return &BaseDialog{ id: uuid.NewString(), concreteDialog: concreteDialog, dlgName: name, dlgKind: kind, window: parent, onConfirm: confirm, ViewModel: viewModel, } } func NewBaseDialogWithScreen( concreteDialog Dialog, name DialogName, kind DialogKind, viewModel DialogViewModel, parent fyne.Window, confirm OnConfirmFn, filesystem embed.FS, screenName string, localizer *i18n.Localizer, assetLoader func(string) (fs.File, error), ) *BaseDialog { d := NewBaseDialog( concreteDialog, name, kind, viewModel, parent, confirm) var err error d.ScreenHandler, err = NewDialogScreenHandler( filesystem, screenName, viewModel, localizer, assetLoader) if err != nil { LogWindowError(d, LoadScreen, err) return nil } return d } func NewBaseDialogWithData( concreteDialog Dialog, name DialogName, kind DialogKind, viewModel DialogViewModel, parent fyne.Window, confirm OnConfirmFn, data binding.DataItem, ) *BaseDialog { return &BaseDialog{ id: uuid.NewString(), concreteDialog: concreteDialog, dlgName: name, dlgKind: kind, window: parent, onConfirm: confirm, Data: data, ViewModel: viewModel, } } func NewBaseDialogWithDataAndScreen( concreteDialog Dialog, name DialogName, kind DialogKind, viewModel DialogViewModel, parent fyne.Window, confirm OnConfirmFn, data binding.DataItem, filesystem embed.FS, screenName string, localizer *i18n.Localizer, assetLoader func(string) (fs.File, error), ) *BaseDialog { d := NewBaseDialogWithData( concreteDialog, name, kind, viewModel, parent, confirm, data) var err error d.ScreenHandler, err = NewDialogScreenHandler( filesystem, screenName, viewModel, localizer, assetLoader) if err != nil { LogWindowError(d, LoadScreen, err) return nil } return d } func (d BaseDialog) Window() fyne.Window { return d.window } func (d *BaseDialog) View() View { return d } func (d BaseDialog) Equals(other View) bool { return d.id == other.ID() } func (d *BaseDialog) SetInitialFocusItem(focusItem fyne.Focusable) { d.focusItem = focusItem } func (d BaseDialog) DialogName() DialogName { return d.dlgName } func (d BaseDialog) DialogKind() DialogKind { return d.dlgKind } func (d BaseDialog) OnConfirmFn() OnConfirmFn { return d.onConfirm } func (d *BaseDialog) Show(parent ...fyne.Window) { d.concreteDialog.OnShow() d.dlg = dialog.NewCustomConfirm( d.concreteDialog.Title(), "OK", "Cancel", d.concreteDialog.Initialize(), d.concreteDialog.OnClose, d.window, ) d.dlg.Show() d.dlg.Resize(fyne.NewSize(640, 480)) if d.focusItem != nil { window := d.window if parent != nil { window = parent[0] } window.Canvas().Focus(d.focusItem) } } func (d BaseDialog) ID() string { return d.id } func (d *BaseDialog) SetID(id string) { d.id = id } func (d BaseDialog) Title() string { return "" } func (d *BaseDialog) Initialize() fyne.CanvasObject { if err := d.ViewModel.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.(*screen.DialogScreen).AsContainer() } return d.CanvasObj } func (d *BaseDialog) Refresh() { if d.CanvasObj != nil { d.CanvasObj.Refresh() } } func (d *BaseDialog) OnShow() { //LogWindowEvent(d, OnShow) } func (d *BaseDialog) OnHide() { //LogWindowEvent(d, OnHide) } func (d *BaseDialog) OnClose(confirmed bool) { LogWindowEventMessage(d, OnClose, fmt.Sprintf("confirmed: %v", confirmed)) d.OnHide() } func (d *BaseDialog) GetCanvasObject() fyne.CanvasObject { return d.CanvasObj } func (d BaseDialog) PreventClose() bool { return false } type FormDialogItemsFn func() []*widget.FormItem type FormDialog struct { *BaseDialog confirmLabel string dismissLabel string itemsFn FormDialogItemsFn items []*widget.FormItem } func NewFormDialog( concreteDialog Dialog, name DialogName, kind DialogKind, viewModel DialogViewModel, parent fyne.Window, confirm OnConfirmFn, itemsFn FormDialogItemsFn, ) *FormDialog { return &FormDialog{ BaseDialog: NewBaseDialog( concreteDialog, name, kind, viewModel, parent, confirm), itemsFn: itemsFn, confirmLabel: "OK", dismissLabel: "Cancel", } } func NewFormDialogWithScreen( concreteDialog Dialog, name DialogName, kind DialogKind, viewModel DialogViewModel, parent fyne.Window, confirm OnConfirmFn, filesystem embed.FS, screenName string, localizer *i18n.Localizer, assetLoader func(string) (fs.File, error), ) *FormDialog { return &FormDialog{ BaseDialog: NewBaseDialogWithScreen( concreteDialog, name, kind, viewModel, parent, confirm, filesystem, screenName, localizer, assetLoader), confirmLabel: "OK", dismissLabel: "Cancel", } } func NewformDialogWithData( concreteDialog Dialog, name DialogName, kind DialogKind, viewModel DialogViewModel, parent fyne.Window, confirm OnConfirmFn, itemsFn FormDialogItemsFn, data binding.DataItem, ) *FormDialog { return &FormDialog{ BaseDialog: NewBaseDialogWithData( concreteDialog, name, kind, viewModel, parent, confirm, data), itemsFn: itemsFn, confirmLabel: "OK", dismissLabel: "Cancel", } } func NewFormDialogWithDataAndScreen( concreteDialog Dialog, name DialogName, kind DialogKind, viewModel DialogViewModel, parent fyne.Window, confirm OnConfirmFn, data binding.DataItem, filesystem embed.FS, screenName string, localizer *i18n.Localizer, assetLoader func(string) (fs.File, error), ) *FormDialog { return &FormDialog{ BaseDialog: NewBaseDialogWithDataAndScreen( concreteDialog, name, kind, viewModel, parent, confirm, data, filesystem, screenName, localizer, assetLoader), confirmLabel: "OK", dismissLabel: "Cancel", } } func (d *FormDialog) SetButtonLabels( confirm string, dismiss string, ) { d.confirmLabel = confirm 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() frm := dialog.NewForm( d.concreteDialog.Title(), d.confirmLabel, d.dismissLabel, d.items, d.concreteDialog.OnClose, d.window, ) d.dlg = frm d.dlg.Show() d.dlg.Resize(fyne.NewSize(640, 480)) if d.focusItem != nil { window := d.window if parent != nil { window = parent[0] } window.Canvas().Focus(d.focusItem) } } func (d *FormDialog) Refresh() { for _, itm := range d.items { itm.Widget.Refresh() } } var dialogCatalog map[DialogName]map[DialogKind]NewDialogFn func RegisterDialog(name DialogName, kind DialogKind, fn NewDialogFn) { if dialogCatalog == nil { dialogCatalog = make(map[DialogName]map[DialogKind]NewDialogFn) } if _, ok := dialogCatalog[name]; !ok { dialogCatalog[name] = make(map[DialogKind]NewDialogFn) } dialogCatalog[name][kind] = fn } func NewDialog( name DialogName, kind DialogKind, parent fyne.Window, confirm OnConfirmFn, ) 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, 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, parent fyne.Window, confirm OnConfirmFn, data binding.DataItem, ) 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, 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 } func ShowConfirmDialog( title string, message string, lblOk string, lblCancel string, callback func(bool), parent fyne.Window, ) { label := widget.NewLabel(message) label.Wrapping = fyne.TextWrapBreak dlg := dialog.NewCustomConfirm( title, lblOk, lblCancel, label, callback, parent) dlg.Resize(fyne.NewSize(480, 100)) dlg.Show() }