From da08bc435cfb7444a2a5b0e0bdc8ec1cd3019e5c Mon Sep 17 00:00:00 2001 From: david-swift Date: Thu, 9 Nov 2023 20:09:21 +0100 Subject: [PATCH] Add support for modal windows --- .../Model/User Interface/App/GTUIApp.swift | 19 +++++-- .../User Interface/Window/WindowScene.swift | 2 + .../User Interface/Window/WindowStorage.swift | 2 + Sources/Adwaita/Window/Window.swift | 14 +++++ Tests/Demo.swift | 6 +++ Tests/OverlayWindowDemo.swift | 53 +++++++++++++++++++ Tests/Page.swift | 12 ++++- 7 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 Tests/OverlayWindowDemo.swift diff --git a/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift b/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift index e38b633..6c228e1 100644 --- a/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift +++ b/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift @@ -32,8 +32,9 @@ public class GTUIApp: Application { let body = body() for windowScene in body.scene.windows() { for _ in 0...updateViews() + if let window = body().scene.windows().last(where: { $0.id == id }) { + let window = window.createWindow(app: self) + sceneStorage.append(window) + setParentWindows() showWindow(id) } } + + /// Set the parents of every window having a parent window. + func setParentWindows() { + for window in sceneStorage { + if let parent = sceneStorage.first { $0.id == window.parentID } { + window.window.setParent(parent.window) + } + } + } } diff --git a/Sources/Adwaita/Model/User Interface/Window/WindowScene.swift b/Sources/Adwaita/Model/User Interface/Window/WindowScene.swift index 88f45ce..ccdb78a 100644 --- a/Sources/Adwaita/Model/User Interface/Window/WindowScene.swift +++ b/Sources/Adwaita/Model/User Interface/Window/WindowScene.swift @@ -12,6 +12,8 @@ public protocol WindowScene: WindowSceneGroup { /// The window type's identifier. var id: String { get } + /// The identifier of the window's parent window. + var parentID: String? { get set } /// The number of instances of the window at the startup. var `open`: Int { get } /// The keyboard shortcuts on the application's level. diff --git a/Sources/Adwaita/Model/User Interface/Window/WindowStorage.swift b/Sources/Adwaita/Model/User Interface/Window/WindowStorage.swift index b94c89d..4f3207c 100644 --- a/Sources/Adwaita/Model/User Interface/Window/WindowStorage.swift +++ b/Sources/Adwaita/Model/User Interface/Window/WindowStorage.swift @@ -12,6 +12,8 @@ public class WindowStorage { /// The window's identifier. public var id: String + /// The identifier of the window's parent window. + public var parentID: String? /// Whether the reference to the window should disappear in the next update. public var destroy = false /// The GTUI window. diff --git a/Sources/Adwaita/Window/Window.swift b/Sources/Adwaita/Window/Window.swift index 3ad235f..1cc9f9b 100644 --- a/Sources/Adwaita/Window/Window.swift +++ b/Sources/Adwaita/Window/Window.swift @@ -18,6 +18,8 @@ public struct Window: WindowScene { var content: (GTUIApplicationWindow) -> Body /// Whether an instance of the window type should be opened when the app is starting up. public var `open`: Int + /// The identifier of the window's parent. + public var parentID: String? /// The keyboard shortcuts. var shortcuts: [String: (GTUIApplicationWindow) -> Void] = [:] /// The keyboard shortcuts on the app level. @@ -45,6 +47,7 @@ public struct Window: WindowScene { windowStorage.destroy = true return false } + windowStorage.parentID = parentID return windowStorage } @@ -80,6 +83,17 @@ public struct Window: WindowScene { } } + /// Add windows that overlay the last instance of this window if presented. + /// - Parameter windows: The windows. + /// - Returns: The new windows and this window. + public func overlay(@SceneBuilder windows: () -> [WindowSceneGroup]) -> [WindowScene] { + windows().windows().map { window in + var newWindow = window + newWindow.parentID = id + return newWindow + } + [self] + } + /// Add a keyboard shortcut. /// - Parameters: /// - shortcut: The keyboard shortcut. diff --git a/Tests/Demo.swift b/Tests/Demo.swift index b0c9deb..5ba6a28 100644 --- a/Tests/Demo.swift +++ b/Tests/Demo.swift @@ -20,6 +20,12 @@ struct Demo: App { Window(id: "main") { window in DemoContent(window: window, app: app) } + .overlay { + Window(id: "overlay", open: 0) { window in + OverlayWindowDemo.WindowContent(window: window) + } + .keyboardShortcut("Escape") { $0.close() } + } HelperWindows() } diff --git a/Tests/OverlayWindowDemo.swift b/Tests/OverlayWindowDemo.swift new file mode 100644 index 0000000..a079dea --- /dev/null +++ b/Tests/OverlayWindowDemo.swift @@ -0,0 +1,53 @@ +// +// OverlayWindowDemo.swift +// Adwaita +// +// Created by david-swift on 09.11.23. +// + +// swiftlint:disable missing_docs implicitly_unwrapped_optional no_magic_numbers + +import Adwaita + +struct OverlayWindowDemo: View { + + var app: GTUIApp! + + var view: Body { + VStack { + Button("Show Window") { + app.showWindow("overlay") + } + .style("pill") + .frame(maxSize: 100) + .padding() + } + } + + struct WindowContent: View { + + var window: GTUIWindow + + var view: Body { + VStack { + Button("Close Window") { + window.close() + } + .style("pill") + .padding() + .frame(maxSize: 100) + } + .valign(.center) + .topToolbar { + HeaderBar.empty() + } + .onAppear { + window.setDefaultSize(width: 300, height: 200) + } + } + + } + +} + +// swiftlint:enable missing_docs implicitly_unwrapped_optional no_magic_numbers diff --git a/Tests/Page.swift b/Tests/Page.swift index 17b256e..a2e29e7 100644 --- a/Tests/Page.swift +++ b/Tests/Page.swift @@ -18,13 +18,19 @@ enum Page: String, Identifiable, CaseIterable { case toolbar case transition case dice + case overlayWindow var id: Self { self } var label: String { - rawValue.capitalized + switch self { + case .overlayWindow: + return "Overlay Window" + default: + return rawValue.capitalized + } } var icon: GTUI.Icon? { @@ -50,6 +56,8 @@ enum Page: String, Identifiable, CaseIterable { return "A slide transition between two views." case .dice: return "Roll the dice." + case .overlayWindow: + return "A window on top of another window." } } @@ -68,6 +76,8 @@ enum Page: String, Identifiable, CaseIterable { TransitionDemo() case .dice: DiceDemo() + case .overlayWindow: + OverlayWindowDemo(app: app) } }