package screen import ( "errors" "fmt" "bitbucket.org/hevanto/ui/uilayout" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/layout" ) // The constants for the Layouts // // Available yaml options for all layouts: // - id: The ID for exporting the layout to code // - decorators: The decorators to wrap the layout into // - hidden: Wether the layout is hidden or not type Layout string const ( // Border Layout // // Available yaml options // - top: The top pane (contains 1 widget) // - bottom: The bottom pane (contains 1 widget) // - left: The left pane (contains 1 widget) // - right: The right pane (contains 1 widget) // - center: The center pane (contains multiple widgets) Border Layout = "Border" // Form Layout // // Available yaml options // - children: The children of the form // - label: The label of the form element // - field: The field of the form element Form Layout = "Form" // Grid Layout // // Available yaml options // - columns: The number of columns (GridWithColumns) // - rows: The number of rows (GridWithRows) // - rowColumns: The number of rows and columns (AdaptiveGrid) // - elementSize: The size of the elements (GridWrap) // - children: The children of the grid Grid Layout = "Grid" // HBox Layout // // Available yaml options // - children: The children of the HBox HBox Layout = "HBox" // MinSize Layout // // Available yaml options // - minSize: The minimum size of the layout // - children: The children of the MinSize MinSize Layout = "MinSize" // Stack Layout // // Available yaml options // - children: The children of the Stack Stack Layout = "Stack" // VBox Layout // // Available yaml options // - children: The children of the VBox VBox Layout = "VBox" ) // 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, ) ( lay fyne.CanvasObject, decorator fyne.CanvasObject, err error, ) { switch l { case Border: lay, err = l.buildBorderLayout(e, s) case Form: lay, err = l.buildFormLayout(e, s) case Grid: lay, err = l.buildGridLayout(e, s) case HBox: lay, err = l.buildHBoxLayout(e, s) case MinSize: lay, err = l.buildMinSizeLayout(e, s) case Stack: lay, err = l.buildStackLayout(e, s) case VBox: lay, err = l.buildVBoxLayout(e, s) default: err = errors.New("invalid layout") } if err != nil { return } decorator = applyDecorators(e, lay) if e.Hidden { decorator.Hide() } return } func (l Layout) buildBorderLayout( e *Element, s ScreenHandler, ) (c *fyne.Container, err error) { var top, left, right, bottom fyne.CanvasObject var center []fyne.CanvasObject if e.Top != nil { if top, err = e.Top.BuildUI(s); err != nil { err = fmt.Errorf("BorderLayout: failed to create top element: %w", err) return } } if e.Left != nil { if left, err = e.Left.BuildUI(s); err != nil { err = fmt.Errorf("BorderLayout: failed to create left element: %w", err) return } } if e.Center != nil { center = make([]fyne.CanvasObject, 0, len(e.Center)) for _, elem := range e.Center { var ce fyne.CanvasObject if ce, err = elem.BuildUI(s); err != nil { err = fmt.Errorf("BorderLayout: failed to create center element: %w", err) return } center = append(center, ce) } } if e.Right != nil { if right, err = e.Right.BuildUI(s); err != nil { err = fmt.Errorf("BorderLayout: failed to create right element: %w", err) return } } if e.Bottom != nil { if bottom, err = e.Bottom.BuildUI(s); err != nil { err = fmt.Errorf("BorderLayout: failed to create bottom element: %w", err) return } } c = container.NewBorder(top, bottom, left, right, center...) return } func (l Layout) buildFormLayout( e *Element, s ScreenHandler, ) (c *fyne.Container, err error) { children := make([]fyne.CanvasObject, 0, len(e.Children)*2) for _, child := range e.Children { if child.Label == nil { child.Label = &Element{ Text: "", } } if child.Field == nil { child.Field = &Element{ Widget: Label, Text: "", } } child.Label.Widget = Label if child.Label.Options == nil { child.Label.Options = make(map[string]any) } if child.Label.Options["TextStyle"] == nil { child.Label.Options["TextStyle"] = make(map[string]any) } child.Label.Options["TextStyle"].(map[string]any)["Bold"] = true var label, field fyne.CanvasObject if label, err = child.Label.BuildUI(s); err != nil { err = fmt.Errorf("FormLayout: failed to create label element: %w", err) return } if field, err = child.Field.BuildUI(s); err != nil { err = fmt.Errorf("FormLayout: failed to create field element: %w", err) return } children = append(children, label, field) } c = container.New(layout.NewFormLayout(), children...) return } func (l Layout) buildGridLayout( e *Element, s ScreenHandler, ) (c *fyne.Container, err error) { children := make([]fyne.CanvasObject, 0, len(e.Children)) for _, child := range e.Children { var obj fyne.CanvasObject if obj, err = child.BuildUI(s); err != nil { err = fmt.Errorf("GridLayout failed to create child element %w", err) return } children = append(children, obj) } if e.Columns != nil { c = container.NewGridWithColumns(*e.Columns, children...) return } if e.Rows != nil { c = container.NewGridWithRows(*e.Rows, children...) return } if e.RowColumns != nil { c = container.NewAdaptiveGrid(*e.RowColumns, children...) return } if e.ElementSize != nil { c = container.NewGridWrap(fyne.NewSize(e.ElementSize.Width, e.ElementSize.Height), children...) return } err = errors.New("GridLayout failed due to incomplete grid type definition") return } func (l Layout) buildHBoxLayout( e *Element, s ScreenHandler, ) (c *fyne.Container, err error) { children := make([]fyne.CanvasObject, 0, len(e.Children)) for _, child := range e.Children { var obj fyne.CanvasObject if obj, err = child.BuildUI(s); err != nil { err = fmt.Errorf("HBoxLayout failed to create child element: %w", err) return } children = append(children, obj) } c = container.New(layout.NewHBoxLayout(), children...) return } func (l Layout) buildMinSizeLayout( e *Element, s ScreenHandler, ) (c *fyne.Container, err error) { children := make([]fyne.CanvasObject, 0, len(e.Children)) for _, child := range e.Children { var obj fyne.CanvasObject if obj, err = child.BuildUI(s); err != nil { err = fmt.Errorf("MinSizeLayout failed to create child element: %w", err) return } children = append(children, obj) } if e.MinSize == nil { e.MinSize = &Size{ Width: 0, Height: 0, } } c = container.New( uilayout.NewMinSizeLayout( fyne.NewSize(e.MinSize.Width, e.MinSize.Height)), children...) return } func (l Layout) buildStackLayout( e *Element, s ScreenHandler, ) (c *fyne.Container, err error) { children := make([]fyne.CanvasObject, 0, len(e.Children)) for _, child := range e.Children { var obj fyne.CanvasObject if obj, err = child.BuildUI(s); err != nil { err = fmt.Errorf("StackLayout failed to create child element: %w", err) return } children = append(children, obj) } c = container.New(layout.NewStackLayout(), children...) return } func (l Layout) buildVBoxLayout( e *Element, s ScreenHandler, ) (c *fyne.Container, err error) { children := make([]fyne.CanvasObject, 0, len(e.Children)) for _, child := range e.Children { var obj fyne.CanvasObject if obj, err = child.BuildUI(s); err != nil { err = fmt.Errorf("VBoxLayout failed to create child element: %w", err) return } children = append(children, obj) } c = container.New(layout.NewVBoxLayout(), children...) return }