diff --git a/Documentation/Reference/README.md b/Documentation/Reference/README.md index bf5a34c..fa93be1 100644 --- a/Documentation/Reference/README.md +++ b/Documentation/Reference/README.md @@ -13,6 +13,7 @@ ## Structs +- [AppearObserver](structs/AppearObserver.md) - [Binding](structs/Binding.md) - [Button](structs/Button.md) - [Clamp](structs/Clamp.md) @@ -28,11 +29,13 @@ - [ModifierStopper](structs/ModifierStopper.md) - [NavigationSplitView](structs/NavigationSplitView.md) - [ScrollView](structs/ScrollView.md) +- [Signal](structs/Signal.md) - [State](structs/State.md) - [StateWrapper](structs/StateWrapper.md) - [StatusPage](structs/StatusPage.md) - [Submenu](structs/Submenu.md) - [Text](structs/Text.md) +- [ToastOverlay](structs/ToastOverlay.md) - [ToolbarView](structs/ToolbarView.md) - [VStack](structs/VStack.md) - [Window](structs/Window.md) diff --git a/Documentation/Reference/extensions/View.md b/Documentation/Reference/extensions/View.md index 3322c3e..6d2be92 100644 --- a/Documentation/Reference/extensions/View.md +++ b/Documentation/Reference/extensions/View.md @@ -24,6 +24,12 @@ Get a storage. ### `getModified(modifiers:)` +### `onAppear(_:)` + +Run a function when the view appears for the first time. +- Parameter closure: The function. +- Returns: A view. + ### `frame(maxSize:)` Set the view's maximal size. @@ -113,6 +119,24 @@ Run a function when the view gets an update. Remove all of the content modifiers for the wrapped views. - Returns: A view. +### `toast(_:signal:)` + +Present a toast when the signal gets activated. +- Parameters: + - title: The title of the toast. + - signal: The signal which activates the presentation of a toast. +- Returns: A view. + +### `toast(_:signal:button:handler:)` + +Present a toast with a button when the signal gets activated. +- Parameters: + - title: The title of the toast. + - signal: The signal which activates the presentation of a toast. + - button: The button's label. + - handler: The handler for the button. +- Returns: A view. + ### `topToolbar(visible:_:)` Add a top toolbar to the view. diff --git a/Documentation/Reference/structs/AppearObserver.md b/Documentation/Reference/structs/AppearObserver.md new file mode 100644 index 0000000..5c9b85f --- /dev/null +++ b/Documentation/Reference/structs/AppearObserver.md @@ -0,0 +1,28 @@ +**STRUCT** + +# `AppearObserver` + +A widget which executes a custom code when being rendered for the first time. + +## Properties +### `onAppear` + +The function. + +### `content` + +The content. + +## Methods +### `container(modifiers:)` + +Get the content's container. +- Parameter modifiers: Modify views before being updated. +- Returns: The content's container. + +### `update(_:modifiers:)` + +Update the content. +- Parameters: + - storage: The content's storage. + - modifiers: Modify views before being updated. diff --git a/Documentation/Reference/structs/Signal.md b/Documentation/Reference/structs/Signal.md new file mode 100644 index 0000000..cb4a10d --- /dev/null +++ b/Documentation/Reference/structs/Signal.md @@ -0,0 +1,23 @@ +**STRUCT** + +# `Signal` + +A type that signalizes an action. + +## Properties +### `boolean` + +An action is signalized by toggling a boolean to `true` and back to `false`. + +### `update` + +Whether the action has caused an update. + +## Methods +### `init()` + +Initialize a signal. + +### `signal()` + +Activate a signal. diff --git a/Documentation/Reference/structs/ToastOverlay.md b/Documentation/Reference/structs/ToastOverlay.md new file mode 100644 index 0000000..13161cf --- /dev/null +++ b/Documentation/Reference/structs/ToastOverlay.md @@ -0,0 +1,41 @@ +**STRUCT** + +# `ToastOverlay` + +A wrapper around a view presenting toasts. + +## Properties +### `signal` + +The signal for showing the toast./// Present a toast when the signal gets activated. + +### `child` + +The child view. + +### `title` + +The title of the toast. + +### `button` + +Information about the button if available (label and handler). + +## Methods +### `container(modifiers:)` + +Get the overlay's view storage. +- Parameter modifiers: The view modifiers. +- Returns: The view storage. + +### `update(_:modifiers:)` + +Update the overlay's view storage. +- Parameters: + - storage: The view storage. + - modifiers: The view modifiers. + +### `setVisibility(_:)` + +Add a toast if the signal is active. +- Parameter overlay: The toast overlay. diff --git a/Icons/Demo.png b/Icons/Demo.png index 77ac2c2..b3f1a90 100644 Binary files a/Icons/Demo.png and b/Icons/Demo.png differ diff --git a/README.md b/README.md index 6470205..8c7526f 100644 --- a/README.md +++ b/README.md @@ -86,25 +86,27 @@ If you want to use _Adwaita_ in a project, but there are widgets missing, open a ### View Modifiers -| Syntax | Description | -| ---------------------------- | --------------------------------------------------------------------------------------- | -| `inspect(_:)` | Edit the underlying [Libadwaita][10] widget. | -| `padding(_:_:)` | Add empty space around a view. | -| `hexpand(_:)` | Enable or disable the horizontal 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(maxSize:)` | Set the view’s maximal size. | -| `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. | -| `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. | -| `modifyContent(_:modify:)` | Replace all occurrences of a certain view type with another view. | -| `stopModifiers()` | Ignore all the `modifyContent(_:modify:)` modifiers from higher above in the view tree. | +| Syntax | Description | +| --------------------------------- | --------------------------------------------------------------------------------------- | +| `inspect(_:)` | Edit the underlying [Libadwaita][10] widget. | +| `padding(_:_:)` | Add empty space around a view. | +| `hexpand(_:)` | Enable or disable the horizontal 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(maxSize:)` | Set the view’s maximal size. | +| `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. | +| `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. | +| `modifyContent(_:modify:)` | Replace all occurrences of a certain view type with another view. | +| `stopModifiers()` | Ignore all the `modifyContent(_:modify:)` modifiers from higher above in the view tree. | +| `toast(_:signal:)` | Show a toast on top of the view whenever the signal gets activated. | +| `toast(_:signal:button:handler:)` | Show a toast with a button on top of the view whenever the signal gets activated. | ### `Button` Modifiers | Syntax | Description | diff --git a/Sources/Adwaita/Model/Data Flow/Signal.swift b/Sources/Adwaita/Model/Data Flow/Signal.swift new file mode 100644 index 0000000..ebcc2da --- /dev/null +++ b/Sources/Adwaita/Model/Data Flow/Signal.swift @@ -0,0 +1,26 @@ +// +// Signal.swift +// Adwaita +// +// Created by david-swift on 30.11.23. +// + +/// A type that signalizes an action. +public struct Signal { + + /// An action is signalized by toggling a boolean to `true` and back to `false`. + @State var boolean = false + + /// Whether the action has caused an update. + public var update: Bool { boolean } + + /// Initialize a signal. + public init() { } + + /// Activate a signal. + public func signal() { + boolean = true + boolean = false + } + +} diff --git a/Sources/Adwaita/View/Modifiers/ToastOverlay.swift b/Sources/Adwaita/View/Modifiers/ToastOverlay.swift new file mode 100644 index 0000000..5266519 --- /dev/null +++ b/Sources/Adwaita/View/Modifiers/ToastOverlay.swift @@ -0,0 +1,83 @@ +// +// ToastOverlay.swift +// Adwaita +// +// Created by david-swift on 30.11.23. +// + +import Libadwaita + +/// A wrapper around a view presenting toasts. +struct ToastOverlay: Widget { + + /// The signal for showing the toast./// Present a toast when the signal gets activated. + var signal: Signal + /// The child view. + var child: View + /// The title of the toast. + var title: String + /// Information about the button if available (label and handler). + var button: (String, () -> Void)? + + /// Get the overlay's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + func container(modifiers: [(View) -> View]) -> ViewStorage { + let contentStorage = child.widget(modifiers: modifiers).storage(modifiers: modifiers) + let overlay = Libadwaita.ToastOverlay(contentStorage.view) + setVisibility(overlay) + return .init(overlay, content: [.mainContent: [contentStorage]]) + } + + /// Update the overlay's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let overlay = storage.view as? Libadwaita.ToastOverlay { + setVisibility(overlay) + } + if let storage = storage.content[.mainContent]?.first { + child.widget(modifiers: modifiers).update(storage, modifiers: modifiers) + } + } + + /// Add a toast if the signal is active. + /// - Parameter overlay: The toast overlay. + func setVisibility(_ overlay: Libadwaita.ToastOverlay) { + if signal.update { + let toast = Toast(title) + if let button { + _ = toast + .buttonLabel(button.0) + .buttonHandler(button.1) + } + overlay.addToast(toast) + } + } + +} + +extension View { + + /// Present a toast when the signal gets activated. + /// - Parameters: + /// - title: The title of the toast. + /// - signal: The signal which activates the presentation of a toast. + /// - Returns: A view. + public func toast(_ title: String, signal: Signal) -> View { + ToastOverlay(signal: signal, child: self, title: title) + } + + /// Present a toast with a button when the signal gets activated. + /// - Parameters: + /// - title: The title of the toast. + /// - signal: The signal which activates the presentation of a toast. + /// - button: The button's label. + /// - handler: The handler for the button. + /// - Returns: A view. + public func toast(_ title: String, signal: Signal, button: String, handler: @escaping () -> Void) -> View { + ToastOverlay(signal: signal, child: self, title: title, button: (button, handler)) + } + +} diff --git a/Tests/Demo.swift b/Tests/Demo.swift index b0b3142..8346fab 100644 --- a/Tests/Demo.swift +++ b/Tests/Demo.swift @@ -46,7 +46,8 @@ struct Demo: App { struct DemoContent: View { - @State private var selection: Page = .transition + @State private var selection: Page = .welcome + @State private var toast: Signal = .init() var window: GTUIApplicationWindow var app: GTUIApp! @@ -87,11 +88,12 @@ struct Demo: App { icon: selection.icon, description: selection.description ) { - selection.view(app: app) + selection.view(app: app, toast: toast) } .topToolbar { HeaderBar.empty() } + .toast("This is a toast!", signal: toast) } .onAppear { window.setDefaultSize(width: 650, height: 450) diff --git a/Tests/Page.swift b/Tests/Page.swift index 0bbd06b..86aa7c6 100644 --- a/Tests/Page.swift +++ b/Tests/Page.swift @@ -19,6 +19,7 @@ enum Page: String, Identifiable, CaseIterable { case transition case dice case overlayWindow + case toast var id: Self { self @@ -36,7 +37,7 @@ enum Page: String, Identifiable, CaseIterable { var icon: Libadwaita.Icon? { switch self { case .welcome: - return .default(icon: .gnomeAdwaita1Demo) + return .default(icon: .emojiNature) default: return nil } @@ -58,11 +59,13 @@ enum Page: String, Identifiable, CaseIterable { return "Roll the dice." case .overlayWindow: return "A window on top of another window." + case .toast: + return "Show a notification inside of your app." } } @ViewBuilder - func view(app: GTUIApp!) -> Body { + func view(app: GTUIApp!, toast: Signal) -> Body { switch self { case .welcome: [] @@ -78,6 +81,8 @@ enum Page: String, Identifiable, CaseIterable { DiceDemo() case .overlayWindow: OverlayWindowDemo(app: app) + case .toast: + ToastDemo(toast: toast) } } diff --git a/Tests/ToastDemo.swift b/Tests/ToastDemo.swift new file mode 100644 index 0000000..12ce935 --- /dev/null +++ b/Tests/ToastDemo.swift @@ -0,0 +1,28 @@ +// +// ToastDemo.swift +// Adwaita +// +// Created by david-swift on 30.11.23. +// + +// swiftlint:disable missing_docs + +import Adwaita + +struct ToastDemo: View { + + var toast: Signal + + var view: Body { + VStack { + Button("Add Toast") { + toast.signal() + } + .style("suggested-action") + .frame(maxSize: 100) + } + } + +} + +// swiftlint:enable missing_docs