package ui import ( "embed" "errors" "fmt" "io/fs" "log" "strings" "gitea.hevanto-it.com/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" ) type screenType int const ( stScreen screenType = iota stDialog ) type ScreenDefinition interface { fyne.Widget Initialize() (fyne.CanvasObject, error) RegisterResizeCallback(func(fyne.Size)) RegisterMoveCallback(func(fyne.Position)) AsContainer() *fyne.Container } // 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 ScreenDefinition localizer *i18n.Localizer viewModel BaseViewModel assetLoader func(string) (fs.File, error) // 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 onOptionSelectedHandlers map[string]screen.OptionSelectedHandlerFn 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, viewModel BaseViewModel, localizer *i18n.Localizer, assetLoader func(string) (fs.File, error), ) (h *ScreenHandler, err error) { return newScreenHandler( stScreen, filesystem, screenName, viewModel, localizer, assetLoader) } func NewDialogScreenHandler( filesystem embed.FS, screenName string, viewModel BaseViewModel, localizer *i18n.Localizer, assetLoader func(string) (fs.File, error), ) (h *ScreenHandler, err error) { return newScreenHandler( stDialog, filesystem, screenName, viewModel, localizer, assetLoader) } func newScreenHandler( screenType screenType, fileSystem embed.FS, screenName string, viewModel BaseViewModel, localizer *i18n.Localizer, assetLoader func(string) (fs.File, error), ) (h *ScreenHandler, err error) { h = new(ScreenHandler) h.viewModel = viewModel h.localizer = localizer switch screenType { case stScreen: h.screenDefinition, err = screen.New(fileSystem, screenName, h) case stDialog: h.screenDefinition, err = screen.NewDialogScreen(fileSystem, screenName, h) default: err = errors.New("Unsupported screen type") } if err != nil { err = fmt.Errorf("failure loading screen: %w", err) return } h.assetLoader = assetLoader 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.onOptionSelectedHandlers = make(map[string]screen.OptionSelectedHandlerFn) h.onValidationChangedHandlers = make(map[string]screen.ValidationChangedHandlerFn) return } // ScreenDefinition returns the screen definition for the ScreenHandler. func (h *ScreenHandler) ScreenDefinition() ScreenDefinition { 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} } func (h *ScreenHandler) LoadAsset(name string) (fh fs.File, err error) { fh, err = h.assetLoader(name) return } // 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 } // RegisterOnOptionSelectedHandler registers a OnOptionSelectedHandler with the ScreenHandler. // // Use this function to register the handler functions used in the screen // definition. func (h *ScreenHandler) RegisterOnOptionSelectedHandler( name string, fn screen.OptionSelectedHandlerFn, ) { h.onOptionSelectedHandlers[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.viewModel.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.viewModel.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 { elem, ok := h.exportedElements[id] if !ok { log.Printf("element %s not found", id) return nil } return elem } func (h *ScreenHandler) ReplaceElementObject( containerId string, id string, newDecorator fyne.CanvasObject, newObject fyne.CanvasObject, ) { containerElement := h.GetElement(containerId) if containerElement == nil { return } containerObj := containerElement.Object.(*fyne.Container) element := h.GetElement(id) if element == nil { return } decorator := element.Decorator allObjs := containerObj.Objects replaced := false for i, obj := range allObjs { if obj == decorator { allObjs[i] = newDecorator replaced = true break } } if replaced { h.exportedElements[id].Decorator = newDecorator h.exportedElements[id].Object = newObject } containerObj.Refresh() } // 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)) } log.Printf("icon %s not found", 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 { log.Printf("list template %s not found", name) 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 { log.Printf("list renderer %s not found", name) 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 { log.Printf("list renderer %s not found", name) 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 { log.Printf("list length %s not found", name) 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 { log.Printf("list item selected handler %s not found", name) 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 { log.Printf("list item unselected handler %s not found", name) 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 { log.Printf("clicked handler %s not found", name) 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 { log.Printf("check changed handler %s not found", name) 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 { log.Printf("date selected handler %s not found", name) return nil } return fn } func (h *ScreenHandler) GetOnOptionSelectedHandler(name string) screen.OptionSelectedHandlerFn { fn, ok := h.onOptionSelectedHandlers[name] if !ok { log.Printf("option selected handler %s not found", name) 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 { log.Printf("validation changed handler %s not found", name) return nil } return fn } func (h *ScreenHandler) RegisterResizeCallback(cb ResizeCallbackFn) { h.screenDefinition.RegisterResizeCallback(cb) } func (h *ScreenHandler) RegisterMoveCallback(cb MoveCallbackFn) { h.screenDefinition.RegisterMoveCallback(cb) }