Add widgets and demo application
This commit is contained in:
parent
2611e0c448
commit
cbbf08bd3b
@ -118,14 +118,6 @@ custom_rules:
|
|||||||
message: 'Spaces should be used instead of tabs.'
|
message: 'Spaces should be used instead of tabs.'
|
||||||
severity: warning
|
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
|
# Thanks to the creator of the SwiftLint rule
|
||||||
# "empty_first_line"
|
# "empty_first_line"
|
||||||
# https://github.com/coteditor/CotEditor/blob/main/.swiftlint.yml
|
# https://github.com/coteditor/CotEditor/blob/main/.swiftlint.yml
|
||||||
|
|||||||
@ -14,10 +14,17 @@
|
|||||||
- [Binding](structs/Binding.md)
|
- [Binding](structs/Binding.md)
|
||||||
- [Button](structs/Button.md)
|
- [Button](structs/Button.md)
|
||||||
- [EitherView](structs/EitherView.md)
|
- [EitherView](structs/EitherView.md)
|
||||||
|
- [HStack](structs/HStack.md)
|
||||||
- [HeaderBar](structs/HeaderBar.md)
|
- [HeaderBar](structs/HeaderBar.md)
|
||||||
- [InspectorWrapper](structs/InspectorWrapper.md)
|
- [InspectorWrapper](structs/InspectorWrapper.md)
|
||||||
|
- [List](structs/List.md)
|
||||||
|
- [NavigationSplitView](structs/NavigationSplitView.md)
|
||||||
|
- [ScrollView](structs/ScrollView.md)
|
||||||
- [State](structs/State.md)
|
- [State](structs/State.md)
|
||||||
|
- [StateWrapper](structs/StateWrapper.md)
|
||||||
|
- [StatusPage](structs/StatusPage.md)
|
||||||
- [Text](structs/Text.md)
|
- [Text](structs/Text.md)
|
||||||
|
- [ToolbarView](structs/ToolbarView.md)
|
||||||
- [UpdateObserver](structs/UpdateObserver.md)
|
- [UpdateObserver](structs/UpdateObserver.md)
|
||||||
- [VStack](structs/VStack.md)
|
- [VStack](structs/VStack.md)
|
||||||
- [Window](structs/Window.md)
|
- [Window](structs/Window.md)
|
||||||
|
|||||||
@ -15,7 +15,7 @@ The view's content.
|
|||||||
|
|
||||||
### `state`
|
### `state`
|
||||||
|
|
||||||
The view's state (used in `VStack`).
|
The view's state (used in `StateWrapper`).
|
||||||
|
|
||||||
## Methods
|
## Methods
|
||||||
### `init(_:content:state:)`
|
### `init(_:content:state:)`
|
||||||
|
|||||||
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
# `Array`
|
# `Array`
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
### `view`
|
||||||
|
|
||||||
|
The array's view body is the array itself.
|
||||||
|
|
||||||
## Methods
|
## Methods
|
||||||
### `widget()`
|
### `widget()`
|
||||||
|
|
||||||
|
|||||||
@ -10,3 +10,7 @@ A label for main content in a view storage.
|
|||||||
### `transition`
|
### `transition`
|
||||||
|
|
||||||
A label for the transition data in a GTUI widget's fields.
|
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.
|
||||||
|
|||||||
@ -44,6 +44,18 @@ Enable or disable the vertical expansion.
|
|||||||
- Parameter enabled: Whether it is enabled or disabled.
|
- Parameter enabled: Whether it is enabled or disabled.
|
||||||
- Returns: A view.
|
- 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:)`
|
### `frame(minWidth:minHeight:)`
|
||||||
|
|
||||||
Set the view's minimal width or height.
|
Set the view's minimal width or height.
|
||||||
@ -64,6 +76,40 @@ Set the view's transition.
|
|||||||
- Parameter transition: The transition.
|
- Parameter transition: The transition.
|
||||||
- Returns: A view.
|
- 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(_:)`
|
### `onUpdate(_:)`
|
||||||
|
|
||||||
Run a function when the view gets an update.
|
Run a function when the view gets an update.
|
||||||
|
|||||||
26
Documentation/Reference/structs/HStack.md
Normal file
26
Documentation/Reference/structs/HStack.md
Normal file
@ -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.
|
||||||
46
Documentation/Reference/structs/List.md
Normal file
46
Documentation/Reference/structs/List.md
Normal file
@ -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.
|
||||||
40
Documentation/Reference/structs/NavigationSplitView.md
Normal file
40
Documentation/Reference/structs/NavigationSplitView.md
Normal file
@ -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.
|
||||||
26
Documentation/Reference/structs/ScrollView.md
Normal file
26
Documentation/Reference/structs/ScrollView.md
Normal file
@ -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.
|
||||||
37
Documentation/Reference/structs/StateWrapper.md
Normal file
37
Documentation/Reference/structs/StateWrapper.md
Normal file
@ -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.
|
||||||
42
Documentation/Reference/structs/StatusPage.md
Normal file
42
Documentation/Reference/structs/StatusPage.md
Normal file
@ -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.
|
||||||
37
Documentation/Reference/structs/ToolbarView.md
Normal file
37
Documentation/Reference/structs/ToolbarView.md
Normal file
@ -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.
|
||||||
@ -9,23 +9,12 @@ A GtkBox equivalent.
|
|||||||
|
|
||||||
The content.
|
The content.
|
||||||
|
|
||||||
### `state`
|
|
||||||
|
|
||||||
The state information (from properties with the `State` wrapper).
|
|
||||||
|
|
||||||
## Methods
|
## Methods
|
||||||
### `init(content:)`
|
### `init(content:)`
|
||||||
|
|
||||||
Initialize a `VStack`.
|
Initialize a `VStack`.
|
||||||
- Parameter content: The view content.
|
- Parameter content: The view content.
|
||||||
|
|
||||||
### `init(content:state:)`
|
|
||||||
|
|
||||||
Initialize a `VStack`.
|
|
||||||
- Parameters:
|
|
||||||
- content: The view content.
|
|
||||||
- state: The state information.
|
|
||||||
|
|
||||||
### `update(_:)`
|
### `update(_:)`
|
||||||
|
|
||||||
Update a view storage.
|
Update a view storage.
|
||||||
|
|||||||
BIN
Icons/Demo.png
Normal file
BIN
Icons/Demo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.2 KiB |
@ -26,7 +26,7 @@ let package = Package(
|
|||||||
dependencies: [.product(name: "GTUI", package: "swiftgui")]
|
dependencies: [.product(name: "GTUI", package: "swiftgui")]
|
||||||
),
|
),
|
||||||
.executableTarget(
|
.executableTarget(
|
||||||
name: "Counter",
|
name: "Swift Adwaita Demo",
|
||||||
dependencies: ["Adwaita"],
|
dependencies: ["Adwaita"],
|
||||||
path: "Tests"
|
path: "Tests"
|
||||||
)
|
)
|
||||||
|
|||||||
137
README.md
137
README.md
@ -27,16 +27,19 @@ struct Example: View {
|
|||||||
@State private var count = 0
|
@State private var count = 0
|
||||||
|
|
||||||
var view: Body {
|
var view: Body {
|
||||||
HeaderBar.start {
|
|
||||||
Button(icon: .default(icon: .goPrevious)) {
|
|
||||||
count -= 1
|
|
||||||
}
|
|
||||||
Button(icon: .default(icon: .goNext)) {
|
|
||||||
count += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Text("\(count)")
|
Text("\(count)")
|
||||||
|
.style("title-1")
|
||||||
.padding(50)
|
.padding(50)
|
||||||
|
.topToolbar {
|
||||||
|
HeaderBar.start {
|
||||||
|
Button(icon: .default(icon: .goPrevious)) {
|
||||||
|
count -= 1
|
||||||
|
}
|
||||||
|
Button(icon: .default(icon: .goNext)) {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -46,49 +49,66 @@ Creates a simple counter view:
|
|||||||
|
|
||||||
![Counter Example][image-1]
|
![Counter Example][image-1]
|
||||||
|
|
||||||
|
More examples are available in the [Demo app][1]:
|
||||||
|
|
||||||
|
![Demo App][image-2]
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Goals][1]
|
- [Goals][2]
|
||||||
- [Widgets][2]
|
- [Widgets][3]
|
||||||
- [Installation][3]
|
- [Installation][4]
|
||||||
- [Usage][4]
|
- [Usage][5]
|
||||||
- [Thanks][5]
|
- [Thanks][6]
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
_Adwaita_’s main goal is to provide an easy-to-use interface for creating GNOME apps. The backend should stay as simple as possible, while not limiting the possibilities there are with [Libadwaita][6] and [GTK][7].
|
_Adwaita_’s main goal is to provide an easy-to-use interface for creating GNOME apps. The backend should stay as simple as possible, while not limiting the possibilities there are with [Libadwaita][7] and [GTK][8].
|
||||||
|
|
||||||
If you want to use _Adwaita_ in a project, but there are widgets missing, open an [issue on GitHub][8].
|
If you want to use _Adwaita_ in a project, but there are widgets missing, open an [issue on GitHub][9].
|
||||||
|
|
||||||
## Widgets
|
## Widgets
|
||||||
|
|
||||||
| Name | Description | Widget |
|
| Name | Description | Widget |
|
||||||
| ---------- | ----------------------------------------------------------------- | ------------ |
|
| -------------------- | ----------------------------------------------------------------- | ---------------------- |
|
||||||
| Button | A widget that triggers a function when being clicked. | GtkButton |
|
| Button | A widget that triggers a function when being clicked. | GtkButton |
|
||||||
| EitherView | A widget that displays one of its child views based on a boolean. | GtkStack |
|
| EitherView | A widget that displays one of its child views based on a boolean. | GtkStack |
|
||||||
| HeaderBar | A widget for creating custom title bars for windows. | GtkHeaderBar |
|
| HeaderBar | A widget for creating custom title bars for windows. | GtkHeaderBar |
|
||||||
| Text | A widget for displaying a small amount of text. | GtkLabel |
|
| Text | A widget for displaying a small amount of text. | GtkLabel |
|
||||||
| VStack | A widget which arranges child widgets into a single column. | GtkBox |
|
| VStack | A widget which arranges child widgets into a single column. | GtkBox |
|
||||||
|
| HStack | A widget which arranges child widgets into a single row. | GtkBox |
|
||||||
|
| List | A widget which arranges child widgets vertically into rows. | GtkListBox |
|
||||||
|
| NavigationSplitView | A widget presenting sidebar and content side by side. | AdwNavigationSplitView |
|
||||||
|
| ScrollView | A container that makes its child scrollable. | GtkScrolledWindow |
|
||||||
|
| StatusPage | A page with an icon, title, and optionally description and widget.| AdwStatusPage |
|
||||||
|
| StateWrapper | A wrapper not affecting the UI which stores state information. | - |
|
||||||
|
|
||||||
### View Modifiers
|
### View Modifiers
|
||||||
|
|
||||||
| Syntax | Description |
|
| Syntax | Description |
|
||||||
| ---------------------------- | -------------------------------------------------------------------------------------- |
|
| ---------------------------- | -------------------------------------------------------------------------------------- |
|
||||||
| `inspect(_:)` | Edit the underlying [GTUI][9] widget. |
|
| `inspect(_:)` | Edit the underlying [GTUI][10] widget. |
|
||||||
| `padding(_:_:)` | Add empty space around a view. |
|
| `padding(_:_:)` | Add empty space around a view. |
|
||||||
| `hexpand(_:)` | Enable or disable the horizontal expansion of a view. |
|
| `hexpand(_:)` | Enable or disable the horizontal expansion of a view. |
|
||||||
| `vexpand(_:)` | Enable or disable the vertical expansion of a view. |
|
| `vexpand(_:)` | Enable or disable the vertical expansion of a view. |
|
||||||
|
| `halign(_:)` | Set the horizontal alignment of a view. |
|
||||||
|
| `valign(_:)` | Set the vertical alignment of a view. |
|
||||||
| `frame(minWidth:minHeight:)` | Set the view’s minimal width or height. |
|
| `frame(minWidth:minHeight:)` | Set the view’s minimal width or height. |
|
||||||
| `frame(maxSize:)` | Set the view’s maximal size. |
|
| `frame(maxSize:)` | Set the view’s maximal size. |
|
||||||
| `transition(_:)` | Assign a transition with the view that is used if it is a direct child of a HeaderBar. |
|
| `transition(_:)` | Assign a transition with the view that is used if it is a direct child of an EitherView. |
|
||||||
| `onUpdate(_:)` | Run a function every time a view gets updated. |
|
| `onUpdate(_:)` | Run a function every time a view gets updated. |
|
||||||
|
| `navigationTitle(_:)` | Add a title that is used if the view is a direct child of a NavigationView. |
|
||||||
|
| `style(_:)` | Add a style class to the view. |
|
||||||
|
| `onAppear(_:)` | Run when the view is rendered for the first time. |
|
||||||
|
| `topToolbar(visible:_:)` | Add a native toolbar to the view. Normally, it contains a HeaderBar. |
|
||||||
|
| `bottomToolbar(visible:_:)` | Add a native bottom toolbar to the view. |
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
### Dependencies
|
### Dependencies
|
||||||
If you are using a Linux distribution, install `libadwaita-devel` or `libadwaita` (or something similar, based on the package manager) as well as `gtk4-devel`, `gtk4` or similar.
|
If you are using a Linux distribution, install `libadwaita-devel` or `libadwaita` (or something similar, based on the package manager) as well as `gtk4-devel`, `gtk4` or similar.
|
||||||
|
|
||||||
On macOS, follow these steps:
|
On macOS, follow these steps:
|
||||||
1. Install [Homebrew][10].
|
1. Install [Homebrew][11].
|
||||||
2. Install Libadwaita (and thereby GTK 4):
|
2. Install Libadwaita (and thereby GTK 4):
|
||||||
```
|
```
|
||||||
brew install libadwaita
|
brew install libadwaita
|
||||||
@ -104,45 +124,52 @@ brew install libadwaita
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
* [Getting Started][11]
|
* [Getting Started][12]
|
||||||
|
|
||||||
### Basics
|
### Basics
|
||||||
|
|
||||||
* [Hello World][12]
|
* [Hello World][13]
|
||||||
* [Creating Views][13]
|
* [Creating Views][14]
|
||||||
* [Windows][14]
|
* [Windows][15]
|
||||||
|
|
||||||
|
### Advanced
|
||||||
|
|
||||||
|
* [Creating Widgets][16]
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
- [SwiftGui][15] licensed under the [GPL-3.0 license][16]
|
- [SwiftGui][17] licensed under the [GPL-3.0 license][18]
|
||||||
|
|
||||||
### Other Thanks
|
### Other Thanks
|
||||||
- The [contributors][17]
|
- The [contributors][19]
|
||||||
- [SwiftLint][18] for checking whether code style conventions are violated
|
- [SwiftLint][20] for checking whether code style conventions are violated
|
||||||
- The programming language [Swift][19]
|
- The programming language [Swift][21]
|
||||||
- [SourceDocs][20] used for generating the [docs][21]
|
- [SourceDocs][22] used for generating the [docs][23]
|
||||||
|
|
||||||
[1]: #goals
|
[1]: Tests/
|
||||||
[2]: #widgets
|
[2]: #goals
|
||||||
[3]: #installation
|
[3]: #widgets
|
||||||
[4]: #usage
|
[4]: #installation
|
||||||
[5]: #thanks
|
[5]: #usage
|
||||||
[6]: https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/index.html
|
[6]: #thanks
|
||||||
[7]: https://docs.gtk.org/gtk4/
|
[7]: https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/index.html
|
||||||
[8]: https://github.com/david-swift/Adwaita/issues
|
[8]: https://docs.gtk.org/gtk4/
|
||||||
[9]: https://github.com/JCWasmx86/SwiftGui
|
[9]: https://github.com/david-swift/Adwaita/issues
|
||||||
[10]: https://brew.sh
|
[10]: https://github.com/JCWasmx86/SwiftGui
|
||||||
[11]: user-manual/GettingStarted.md
|
[11]: https://brew.sh
|
||||||
[12]: user-manual/Basics/HelloWorld.md
|
[12]: user-manual/GettingStarted.md
|
||||||
[13]: user-manual/Basics/CreatingViews.md
|
[13]: user-manual/Basics/HelloWorld.md
|
||||||
[14]: user-manual/Basics/Windows.md
|
[14]: user-manual/Basics/CreatingViews.md
|
||||||
[15]: https://github.com/JCWasmx86/SwiftGui
|
[15]: user-manual/Basics/Windows.md
|
||||||
[16]: https://github.com/JCWasmx86/SwiftGui/blob/main/COPYING
|
[16]: user-manual/Advanced/CreatingWidgets.md
|
||||||
[17]: Contributors.md
|
[17]: https://github.com/JCWasmx86/SwiftGui
|
||||||
[18]: https://github.com/realm/SwiftLint
|
[18]: https://github.com/JCWasmx86/SwiftGui/blob/main/COPYING
|
||||||
[19]: https://github.com/apple/swift
|
[19]: Contributors.md
|
||||||
[20]: https://github.com/SourceDocs/SourceDocs
|
[20]: https://github.com/realm/SwiftLint
|
||||||
[21]: Documentation/Reference/README.md
|
[21]: https://github.com/apple/swift
|
||||||
|
[22]: https://github.com/SourceDocs/SourceDocs
|
||||||
|
[23]: Documentation/Reference/README.md
|
||||||
|
|
||||||
[image-1]: Icons/Screenshot.png
|
[image-1]: Icons/Screenshot.png
|
||||||
|
[image-2]: Icons/Demo.png
|
||||||
|
|||||||
@ -9,8 +9,13 @@
|
|||||||
* [Creating Views][4]
|
* [Creating Views][4]
|
||||||
* [Windows][5]
|
* [Windows][5]
|
||||||
|
|
||||||
|
## Advanced
|
||||||
|
|
||||||
|
* [Creating Widgets][6]
|
||||||
|
|
||||||
[1]: README.md
|
[1]: README.md
|
||||||
[2]: user-manual/GettingStarted.md
|
[2]: user-manual/GettingStarted.md
|
||||||
[3]: user-manual/Basics/HelloWorld.md
|
[3]: user-manual/Basics/HelloWorld.md
|
||||||
[4]: user-manual/Basics/CreatingViews.md
|
[4]: user-manual/Basics/CreatingViews.md
|
||||||
[5]: user-manual/Basics/Windows.md
|
[5]: user-manual/Basics/Windows.md
|
||||||
|
[6]: user-manual/Advanced/CreatingWidgets.md
|
||||||
|
|||||||
@ -7,7 +7,10 @@
|
|||||||
|
|
||||||
import GTUI
|
import GTUI
|
||||||
|
|
||||||
extension Array where Element == View {
|
extension Array: View where Element == View {
|
||||||
|
|
||||||
|
/// The array's view body is the array itself.
|
||||||
|
public var view: Body { self }
|
||||||
|
|
||||||
/// Get a widget from a collection of views.
|
/// Get a widget from a collection of views.
|
||||||
/// - Returns: A widget.
|
/// - Returns: A widget.
|
||||||
|
|||||||
@ -11,5 +11,7 @@ extension String {
|
|||||||
static var mainContent: Self { "main" }
|
static var mainContent: Self { "main" }
|
||||||
/// A label for the transition data in a GTUI widget's fields.
|
/// A label for the transition data in a GTUI widget's fields.
|
||||||
static var transition: Self { "transition" }
|
static var transition: Self { "transition" }
|
||||||
|
/// A label for the navigation label in a GTUI widget's fields.
|
||||||
|
static var navigationLabel: Self { "navigation-label" }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,7 +41,7 @@ extension View {
|
|||||||
state[label] = value
|
state[label] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return VStack(content: { view }, state: state)
|
return StateWrapper(content: { view }, state: state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,6 +50,8 @@ extension View {
|
|||||||
func updateStorage(_ storage: ViewStorage) {
|
func updateStorage(_ storage: ViewStorage) {
|
||||||
if let widget = self as? Widget {
|
if let widget = self as? Widget {
|
||||||
widget.update(storage)
|
widget.update(storage)
|
||||||
|
} else {
|
||||||
|
StateWrapper { self }.update(storage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ public class ViewStorage {
|
|||||||
public var view: NativeWidgetPeer
|
public var view: NativeWidgetPeer
|
||||||
/// The view's content.
|
/// The view's content.
|
||||||
public var content: [String: [ViewStorage]]
|
public var content: [String: [ViewStorage]]
|
||||||
/// The view's state (used in `VStack`).
|
/// The view's state (used in `StateWrapper`).
|
||||||
public var state: [String: StateProtocol]
|
public var state: [String: StateProtocol]
|
||||||
|
|
||||||
/// Initialize a view storage.
|
/// Initialize a view storage.
|
||||||
|
|||||||
41
Sources/Adwaita/View/HStack.swift
Normal file
41
Sources/Adwaita/View/HStack.swift
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// HStack.swift
|
||||||
|
// Adwaita
|
||||||
|
//
|
||||||
|
// Created by david-swift on 26.09.23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import GTUI
|
||||||
|
|
||||||
|
/// A horizontal GtkBox equivalent.
|
||||||
|
public struct HStack: Widget {
|
||||||
|
|
||||||
|
/// The content.
|
||||||
|
var content: () -> 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])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
76
Sources/Adwaita/View/List.swift
Normal file
76
Sources/Adwaita/View/List.swift
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// List.swift
|
||||||
|
// Adwaita
|
||||||
|
//
|
||||||
|
// Created by david-swift on 25.09.23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import GTUI
|
||||||
|
|
||||||
|
/// A list box widget.
|
||||||
|
public struct List<Element>: 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<Element.ID>,
|
||||||
|
@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")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -63,6 +63,20 @@ extension View {
|
|||||||
inspect { _ = $0?.vexpand() }
|
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.
|
/// Set the view's minimal width or height.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - minWidth: The minimal width.
|
/// - minWidth: The minimal width.
|
||||||
@ -86,4 +100,25 @@ extension View {
|
|||||||
inspect { $0?.fields[.transition] = transition }
|
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() }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
90
Sources/Adwaita/View/Modifiers/ToolbarView.swift
Normal file
90
Sources/Adwaita/View/Modifiers/ToolbarView.swift
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
62
Sources/Adwaita/View/NavigationSplitView.swift
Normal file
62
Sources/Adwaita/View/NavigationSplitView.swift
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
37
Sources/Adwaita/View/ScrollView.swift
Normal file
37
Sources/Adwaita/View/ScrollView.swift
Normal file
@ -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]])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
53
Sources/Adwaita/View/StateWrapper.swift
Normal file
53
Sources/Adwaita/View/StateWrapper.swift
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
56
Sources/Adwaita/View/StatusPage.swift
Normal file
56
Sources/Adwaita/View/StatusPage.swift
Normal file
@ -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]]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// HeaderBar.swift
|
// Text.swift
|
||||||
// Adwaita
|
// Adwaita
|
||||||
//
|
//
|
||||||
// Created by david-swift on 23.08.23.
|
// Created by david-swift on 23.08.23.
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// EitherView.swift
|
// VStack.swift
|
||||||
// Adwaita
|
// Adwaita
|
||||||
//
|
//
|
||||||
// Created by david-swift on 23.08.23.
|
// Created by david-swift on 23.08.23.
|
||||||
@ -12,8 +12,6 @@ public struct VStack: Widget {
|
|||||||
|
|
||||||
/// The content.
|
/// The content.
|
||||||
var content: () -> Body
|
var content: () -> Body
|
||||||
/// The state information (from properties with the `State` wrapper).
|
|
||||||
var state: [String: StateProtocol] = [:]
|
|
||||||
|
|
||||||
/// Initialize a `VStack`.
|
/// Initialize a `VStack`.
|
||||||
/// - Parameter content: The view content.
|
/// - Parameter content: The view content.
|
||||||
@ -21,23 +19,9 @@ public struct VStack: Widget {
|
|||||||
self.content = content
|
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.
|
/// Update a view storage.
|
||||||
/// - Parameter storage: The view storage.
|
/// - Parameter storage: The view storage.
|
||||||
public func update(_ storage: ViewStorage) {
|
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] ?? [])
|
content().update(storage.content[.mainContent] ?? [])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +35,7 @@ public struct VStack: Widget {
|
|||||||
_ = box.append(widget.view)
|
_ = box.append(widget.view)
|
||||||
content.append(widget)
|
content.append(widget)
|
||||||
}
|
}
|
||||||
return .init(box, content: [.mainContent: content], state: state)
|
return .init(box, content: [.mainContent: content])
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
37
Tests/CounterDemo.swift
Normal file
37
Tests/CounterDemo.swift
Normal file
@ -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
|
||||||
68
Tests/Demo.swift
Normal file
68
Tests/Demo.swift
Normal file
@ -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
|
||||||
40
Tests/Page.swift
Normal file
40
Tests/Page.swift
Normal file
@ -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
|
||||||
29
Tests/WelcomeDemo.swift
Normal file
29
Tests/WelcomeDemo.swift
Normal file
@ -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
|
||||||
34
Tests/WindowsDemo.swift
Normal file
34
Tests/WindowsDemo.swift
Normal file
@ -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
|
||||||
@ -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
|
|
||||||
64
user-manual/Advanced/CreatingWidgets.md
Normal file
64
user-manual/Advanced/CreatingWidgets.md
Normal file
@ -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
|
||||||
Loading…
x
Reference in New Issue
Block a user