Update demo and fix bugs

- Fix build optional in ViewBuilder
- Fix maximum size frame modifier
- Improve inspector wrapper
- Improve header bar
- Improve status page
- Improve the naming of some elements
This commit is contained in:
david-swift 2023-10-12 22:15:15 +02:00
parent 22c10d6ff3
commit 921f025e39
29 changed files with 451 additions and 101 deletions

View File

@ -13,6 +13,7 @@
- [Binding](structs/Binding.md)
- [Button](structs/Button.md)
- [Clamp](structs/Clamp.md)
- [EitherView](structs/EitherView.md)
- [HStack](structs/HStack.md)
- [HeaderBar](structs/HeaderBar.md)
@ -57,6 +58,7 @@
## Typealiases
- [Body](typealiases/Body.md)
- [GTUIWindow](typealiases/GTUIWindow.md)
- [Scene](typealiases/Scene.md)
- [SceneBuilder](typealiases/SceneBuilder.md)

View File

@ -18,9 +18,15 @@ Update a storage to a view.
Get a storage.
- Returns: The storage.
### `frame(maxSize:)`
Set the view's maximal size.
- Parameter maxSize: The maximal size.
- Returns: A view.
### `inspect(_:)`
Modify a GTUI widget before being displayed.
Modify a GTUI widget before being displayed and when being updated.
- Parameter modify: Modify the widget.
- Returns: A view.
@ -64,12 +70,6 @@ Set the view's minimal width or height.
- minHeight: The minimal height.
- Returns: A view.
### `frame(maxSize:)`
Set the view's maximal size.
- Parameter maxSize: The maximal size.
- Returns: A view.
### `transition(_:)`
Set the view's transition.

View File

@ -3,6 +3,6 @@
# `WindowScene`
## Properties
### `body`
### `scene`
The window scene's body is itself.

View File

@ -5,6 +5,6 @@
A structure conforming to `WindowScene` can be added to an app's `scene`.
## Properties
### `body`
### `scene`
The group's content.

View File

@ -0,0 +1,25 @@
**STRUCT**
# `Clamp`
A horizontal AdwClamp equivalent.
## Properties
### `content`
The content.
### `maxSize`
The maximum size.
## Methods
### `update(_:)`
Update a view storage.
- Parameter storage: The view storage.
### `container()`
Get a view storage.
- Returns: The view storage.

View File

@ -13,6 +13,14 @@ The start content of the header bar.
The end content of the header bar.
### `titleButtons`
Whether the title buttons are visible.
### `headerBarTitle`
The view acting as the title of the header bar.
### `startID`
The start content's id.
@ -21,11 +29,16 @@ The start content's id.
The end content's id.
### `titleID`
The title's id.
## Methods
### `init(start:end:)`
### `init(titleButtons:start:end:)`
Initialize a header bar.
- Parameters:
- titleButtons: Whether the title buttons (e.g. close button) are visible.
- start: The start content.
- end: The end content.
@ -55,3 +68,9 @@ Update a header bar's view storage.
Get the container for a header bar.
- Returns: The view storage.
### `headerBarTitle(view:)`
Set the title widget for the header bar.
- Parameter view: The widget in the header bar.
- Returns: The header bar.

View File

@ -2,7 +2,7 @@
# `InspectorWrapper`
A widget which executes a custom code on the GTUI widget when being created.
A widget which executes a custom code on the GTUI widget when being created and updated.
## Properties
### `modify`

View File

@ -20,7 +20,7 @@ The identifier of the selected element.
## Methods
### `init(_:selection:content:)`
Initialize `ForEach`.
Initialize `List`.
- Parameters:
- elements: The elements.
- selection: The identifier of the selected element.

View File

@ -0,0 +1,5 @@
**TYPEALIAS**
# `GTUIWindow`
A GTUI window.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,11 @@
//
// GTUIWindow.swift
// Adwaita
//
// Created by david-swift on 12.10.23.
//
import GTUI
/// A GTUI window.
public typealias GTUIWindow = GTUI.Window

View File

@ -57,7 +57,11 @@ public enum ViewBuilder {
/// - Parameter component: An optional component.
/// - Returns: A nonoptional component.
public static func buildOptional(_ component: Component?) -> Component {
component ?? .components([])
if let component {
return .element(EitherView(true, { buildFinalResult(component) }, else: nil))
} else {
return .element(EitherView(false, nil) { [] })
}
}
/// Enables support for `if`-`else` and `switch` statements.

View File

@ -27,6 +27,6 @@ public protocol WindowScene: WindowSceneGroup {
extension WindowScene {
/// The window scene's body is itself.
@SceneBuilder public var body: Scene { self }
@SceneBuilder public var scene: Scene { self }
}

View File

@ -9,7 +9,7 @@
public protocol WindowSceneGroup {
/// The group's content.
@SceneBuilder var body: Scene { get }
@SceneBuilder var scene: Scene { get }
}
@ -19,7 +19,7 @@ extension WindowSceneGroup {
/// - Returns: The windows.
func windows() -> [WindowScene] {
var content: [WindowScene] = []
for element in body {
for element in scene {
if let window = element as? WindowScene {
content.append(window)
} else {

View File

@ -15,7 +15,7 @@ public class WindowStorage {
/// Whether the reference to the window should disappear in the next update.
public var destroy = false
/// The GTUI window.
public var window: GTUI.Window
public var window: GTUIWindow
/// The content's storage.
public var view: ViewStorage
@ -24,7 +24,7 @@ public class WindowStorage {
/// - id: The window's identifier.
/// - window: The GTUI window.
/// - view: The content's storage.
public init(id: String, window: GTUI.Window, view: ViewStorage) {
public init(id: String, window: GTUIWindow, view: ViewStorage) {
self.id = id
self.window = window
self.view = view

View File

@ -14,17 +14,25 @@ public struct HeaderBar: Widget {
var start: Body
/// The end content of the header bar.
var end: Body
/// Whether the title buttons are visible.
var titleButtons: Bool
/// The view acting as the title of the header bar.
var headerBarTitle: Body?
/// The start content's id.
let startID = "start"
/// The end content's id.
let endID = "end"
/// The title's id.
let titleID = "title"
/// Initialize a header bar.
/// - Parameters:
/// - titleButtons: Whether the title buttons (e.g. close button) are visible.
/// - start: The start content.
/// - end: The end content.
public init(@ViewBuilder start: () -> Body, @ViewBuilder end: () -> Body) {
public init(titleButtons: Bool = true, @ViewBuilder start: () -> Body, @ViewBuilder end: () -> Body) {
self.titleButtons = titleButtons
self.start = start()
self.end = end()
}
@ -52,8 +60,14 @@ public struct HeaderBar: Widget {
/// Update a header bar's view storage.
/// - Parameter storage: The view storage.
public func update(_ storage: ViewStorage) {
if let bar = storage.view as? GTUI.HeaderBar {
_ = bar.showTitleButtons(titleButtons)
}
start.update(storage.content[startID] ?? [])
end.update(storage.content[endID] ?? [])
if let first = storage.content[titleID]?.first {
headerBarTitle?.widget().update(first)
}
}
/// Get the container for a header bar.
@ -72,7 +86,25 @@ public struct HeaderBar: Widget {
_ = bar.packEnd(element.view)
endContent.append(element)
}
return .init(bar, content: [startID: startContent, endID: endContent])
let title = headerBarTitle?.widget().container()
let titleStorage: [ViewStorage]
if let title {
_ = bar.titleWidget(title.view)
titleStorage = [title]
} else {
titleStorage = []
}
_ = bar.showTitleButtons(titleButtons)
return .init(bar, content: [startID: startContent, endID: endContent, titleID: titleStorage])
}
/// Set the title widget for the header bar.
/// - Parameter view: The widget in the header bar.
/// - Returns: The header bar.
public func headerBarTitle(@ViewBuilder view: () -> Body) -> Self {
var newSelf = self
newSelf.headerBarTitle = view()
return newSelf
}
}

View File

@ -17,7 +17,7 @@ public struct List<Element>: Widget where Element: Identifiable {
/// The identifier of the selected element.
@Binding var selection: Element.ID
/// Initialize `ForEach`.
/// Initialize `List`.
/// - Parameters:
/// - elements: The elements.
/// - selection: The identifier of the selected element.

View File

@ -0,0 +1,49 @@
//
// Clamp.swift
// Adwaita
//
// Created by david-swift on 12.10.23.
//
import GTUI
/// A horizontal AdwClamp equivalent.
struct Clamp: Widget {
/// The content.
var content: View
/// The maximum size.
var maxSize: Int
/// Update a view storage.
/// - Parameter storage: The view storage.
func update(_ storage: ViewStorage) {
if let clamp = storage.view as? GTUI.Clamp {
_ = clamp.maximumSize(maxSize)
}
if let storage = storage.content[.mainContent]?[safe: 0] {
content.widget().update(storage)
}
}
/// Get a view storage.
/// - Returns: The view storage.
func container() -> ViewStorage {
let container = content.storage()
let clamp: GTUI.Clamp = .init(container.view)
_ = clamp.maximumSize(maxSize)
return .init(clamp, content: [.mainContent: [container]])
}
}
extension View {
/// Set the view's maximal size.
/// - Parameter maxSize: The maximal size.
/// - Returns: A view.
public func frame(maxSize: Int? = nil) -> View {
Clamp(content: self, maxSize: maxSize ?? -1)
}
}

View File

@ -7,7 +7,7 @@
import GTUI
/// A widget which executes a custom code on the GTUI widget when being created.
/// A widget which executes a custom code on the GTUI widget when being created and updated.
struct InspectorWrapper: Widget {
/// The custom code to edit the widget.
@ -27,13 +27,14 @@ struct InspectorWrapper: Widget {
/// - Parameter storage: The content's storage.
func update(_ storage: ViewStorage) {
content.updateStorage(storage)
modify(storage.view)
}
}
extension View {
/// Modify a GTUI widget before being displayed.
/// Modify a GTUI widget before being displayed and when being updated.
/// - Parameter modify: Modify the widget.
/// - Returns: A view.
public func inspect(_ modify: @escaping (NativeWidgetPeer?) -> Void) -> View {
@ -86,13 +87,6 @@ extension View {
inspect { _ = $0?.frame(minWidth: minWidth, minHeight: minHeight) }
}
/// Set the view's maximal size.
/// - Parameter maxSize: The maximal size.
/// - Returns: A view.
public func frame(maxSize: Int? = nil) -> View {
inspect { _ = $0?.frame(maxSize: maxSize) }
}
/// Set the view's transition.
/// - Parameter transition: The transition.
/// - Returns: A view.

View File

@ -25,10 +25,15 @@ public struct StatusPage: Widget {
/// - icon: The icon.
/// - description: Additional details.
/// - content: Additional content.
public init(_ title: String, icon: Icon, description: String = "", @ViewBuilder content: () -> Body = { [] }) {
public init(
_ title: String,
icon: Icon? = nil,
description: String = "",
@ViewBuilder content: () -> Body = { [] }
) {
self.title = title
self.description = description
self.icon = icon
self.icon = icon ?? .custom(name: "")
self.content = content()
}

View File

@ -15,7 +15,7 @@ public struct Window: WindowScene {
/// The window's identifier.
public var id: String
/// The window's content.
var content: (GTUI.Window) -> Body
var content: (GTUIWindow) -> Body
/// Whether an instance of the window type should be opened when the app is starting up.
public var `open`: Int
@ -24,7 +24,7 @@ public struct Window: WindowScene {
/// - id: The identifier.
/// - open: The number of instances of the window type when the app is starting.
/// - content: The window's content.
public init(id: String, `open`: Int = 1, @ViewBuilder content: @escaping (GTUI.Window) -> Body) {
public init(id: String, `open`: Int = 1, @ViewBuilder content: @escaping (GTUIWindow) -> Body) {
self.content = content
self.id = id
self.open = open
@ -47,8 +47,8 @@ public struct Window: WindowScene {
/// Get the window.
/// - Parameter app: The application.
/// - Returns: The window.
func createGTUIWindow(app: GTUIApp) -> GTUI.Window {
let window = GTUI.Window(app: app)
func createGTUIWindow(app: GTUIApp) -> GTUIWindow {
let window = GTUIWindow(app: app)
window.show()
return window
}
@ -56,7 +56,7 @@ public struct Window: WindowScene {
/// Get the storage of the content view.
/// - Parameter window: The window.
/// - Returns: The storage of the content of the window.
func getViewStorage(window: GTUI.Window) -> ViewStorage {
func getViewStorage(window: GTUIWindow) -> ViewStorage {
let storage = content(window).widget().container()
window.setChild(storage.view)
return storage

View File

@ -8,28 +8,39 @@
// swiftlint:disable missing_docs
import Adwaita
import GTUI
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
}
}
VStack {
HStack {
CountButton(count: $count, icon: .goPrevious) { $0 -= 1 }
Text("\(count)")
.style("title-1")
.frame(minWidth: 100)
CountButton(count: $count, icon: .goNext) { $0 += 1 }
}
.halign(.center)
}
.valign(.center)
.padding()
}
@ViewBuilder private var description: Body {
Text("\(count)")
.style("title-1")
private struct CountButton: View {
@Binding var count: Int
var icon: Icon.DefaultIcon
var action: (inout Int) -> Void
var view: Body {
Button(icon: .default(icon: icon)) {
action(&count)
}
.style("circular")
}
}
}

View File

@ -8,7 +8,6 @@
// swiftlint:disable missing_docs implicitly_unwrapped_optional no_magic_numbers
import Adwaita
import GTUI
@main
struct Demo: App {
@ -21,22 +20,26 @@ struct Demo: App {
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)
}
HelperWindows()
}
struct HelperWindows: WindowSceneGroup {
var scene: Scene {
Window(id: "content", open: 0) { window in
WindowsDemo.WindowContent(window: window)
}
Window(id: "toolbar-demo", open: 0) { window in
ToolbarDemo.WindowContent(window: window)
}
}
}
struct DemoContent: View {
@State private var selection: Page = .welcome
var window: GTUI.Window
var window: GTUIWindow
var app: GTUIApp!
var view: Body {
@ -54,7 +57,16 @@ struct Demo: App {
}
.navigationTitle("Demo")
} content: {
selection.view(app: app)
StatusPage(
selection.label,
icon: selection.icon,
description: selection.description
) {
selection.view(app: app)
}
.topToolbar {
HeaderBar.empty()
}
}
.onAppear {
window.setDefaultSize(width: 650, height: 450)

39
Tests/DiceDemo.swift Normal file
View File

@ -0,0 +1,39 @@
//
// DiceDemo.swift
// Adwaita
//
// Created by david-swift on 12.10.23.
//
// swiftlint:disable missing_docs no_magic_numbers
import Adwaita
struct DiceDemo: View {
@State private var number: Int?
private var label: String {
if let number {
return "\(number)"
} else {
return "Roll the Dice!"
}
}
var view: Body {
VStack {
Button(label) {
number = .random(in: 1...6)
}
.style("pill")
.style("suggested-action")
.frame(maxSize: 100)
}
.valign(.center)
.padding()
}
}
// swiftlint:enable missing_docs no_magic_numbers

View File

@ -8,12 +8,16 @@
// swiftlint:disable missing_docs implicitly_unwrapped_optional
import Adwaita
import GTUI
enum Page: String, Identifiable, CaseIterable {
case welcome
case counter
case windows
case toolbar
case transition
case dice
var id: Self {
self
@ -23,15 +27,47 @@ enum Page: String, Identifiable, CaseIterable {
rawValue.capitalized
}
var icon: GTUI.Icon? {
switch self {
case .welcome:
return .default(icon: .gnomeAdwaita1Demo)
default:
return nil
}
}
var description: String {
switch self {
case .welcome:
return "This is a collection of examples for the Swift Adwaita package."
case .counter:
return "A simple sample view."
case .windows:
return "Showcase window management."
case .toolbar:
return "Toggle the bottom toolbar."
case .transition:
return "A slide transition between two views."
case .dice:
return "Roll the dice."
}
}
@ViewBuilder
func view(app: GTUIApp!) -> Body {
switch self {
case .welcome:
WelcomeDemo()
[]
case .counter:
CounterDemo()
case .windows:
WindowsDemo(app: app)
case .toolbar:
ToolbarDemo(app: app)
case .transition:
TransitionDemo()
case .dice:
DiceDemo()
}
}

68
Tests/ToolbarDemo.swift Normal file
View File

@ -0,0 +1,68 @@
//
// ToolbarDemo.swift
// Adwaita
//
// Created by david-swift on 12.10.23.
//
// swiftlint:disable missing_docs no_magic_numbers
import Adwaita
struct ToolbarDemo: View {
var app: GTUIApp
var view: Body {
VStack {
Button("View Demo") {
app.showWindow("toolbar-demo")
}
.style("suggested-action")
.frame(maxSize: 100)
}
}
struct WindowContent: View {
@State private var visible = false
@State private var moreContent = false
var window: GTUIWindow
var view: Body {
VStack {
Button("Toggle Toolbar") {
visible.toggle()
}
.style("suggested-action")
.frame(maxSize: 100)
.padding(15)
}
.valign(.center)
.bottomToolbar(visible: visible) {
HeaderBar(titleButtons: false) {
Button(icon: .default(icon: .audioInputMicrophone)) { }
} end: {
Button(icon: .default(icon: .userTrash)) { }
}
.headerBarTitle { }
}
.topToolbar {
HeaderBar.empty()
}
.onAppear {
window.setDefaultSize(width: 400, height: 250)
}
}
}
}
extension Int: Identifiable {
public var id: Self { self }
}
// swiftlint:enable missing_docs no_magic_numbers

View File

@ -0,0 +1,47 @@
//
// TransitionDemo.swift
// Adwaita
//
// Created by david-swift on 12.10.23.
//
// swiftlint:disable missing_docs no_magic_numbers
import Adwaita
struct TransitionDemo: View {
@State private var firstView = true
var view: Body {
VStack {
if firstView {
content("First View")
.transition(.slideDown)
.style("accent")
} else {
content("Second View")
.transition(.slideUp)
.style("success")
}
}
.style("card")
.frame(maxSize: 200)
.padding()
Button("Toggle View") {
firstView.toggle()
}
.style("pill")
.padding()
.frame(maxSize: 100)
}
private func content(_ text: String) -> View {
Text(text)
.style("title-2")
.padding()
}
}
// swiftlint:enable missing_docs no_magic_numbers

View File

@ -1,29 +0,0 @@
//
// 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

View File

@ -14,19 +14,39 @@ struct WindowsDemo: View {
var app: GTUIApp!
var view: Body {
VStack {
Button("Show Window") {
app.showWindow("content")
HStack {
VStack {
Button("Show Window") {
app.showWindow("content")
}
.hexpand()
Button("Add Window") {
app.addWindow("main")
}
.hexpand()
}
.valign(.center)
.style("linked")
.padding()
Button("Add Window") {
app.addWindow("main")
}
.padding(10, .horizontal.add(.bottom))
}
.topToolbar {
HeaderBar.empty()
.frame(maxSize: 100)
}
struct WindowContent: View {
var window: GTUIWindow
var view: Body {
Text("This window exists at most once.")
.padding()
.topToolbar {
HeaderBar.empty()
}
.onAppear {
window.setDefaultSize(width: 400, height: 250)
}
}
}
}