From cbbf08bd3b182ec734c897f90b8651446bd958b3 Mon Sep 17 00:00:00 2001 From: david-swift Date: Tue, 26 Sep 2023 15:30:07 +0200 Subject: [PATCH] Add widgets and demo application --- .swiftlint.yml | 8 - Documentation/Reference/README.md | 7 + .../Reference/classes/ViewStorage.md | 2 +- Documentation/Reference/extensions/Array.md | 5 + Documentation/Reference/extensions/String.md | 4 + Documentation/Reference/extensions/View.md | 46 ++++++ Documentation/Reference/structs/HStack.md | 26 ++++ Documentation/Reference/structs/List.md | 46 ++++++ .../Reference/structs/NavigationSplitView.md | 40 +++++ Documentation/Reference/structs/ScrollView.md | 26 ++++ .../Reference/structs/StateWrapper.md | 37 +++++ Documentation/Reference/structs/StatusPage.md | 42 ++++++ .../Reference/structs/ToolbarView.md | 37 +++++ Documentation/Reference/structs/VStack.md | 11 -- Icons/Demo.png | Bin 0 -> 22184 bytes Icons/Screenshot.png | Bin 7459 -> 7332 bytes Package.swift | 2 +- README.md | 137 +++++++++++------- SUMMARY.md | 9 +- Sources/Adwaita/Model/Extensions/Array.swift | 5 +- Sources/Adwaita/Model/Extensions/String.swift | 2 + .../Adwaita/Model/User Interface/View.swift | 4 +- .../Model/User Interface/ViewStorage.swift | 2 +- Sources/Adwaita/View/HStack.swift | 41 ++++++ Sources/Adwaita/View/List.swift | 76 ++++++++++ .../View/Modifiers/InspectorWrapper.swift | 35 +++++ .../Adwaita/View/Modifiers/ToolbarView.swift | 90 ++++++++++++ .../Adwaita/View/NavigationSplitView.swift | 62 ++++++++ Sources/Adwaita/View/ScrollView.swift | 37 +++++ Sources/Adwaita/View/StateWrapper.swift | 53 +++++++ Sources/Adwaita/View/StatusPage.swift | 56 +++++++ Sources/Adwaita/View/Text.swift | 2 +- Sources/Adwaita/View/VStack.swift | 20 +-- Tests/CounterDemo.swift | 37 +++++ Tests/Demo.swift | 68 +++++++++ Tests/Page.swift | 40 +++++ Tests/WelcomeDemo.swift | 29 ++++ Tests/WindowsDemo.swift | 34 +++++ Tests/main.swift | 72 --------- user-manual/Advanced/CreatingWidgets.md | 64 ++++++++ 40 files changed, 1142 insertions(+), 172 deletions(-) create mode 100644 Documentation/Reference/structs/HStack.md create mode 100644 Documentation/Reference/structs/List.md create mode 100644 Documentation/Reference/structs/NavigationSplitView.md create mode 100644 Documentation/Reference/structs/ScrollView.md create mode 100644 Documentation/Reference/structs/StateWrapper.md create mode 100644 Documentation/Reference/structs/StatusPage.md create mode 100644 Documentation/Reference/structs/ToolbarView.md create mode 100644 Icons/Demo.png create mode 100644 Sources/Adwaita/View/HStack.swift create mode 100644 Sources/Adwaita/View/List.swift create mode 100644 Sources/Adwaita/View/Modifiers/ToolbarView.swift create mode 100644 Sources/Adwaita/View/NavigationSplitView.swift create mode 100644 Sources/Adwaita/View/ScrollView.swift create mode 100644 Sources/Adwaita/View/StateWrapper.swift create mode 100644 Sources/Adwaita/View/StatusPage.swift create mode 100644 Tests/CounterDemo.swift create mode 100644 Tests/Demo.swift create mode 100644 Tests/Page.swift create mode 100644 Tests/WelcomeDemo.swift create mode 100644 Tests/WindowsDemo.swift delete mode 100644 Tests/main.swift create mode 100644 user-manual/Advanced/CreatingWidgets.md diff --git a/.swiftlint.yml b/.swiftlint.yml index bd7da2d..12e02e3 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -118,14 +118,6 @@ custom_rules: message: 'Spaces should be used instead of tabs.' severity: warning - string_literals: - name: 'String Literals' - regex: '(".*")|("""(.|\n)*""")' - message: 'String literals should not be used. Disable this rule in String and LocalizedStringResource extensions.' - match_kinds: - - string - severity: warning - # Thanks to the creator of the SwiftLint rule # "empty_first_line" # https://github.com/coteditor/CotEditor/blob/main/.swiftlint.yml diff --git a/Documentation/Reference/README.md b/Documentation/Reference/README.md index eb9fb5f..ace6cf4 100644 --- a/Documentation/Reference/README.md +++ b/Documentation/Reference/README.md @@ -14,10 +14,17 @@ - [Binding](structs/Binding.md) - [Button](structs/Button.md) - [EitherView](structs/EitherView.md) +- [HStack](structs/HStack.md) - [HeaderBar](structs/HeaderBar.md) - [InspectorWrapper](structs/InspectorWrapper.md) +- [List](structs/List.md) +- [NavigationSplitView](structs/NavigationSplitView.md) +- [ScrollView](structs/ScrollView.md) - [State](structs/State.md) +- [StateWrapper](structs/StateWrapper.md) +- [StatusPage](structs/StatusPage.md) - [Text](structs/Text.md) +- [ToolbarView](structs/ToolbarView.md) - [UpdateObserver](structs/UpdateObserver.md) - [VStack](structs/VStack.md) - [Window](structs/Window.md) diff --git a/Documentation/Reference/classes/ViewStorage.md b/Documentation/Reference/classes/ViewStorage.md index 11d060f..3e0cdea 100644 --- a/Documentation/Reference/classes/ViewStorage.md +++ b/Documentation/Reference/classes/ViewStorage.md @@ -15,7 +15,7 @@ The view's content. ### `state` -The view's state (used in `VStack`). +The view's state (used in `StateWrapper`). ## Methods ### `init(_:content:state:)` diff --git a/Documentation/Reference/extensions/Array.md b/Documentation/Reference/extensions/Array.md index b530cdf..be7bef5 100644 --- a/Documentation/Reference/extensions/Array.md +++ b/Documentation/Reference/extensions/Array.md @@ -2,6 +2,11 @@ # `Array` +## Properties +### `view` + +The array's view body is the array itself. + ## Methods ### `widget()` diff --git a/Documentation/Reference/extensions/String.md b/Documentation/Reference/extensions/String.md index 05a18a0..2705ad9 100644 --- a/Documentation/Reference/extensions/String.md +++ b/Documentation/Reference/extensions/String.md @@ -10,3 +10,7 @@ A label for main content in a view storage. ### `transition` A label for the transition data in a GTUI widget's fields. + +### `navigationLabel` + +A label for the navigation label in a GTUI widget's fields. diff --git a/Documentation/Reference/extensions/View.md b/Documentation/Reference/extensions/View.md index 4b3564b..ffdce33 100644 --- a/Documentation/Reference/extensions/View.md +++ b/Documentation/Reference/extensions/View.md @@ -44,6 +44,18 @@ Enable or disable the vertical expansion. - Parameter enabled: Whether it is enabled or disabled. - Returns: A view. +### `halign(_:)` + +Set the horizontal alignment. +- Parameter align: The alignment. +- Returns: A view. + +### `valign(_:)` + +Set the vertical alignment. +- Parameter align: The alignment. +- Returns: A view. + ### `frame(minWidth:minHeight:)` Set the view's minimal width or height. @@ -64,6 +76,40 @@ Set the view's transition. - Parameter transition: The transition. - Returns: A view. +### `navigationTitle(_:)` + +Set the view's navigation title. +- Parameter label: The navigation title. +- Returns: A view. + +### `style(_:)` + +Add a style class to the view. +- Parameter style: The style class. +- Returns: A view. + +### `onAppear(_:)` + +Run a function when the view appears for the first time. +- Parameter closure: The function. +- Returns: A view. + +### `topToolbar(visible:_:)` + +Add a top toolbar to the view. +- Parameters: + - toolbar: The toolbar's content. + - visible: Whether the toolbar is visible. +- Returns: A view. + +### `bottomToolbar(visible:_:)` + +Add a bottom toolbar to the view. +- Parameters: + - toolbar: The toolbar's content. + - visible: Whether the toolbar is visible. +- Returns: A view. + ### `onUpdate(_:)` Run a function when the view gets an update. diff --git a/Documentation/Reference/structs/HStack.md b/Documentation/Reference/structs/HStack.md new file mode 100644 index 0000000..dfa5f93 --- /dev/null +++ b/Documentation/Reference/structs/HStack.md @@ -0,0 +1,26 @@ +**STRUCT** + +# `HStack` + +A horizontal GtkBox equivalent. + +## Properties +### `content` + +The content. + +## Methods +### `init(content:)` + +Initialize a `HStack`. +- Parameter content: The view content. + +### `update(_:)` + +Update a view storage. +- Parameter storage: The view storage. + +### `container()` + +Get a view storage. +- Returns: The view storage. diff --git a/Documentation/Reference/structs/List.md b/Documentation/Reference/structs/List.md new file mode 100644 index 0000000..9cf821e --- /dev/null +++ b/Documentation/Reference/structs/List.md @@ -0,0 +1,46 @@ +**STRUCT** + +# `List` + +A list box widget. + +## Properties +### `elements` + +The elements. + +### `content` + +The content. + +### `selection` + +The identifier of the selected element. + +## Methods +### `init(_:selection:content:)` + +Initialize `ForEach`. +- Parameters: + - elements: The elements. + - selection: The identifier of the selected element. + - content: The view for an element. + +### `update(_:)` + +Update a view storage. +- Parameter storage: The view storage. + +### `container()` + +Get a view storage. +- Returns: The view storage. + +### `updateSelection(box:)` + +Update the list's selection. +- Parameter box: The list box. + +### `sidebarStyle()` + +Add the "navigation-sidebar" style class. diff --git a/Documentation/Reference/structs/NavigationSplitView.md b/Documentation/Reference/structs/NavigationSplitView.md new file mode 100644 index 0000000..a58e25c --- /dev/null +++ b/Documentation/Reference/structs/NavigationSplitView.md @@ -0,0 +1,40 @@ +**STRUCT** + +# `NavigationSplitView` + +A navigation split view widget. + +## Properties +### `sidebar` + +The sidebar's content. + +### `content` + +The split view's main content. + +### `sidebarID` + +The sidebar content's id. + +### `contentID` + +The main content's id. + +## Methods +### `init(sidebar:content:)` + +Initialize a navigation split view. +- Parameters: + - sidebar: The sidebar content. + - content: The main content. + +### `container()` + +Get the container of the navigation split view widget. +- Returns: The view storage. + +### `update(_:)` + +Update the view storage of the navigation split view widget. +- Parameter storage: The view storage. diff --git a/Documentation/Reference/structs/ScrollView.md b/Documentation/Reference/structs/ScrollView.md new file mode 100644 index 0000000..2d978ab --- /dev/null +++ b/Documentation/Reference/structs/ScrollView.md @@ -0,0 +1,26 @@ +**STRUCT** + +# `ScrollView` + +A GtkScrolledWindow equivalent. + +## Properties +### `content` + +The content. + +## Methods +### `init(content:)` + +Initialize a `ScrollView`. +- Parameter content: The view content. + +### `update(_:)` + +Update a view storage. +- Parameter storage: The view storage. + +### `container()` + +Get a view storage. +- Returns: The view storage. diff --git a/Documentation/Reference/structs/StateWrapper.md b/Documentation/Reference/structs/StateWrapper.md new file mode 100644 index 0000000..4346fb8 --- /dev/null +++ b/Documentation/Reference/structs/StateWrapper.md @@ -0,0 +1,37 @@ +**STRUCT** + +# `StateWrapper` + +A storage for `@State` properties. + +## Properties +### `content` + +The content. + +### `state` + +The state information (from properties with the `State` wrapper). + +## Methods +### `init(content:)` + +Initialize a `StateWrapper`. +- Parameter content: The view content. + +### `init(content:state:)` + +Initialize a `StateWrapper`. +- Parameters: + - content: The view content. + - state: The state information. + +### `update(_:)` + +Update a view storage. +- Parameter storage: The view storage. + +### `container()` + +Get a view storage. +- Returns: The view storage. diff --git a/Documentation/Reference/structs/StatusPage.md b/Documentation/Reference/structs/StatusPage.md new file mode 100644 index 0000000..5ba82f0 --- /dev/null +++ b/Documentation/Reference/structs/StatusPage.md @@ -0,0 +1,42 @@ +**STRUCT** + +# `StatusPage` + +A status page widget. + +## Properties +### `title` + +The title. + +### `description` + +The description. + +### `icon` + +The icon. + +### `content` + +Additional content. + +## Methods +### `init(_:icon:description:content:)` + +Initialize a status page widget. +- Parameters: + - title: The title. + - icon: The icon. + - description: Additional details. + - content: Additional content. + +### `update(_:)` + +Update the view storage of the text widget. +- Parameter storage: The view storage. + +### `container()` + +Get the container of the text widget. +- Returns: The view storage. diff --git a/Documentation/Reference/structs/ToolbarView.md b/Documentation/Reference/structs/ToolbarView.md new file mode 100644 index 0000000..4268a4e --- /dev/null +++ b/Documentation/Reference/structs/ToolbarView.md @@ -0,0 +1,37 @@ +**STRUCT** + +# `ToolbarView` + +A toolbar view widget. + +## Properties +### `content` + +The sidebar's content. + +### `toolbar` + +The toolbars. + +### `bottom` + +Whether the toolbars are bottom toolbars. + +### `visible` + +Whether the toolbar is visible. + +### `toolbarID` + +The identifier of the toolbar content. + +## Methods +### `container()` + +Get the container of the toolbar view widget. +- Returns: The view storage. + +### `update(_:)` + +Update the view storage of the toolbar view widget. +- Parameter storage: The view storage. diff --git a/Documentation/Reference/structs/VStack.md b/Documentation/Reference/structs/VStack.md index da6a23e..89a7b65 100644 --- a/Documentation/Reference/structs/VStack.md +++ b/Documentation/Reference/structs/VStack.md @@ -9,23 +9,12 @@ A GtkBox equivalent. The content. -### `state` - -The state information (from properties with the `State` wrapper). - ## Methods ### `init(content:)` Initialize a `VStack`. - Parameter content: The view content. -### `init(content:state:)` - -Initialize a `VStack`. -- Parameters: - - content: The view content. - - state: The state information. - ### `update(_:)` Update a view storage. diff --git a/Icons/Demo.png b/Icons/Demo.png new file mode 100644 index 0000000000000000000000000000000000000000..96f06e93c940cc92fd1e72ae668ab6ae043c2d63 GIT binary patch literal 22184 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 YENEm&``pKLG>*LNf197nI;Vst0Nw?41ONa4 literal 0 HcmV?d00001 diff --git a/Icons/Screenshot.png b/Icons/Screenshot.png index 728711b97425b87a1eba5198bc9d1e2420cfc97c..2dc277dfb1261f723c205d6170040dc92d699d3c 100644 GIT binary patch delta 6698 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 Body + + /// Initialize a `HStack`. + /// - Parameter content: The view content. + public init(@ViewBuilder content: @escaping () -> Body) { + self.content = content + } + + /// Update a view storage. + /// - Parameter storage: The view storage. + public func update(_ storage: ViewStorage) { + content().update(storage.content[.mainContent] ?? []) + } + + /// Get a view storage. + /// - Returns: The view storage. + public func container() -> ViewStorage { + let box: Box = .init(horizontal: true) + var content: [ViewStorage] = [] + for element in self.content() { + let widget = element.storage() + _ = box.append(widget.view) + content.append(widget) + } + return .init(box, content: [.mainContent: content]) + } + +} diff --git a/Sources/Adwaita/View/List.swift b/Sources/Adwaita/View/List.swift new file mode 100644 index 0000000..1f4917d --- /dev/null +++ b/Sources/Adwaita/View/List.swift @@ -0,0 +1,76 @@ +// +// List.swift +// Adwaita +// +// Created by david-swift on 25.09.23. +// + +import GTUI + +/// A list box widget. +public struct List: Widget where Element: Identifiable { + + /// The elements. + var elements: [Element] + /// The content. + var content: (Element) -> Body + /// The identifier of the selected element. + @Binding var selection: Element.ID + + /// Initialize `ForEach`. + /// - Parameters: + /// - elements: The elements. + /// - selection: The identifier of the selected element. + /// - content: The view for an element. + public init( + _ elements: [Element], + selection: Binding, + @ViewBuilder content: @escaping (Element) -> Body + ) { + self.content = content + self.elements = elements + self._selection = selection + } + + /// Update a view storage. + /// - Parameter storage: The view storage. + public func update(_ storage: ViewStorage) { + if let box = storage.view as? ListBox { + updateSelection(box: box) + } + } + + /// Get a view storage. + /// - Returns: The view storage. + public func container() -> ViewStorage { + let box: ListBox = .init() + var content: [ViewStorage] = [] + for element in elements { + let widget = self.content(element).widget().container() + _ = box.append(widget.view) + content.append(widget) + } + _ = box.handler { + let selection = box.getSelectedRow() + if let id = elements[safe: selection]?.id { + self.selection = id + } + } + updateSelection(box: box) + return .init(box, content: [.mainContent: content]) + } + + /// Update the list's selection. + /// - Parameter box: The list box. + func updateSelection(box: ListBox) { + if let index = elements.firstIndex(where: { $0.id == selection }) { + box.selectRow(at: index) + } + } + + /// Add the "navigation-sidebar" style class. + public func sidebarStyle() -> View { + style("navigation-sidebar") + } + +} diff --git a/Sources/Adwaita/View/Modifiers/InspectorWrapper.swift b/Sources/Adwaita/View/Modifiers/InspectorWrapper.swift index ed0d5cb..0e65c87 100644 --- a/Sources/Adwaita/View/Modifiers/InspectorWrapper.swift +++ b/Sources/Adwaita/View/Modifiers/InspectorWrapper.swift @@ -63,6 +63,20 @@ extension View { inspect { _ = $0?.vexpand() } } + /// Set the horizontal alignment. + /// - Parameter align: The alignment. + /// - Returns: A view. + public func halign(_ align: Alignment) -> View { + inspect { _ = $0?.halign(align) } + } + + /// Set the vertical alignment. + /// - Parameter align: The alignment. + /// - Returns: A view. + public func valign(_ align: Alignment) -> View { + inspect { _ = $0?.valign(align) } + } + /// Set the view's minimal width or height. /// - Parameters: /// - minWidth: The minimal width. @@ -86,4 +100,25 @@ extension View { inspect { $0?.fields[.transition] = transition } } + /// Set the view's navigation title. + /// - Parameter label: The navigation title. + /// - Returns: A view. + public func navigationTitle(_ label: String) -> View { + inspect { $0?.fields[.navigationLabel] = label } + } + + /// Add a style class to the view. + /// - Parameter style: The style class. + /// - Returns: A view. + public func style(_ style: String) -> View { + inspect { _ = $0?.addStyle(style) } + } + + /// Run a function when the view appears for the first time. + /// - Parameter closure: The function. + /// - Returns: A view. + public func onAppear(_ closure: @escaping () -> Void) -> View { + inspect { _ in closure() } + } + } diff --git a/Sources/Adwaita/View/Modifiers/ToolbarView.swift b/Sources/Adwaita/View/Modifiers/ToolbarView.swift new file mode 100644 index 0000000..6efa8dc --- /dev/null +++ b/Sources/Adwaita/View/Modifiers/ToolbarView.swift @@ -0,0 +1,90 @@ +// +// ToolbarView.swift +// Adwaita +// +// Created by david-swift on 24.09.23. +// + +import GTUI + +/// A toolbar view widget. +struct ToolbarView: Widget { + + /// The sidebar's content. + var content: View + /// The toolbars. + var toolbar: () -> Body + /// Whether the toolbars are bottom toolbars. + var bottom: Bool + /// Whether the toolbar is visible. + var visible: Bool + + /// The identifier of the toolbar content. + let toolbarID = "toolbar" + + /// Get the container of the toolbar view widget. + /// - Returns: The view storage. + func container() -> ViewStorage { + let content = content.storage() + let view = GTUI.ToolbarView(content.view) + var toolbarContent: [ViewStorage] = [] + for item in toolbar() { + let storage = item.storage() + toolbarContent.append(storage) + if bottom { + _ = view.addBottomBar(storage.view) + } else { + _ = view.addTopBar(storage.view) + } + } + if bottom { + view.setRevealBottomBar(visible) + } else { + view.setRevealTopBar(visible) + } + return .init(view, content: [.mainContent: [content], toolbarID: toolbarContent]) + } + + /// Update the view storage of the toolbar view widget. + /// - Parameter storage: The view storage. + func update(_ storage: ViewStorage) { + if let mainContent = storage.content[.mainContent]?.first { + content.widget().update(mainContent) + } + if let toolbar = storage.content[toolbarID] { + for (index, content) in toolbar.enumerated() { + self.toolbar()[safe: index]?.updateStorage(content) + } + } + if let view = storage.view as? GTUI.ToolbarView { + if bottom { + view.setRevealBottomBar(visible) + } else { + view.setRevealTopBar(visible) + } + } + } + +} + +extension View { + + /// Add a top toolbar to the view. + /// - Parameters: + /// - toolbar: The toolbar's content. + /// - visible: Whether the toolbar is visible. + /// - Returns: A view. + public func topToolbar(visible: Bool = true, @ViewBuilder _ toolbar: @escaping () -> Body) -> View { + ToolbarView(content: self, toolbar: toolbar, bottom: false, visible: visible) + } + + /// Add a bottom toolbar to the view. + /// - Parameters: + /// - toolbar: The toolbar's content. + /// - visible: Whether the toolbar is visible. + /// - Returns: A view. + public func bottomToolbar(visible: Bool = true, @ViewBuilder _ toolbar: @escaping () -> Body) -> View { + ToolbarView(content: self, toolbar: toolbar, bottom: true, visible: visible) + } + +} diff --git a/Sources/Adwaita/View/NavigationSplitView.swift b/Sources/Adwaita/View/NavigationSplitView.swift new file mode 100644 index 0000000..752b1ec --- /dev/null +++ b/Sources/Adwaita/View/NavigationSplitView.swift @@ -0,0 +1,62 @@ +// +// NavigationSplitView.swift +// Adwaita +// +// Created by david-swift on 24.09.23. +// + +import GTUI + +/// A navigation split view widget. +public struct NavigationSplitView: Widget { + + /// The sidebar's content. + var sidebar: () -> Body + /// The split view's main content. + var content: () -> Body + + /// The sidebar content's id. + let sidebarID = "sidebar" + /// The main content's id. + let contentID = "content" + + /// Initialize a navigation split view. + /// - Parameters: + /// - sidebar: The sidebar content. + /// - content: The main content. + public init(@ViewBuilder sidebar: @escaping () -> Body, @ViewBuilder content: @escaping () -> Body) { + self.sidebar = sidebar + self.content = content + } + + /// Get the container of the navigation split view widget. + /// - Returns: The view storage. + public func container() -> ViewStorage { + let splitView: GTUI.NavigationSplitView = .init() + var content: [String: [ViewStorage]] = [:] + + let sidebar = sidebar().widget().container() + let label = sidebar.view.fields[.navigationLabel] as? String ?? "" + _ = splitView.sidebar(sidebar.view, title: label) + content[sidebarID] = [sidebar] + + let mainContent = self.content().widget().container() + let mainLabel = mainContent.view.fields[.navigationLabel] as? String ?? "" + _ = splitView.content(mainContent.view, title: mainLabel) + content[contentID] = [mainContent] + + return .init(splitView, content: content) + } + + /// Update the view storage of the navigation split view widget. + /// - Parameter storage: The view storage. + public func update(_ storage: ViewStorage) { + if let storage = storage.content[contentID]?[safe: 0] { + content().widget().update(storage) + } + if let storage = storage.content[sidebarID]?[safe: 0] { + sidebar().widget().update(storage) + } + } + +} diff --git a/Sources/Adwaita/View/ScrollView.swift b/Sources/Adwaita/View/ScrollView.swift new file mode 100644 index 0000000..bd44b4c --- /dev/null +++ b/Sources/Adwaita/View/ScrollView.swift @@ -0,0 +1,37 @@ +// +// ScrollView.swift +// Adwaita +// +// Created by david-swift on 26.09.23. +// + +import GTUI + +/// A GtkScrolledWindow equivalent. +public struct ScrollView: Widget { + + /// The content. + var content: () -> Body + + /// Initialize a `ScrollView`. + /// - Parameter content: The view content. + public init(@ViewBuilder content: @escaping () -> Body) { + self.content = content + } + + /// Update a view storage. + /// - Parameter storage: The view storage. + public func update(_ storage: ViewStorage) { + if let first = storage.content[.mainContent]?.first { + content().widget().update(first) + } + } + + /// Get a view storage. + /// - Returns: The view storage. + public func container() -> ViewStorage { + let container = content().widget().container() + return .init(Scrolled().setChild(container.view), content: [.mainContent: [container]]) + } + +} diff --git a/Sources/Adwaita/View/StateWrapper.swift b/Sources/Adwaita/View/StateWrapper.swift new file mode 100644 index 0000000..e9d94bf --- /dev/null +++ b/Sources/Adwaita/View/StateWrapper.swift @@ -0,0 +1,53 @@ +// +// StateWrapper.swift +// Adwaita +// +// Created by david-swift on 26.09.23. +// + +import GTUI + +/// A storage for `@State` properties. +public struct StateWrapper: Widget { + + /// The content. + var content: () -> Body + /// The state information (from properties with the `State` wrapper). + var state: [String: StateProtocol] = [:] + + /// Initialize a `StateWrapper`. + /// - Parameter content: The view content. + public init(@ViewBuilder content: @escaping () -> Body) { + self.content = content + } + + /// Initialize a `StateWrapper`. + /// - Parameters: + /// - content: The view content. + /// - state: The state information. + init(content: @escaping () -> Body, state: [String: StateProtocol]) { + self.content = content + self.state = state + } + + /// Update a view storage. + /// - Parameter storage: The view storage. + public func update(_ storage: ViewStorage) { + for property in state { + if let value = storage.state[property.key]?.value { + property.value.value = value + } + } + if let storage = storage.content[.mainContent]?.first { + content().widget().update(storage) + } + } + + /// Get a view storage. + /// - Returns: The view storage. + public func container() -> ViewStorage { + let content = content().widget().container() + return .init(content.view, content: [.mainContent: [content]], state: state) + } + +} diff --git a/Sources/Adwaita/View/StatusPage.swift b/Sources/Adwaita/View/StatusPage.swift new file mode 100644 index 0000000..ffd0734 --- /dev/null +++ b/Sources/Adwaita/View/StatusPage.swift @@ -0,0 +1,56 @@ +// +// StatusPage.swift +// Adwaita +// +// Created by david-swift on 25.09.23. +// + +import GTUI + +/// A status page widget. +public struct StatusPage: Widget { + + /// The title. + var title: String + /// The description. + var description: String + /// The icon. + var icon: Icon + /// Additional content. + var content: Body + + /// Initialize a status page widget. + /// - Parameters: + /// - title: The title. + /// - icon: The icon. + /// - description: Additional details. + /// - content: Additional content. + public init(_ title: String, icon: Icon, description: String = "", @ViewBuilder content: () -> Body = { [] }) { + self.title = title + self.description = description + self.icon = icon + self.content = content() + } + + /// Update the view storage of the text widget. + /// - Parameter storage: The view storage. + public func update(_ storage: ViewStorage) { + if let statusPage = storage.view as? GTUI.StatusPage { + _ = statusPage.title(title).description(description).icon(icon) + } + if let storage = storage.content[.mainContent]?.first { + content.widget().update(storage) + } + } + + /// Get the container of the text widget. + /// - Returns: The view storage. + public func container() -> ViewStorage { + let child = content.widget().container() + return .init( + GTUI.StatusPage().title(title).description(description).icon(icon).child(child.view), + content: [.mainContent: [child]] + ) + } + +} diff --git a/Sources/Adwaita/View/Text.swift b/Sources/Adwaita/View/Text.swift index aa7831f..08ae833 100644 --- a/Sources/Adwaita/View/Text.swift +++ b/Sources/Adwaita/View/Text.swift @@ -1,5 +1,5 @@ // -// HeaderBar.swift +// Text.swift // Adwaita // // Created by david-swift on 23.08.23. diff --git a/Sources/Adwaita/View/VStack.swift b/Sources/Adwaita/View/VStack.swift index 3dc12da..7807b7b 100644 --- a/Sources/Adwaita/View/VStack.swift +++ b/Sources/Adwaita/View/VStack.swift @@ -1,5 +1,5 @@ // -// EitherView.swift +// VStack.swift // Adwaita // // Created by david-swift on 23.08.23. @@ -12,8 +12,6 @@ public struct VStack: Widget { /// The content. var content: () -> Body - /// The state information (from properties with the `State` wrapper). - var state: [String: StateProtocol] = [:] /// Initialize a `VStack`. /// - Parameter content: The view content. @@ -21,23 +19,9 @@ public struct VStack: Widget { self.content = content } - /// Initialize a `VStack`. - /// - Parameters: - /// - content: The view content. - /// - state: The state information. - init(content: @escaping () -> Body, state: [String: StateProtocol]) { - self.content = content - self.state = state - } - /// Update a view storage. /// - Parameter storage: The view storage. public func update(_ storage: ViewStorage) { - for property in state { - if let value = storage.state[property.key]?.value { - property.value.value = value - } - } content().update(storage.content[.mainContent] ?? []) } @@ -51,7 +35,7 @@ public struct VStack: Widget { _ = box.append(widget.view) content.append(widget) } - return .init(box, content: [.mainContent: content], state: state) + return .init(box, content: [.mainContent: content]) } } diff --git a/Tests/CounterDemo.swift b/Tests/CounterDemo.swift new file mode 100644 index 0000000..7416496 --- /dev/null +++ b/Tests/CounterDemo.swift @@ -0,0 +1,37 @@ +// +// CounterDemo.swift +// Adwaita +// +// Created by david-swift on 25.09.23. +// + +// swiftlint:disable missing_docs + +import Adwaita + +struct CounterDemo: View { + + @State private var count = 0 + + var view: Body { + description + .topToolbar { + HeaderBar.start { + Button(icon: .default(icon: .goPrevious)) { + count -= 1 + } + Button(icon: .default(icon: .goNext)) { + count += 1 + } + } + } + } + + @ViewBuilder private var description: Body { + Text("\(count)") + .style("title-1") + } + +} + +// swiftlint:enable missing_docs diff --git a/Tests/Demo.swift b/Tests/Demo.swift new file mode 100644 index 0000000..51bf81e --- /dev/null +++ b/Tests/Demo.swift @@ -0,0 +1,68 @@ +// +// Demo.swift +// Adwaita +// +// Created by david-swift on 25.09.23. +// + +// swiftlint:disable missing_docs implicitly_unwrapped_optional no_magic_numbers + +import Adwaita +import GTUI + +@main +struct Demo: App { + + let id = "io.github.david-swift.Demo" + var app: GTUIApp! + @State private var toolbar = false + + var scene: Scene { + Window(id: "main") { window in + DemoContent(window: window, app: app) + } + Window(id: "content", open: 0) { window in + Text("This window exists at most once.") + .padding() + .topToolbar { + HeaderBar.empty() + } + .onAppear { + window.setDefaultSize(width: 400, height: 250) + } + } + } + + struct DemoContent: View { + + @State private var selection: Page = .welcome + var window: GTUI.Window + var app: GTUIApp! + + var view: Body { + NavigationSplitView { + ScrollView { + List(Page.allCases, selection: $selection) { element in + Text(element.label) + .halign(.start) + .padding() + } + .sidebarStyle() + } + .topToolbar { + HeaderBar.empty() + } + .navigationTitle("Demo") + } content: { + selection.view(app: app) + } + .onAppear { + window.setDefaultSize(width: 650, height: 450) + } + } + + } + +} + +// swiftlint:enable missing_docs implicitly_unwrapped_optional no_magic_numbers diff --git a/Tests/Page.swift b/Tests/Page.swift new file mode 100644 index 0000000..3018381 --- /dev/null +++ b/Tests/Page.swift @@ -0,0 +1,40 @@ +// +// Page.swift +// Adwaita +// +// Created by david-swift on 25.09.23. +// + +// swiftlint:disable missing_docs implicitly_unwrapped_optional + +import Adwaita + +enum Page: String, Identifiable, CaseIterable { + + case welcome + case counter + case windows + + var id: Self { + self + } + + var label: String { + rawValue.capitalized + } + + @ViewBuilder + func view(app: GTUIApp!) -> Body { + switch self { + case .welcome: + WelcomeDemo() + case .counter: + CounterDemo() + case .windows: + WindowsDemo(app: app) + } + } + +} + +// swiftlint:enable missing_docs implicitly_unwrapped_optional diff --git a/Tests/WelcomeDemo.swift b/Tests/WelcomeDemo.swift new file mode 100644 index 0000000..420f294 --- /dev/null +++ b/Tests/WelcomeDemo.swift @@ -0,0 +1,29 @@ +// +// WelcomeDemo.swift +// Adwaita +// +// Created by david-swift on 25.09.23. +// + +// swiftlint:disable missing_docs + +import Adwaita + +struct WelcomeDemo: View { + + @State private var test = false + + var view: Body { + StatusPage( + "Swift Adwaita Demo", + icon: .default(icon: .gnomeAdwaita1Demo), + description: "This is a collection of examples for the Swift Adwaita package." + ) + .topToolbar { + HeaderBar.empty() + } + } + +} + +// swiftlint:enable missing_docs diff --git a/Tests/WindowsDemo.swift b/Tests/WindowsDemo.swift new file mode 100644 index 0000000..bb25f41 --- /dev/null +++ b/Tests/WindowsDemo.swift @@ -0,0 +1,34 @@ +// +// WindowsDemo.swift +// Adwaita +// +// Created by david-swift on 25.09.23. +// + +// swiftlint:disable missing_docs implicitly_unwrapped_optional no_magic_numbers + +import Adwaita + +struct WindowsDemo: View { + + var app: GTUIApp! + + var view: Body { + VStack { + Button("Show Window") { + app.showWindow("content") + } + .padding() + Button("Add Window") { + app.addWindow("main") + } + .padding(10, .horizontal.add(.bottom)) + } + .topToolbar { + HeaderBar.empty() + } + } + +} + +// swiftlint:enable missing_docs implicitly_unwrapped_optional no_magic_numbers diff --git a/Tests/main.swift b/Tests/main.swift deleted file mode 100644 index b28c77c..0000000 --- a/Tests/main.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// main.swift -// Adwaita -// -// Created by david-swift on 05.08.23. -// - -// swiftlint:disable missing_docs implicitly_unwrapped_optional no_magic_numbers - -import Adwaita - -@main -struct Counter: App { - - let id = "io.github.david-swift.Counter" - var app: GTUIApp! - - var scene: Scene { - Window(id: "toggle") { _ in - Button("Add Window") { - app.addWindow("content-view") - } - .padding() - Button("Show Window") { - app.showWindow("content-view") - } - .padding(10, .horizontal.add(.bottom)) - } - Window(id: "content-view", open: 0) { _ in - ContentView() - } - } - -} - -struct ContentView: View { - - @State private var count = 0 - - var view: Body { - HeaderBar.start { - Button(icon: .default(icon: .goPrevious)) { - count -= 1 - } - Button(icon: .default(icon: .goNext)) { - count += 1 - } - } - description - } - - @ViewBuilder private var description: Body { - VStack { - switch count { - case 1: - Text("One") - .transition(.slideUp) - case 0: - Text("Zero") - default: - Text("Hello, world, \(count)!") - } - } - .padding(50) - .onUpdate { - print(count) - } - } - -} - -// swiftlint:enable missing_docs implicitly_unwrapped_optional no_magic_numbers diff --git a/user-manual/Advanced/CreatingWidgets.md b/user-manual/Advanced/CreatingWidgets.md new file mode 100644 index 0000000..e96d9a0 --- /dev/null +++ b/user-manual/Advanced/CreatingWidgets.md @@ -0,0 +1,64 @@ +# Creating Widgets + +Widgets are special views that do not provide a collection of other views as a content, +but have functions that are called when creating or updating the view. +Normally, a widget manages a GTK or Libadwaita widget using [SwiftGui][1]. + +## Recreate the `Text` widget +In this tutorial, we will recreate the text widget. +A widget conforms to the `Widget` protocol: +```swift +struct CustomText: Widget { } +``` +You can add properties to the widget: +```swift +struct CustomText: Widget { + + var text: String + +} +``` +This widget can be called in a view body using `CustomText(text: "Hello, world!")`. +Now, add the two functions required by the protocol: +```swift +struct CustomText: Widget { + + var text: String + + public func container() -> ViewStorage { } + public func update(_ storage: ViewStorage) { } + +} +``` + +## The `container()` Function +This function initializes the widget when the widget appears for the first time. +It expects a `ViewStorage` as the return type. +In our case, this function is very simple: +```swift +func container() -> ViewStorage { + .init(MarkupLabel(self.text)) +} +``` +`MarkupLabel` is defined in [SwiftGui][1]. + +## The `update(_:)` Function +Whenever a state of the app changes, the `update(_:)` function of the widget gets called. +You get the view storage that you have previously initialized as a parameter. +Update the storage to reflect the current state of the widget: +```swift +func update(_ storage: ViewStorage) { + if let label = storage.view as? MarkupLabel { + label.setText(text) + } +} +``` + +## Containers +Some widgets act as containers that accept other widgets as children. +In that case, use the `ViewStorage`'s `content` property for storing their view storages. +In the `update(_:)` function, update the children's storages. +An example showcasing how to implement containers is the [VStack][2]. + +[1]: https://github.com/JCWasmx86/SwiftGui +[2]: ../../Sources/Adwaita/View/VStack.swift