From 8b4ce2bede8b1b875514cd4b20d3f5cb5c43993d Mon Sep 17 00:00:00 2001 From: david-swift Date: Thu, 21 Mar 2024 09:13:08 +0100 Subject: [PATCH] Add support for the about dialog --- .../Adwaita/View/Dialogs/AboutDialog.swift | 120 ++++++++++++++++++ Tests/Demo.swift | 18 ++- 2 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 Sources/Adwaita/View/Dialogs/AboutDialog.swift diff --git a/Sources/Adwaita/View/Dialogs/AboutDialog.swift b/Sources/Adwaita/View/Dialogs/AboutDialog.swift new file mode 100644 index 0000000..e2088e6 --- /dev/null +++ b/Sources/Adwaita/View/Dialogs/AboutDialog.swift @@ -0,0 +1,120 @@ +// +// AboutDialog.swift +// Adwaita +// +// Created by david-swift on 21.03.24. +// + +import CAdw +import Foundation + +/// The about dialog widget. +struct AboutDialog: Widget { + + /// Whether the dialog is visible. + @Binding var visible: Bool + /// The wrapped view. + var child: View + + /// The app's name. + var appName: String? + /// The developer's name. + var developer: String? + /// The app version. + var version: String? + /// The app icon. + var icon: Icon? + /// The app's website. + var website: URL? + /// The link for opening issues. + var issues: URL? + + /// The ID for the dialog's storage. + let dialogID = "dialog" + + /// Get the container of the child. + /// - Parameter modifiers: Modify views before being updated. + /// - Returns: The view storage. + func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = child.storage(modifiers: modifiers) + update(storage, modifiers: modifiers, updateProperties: true) + return storage + } + + /// Update the view storage of the child, dialog, and dialog content. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: Modify views before being updated. + /// - updateProperties: Whether to update properties. + func update(_ storage: ViewStorage, modifiers: [(View) -> View], updateProperties: Bool) { + child.widget(modifiers: modifiers).update(storage, modifiers: modifiers, updateProperties: updateProperties) + guard updateProperties else { + return + } + if visible { + if storage.content[dialogID]?.first == nil { + createDialog(storage: storage, modifiers: modifiers) + adw_dialog_present(storage.content[dialogID]?.first?.pointer?.cast(), storage.pointer?.cast()) + } + let dialog = storage.content[dialogID]?.first?.pointer + adw_about_dialog_set_application_name(dialog, appName) + adw_about_dialog_set_developer_name(dialog, developer) + adw_about_dialog_set_version(dialog, version) + adw_about_dialog_set_application_icon(dialog, icon?.string) + adw_about_dialog_set_website(dialog, website?.absoluteString) + adw_about_dialog_set_support_url(dialog, issues?.absoluteString) + adw_dialog_set_content_height(dialog?.cast(), -1) + } else { + if storage.content[dialogID]?.first != nil { + adw_dialog_close(storage.content[dialogID]?.first?.pointer?.cast()) + } + } + } + + /// Create a new instance of the dialog. + /// - Parameters: + /// - storage: The wrapped view's storage. + /// - modifiers: The view modifiers. + func createDialog(storage: ViewStorage, modifiers: [(View) -> View]) { + let pointer = adw_about_dialog_new() + let dialog = ViewStorage(pointer?.opaque()) + storage.content[dialogID] = [dialog] + dialog.connectSignal(name: "closed") { + storage.content[dialogID] = [] + if visible { + visible = false + } + } + } + +} + +extension View { + + /// Add a dialog to the parent window. + /// - Parameters: + /// - visible: Whether the dialog is presented. + /// - title: The dialog's title. + /// - content: The dialog's content. + public func aboutDialog( + visible: Binding, + app: String? = nil, + developer: String? = nil, + version: String? = nil, + icon: Icon? = nil, + website: URL? = nil, + issues: URL? = nil + ) -> View { + AboutDialog( + visible: visible, + child: self, + appName: app, + developer: developer, + version: version, + icon: icon, + website: website, + issues: issues + ) + } + +} diff --git a/Tests/Demo.swift b/Tests/Demo.swift index a698cea..d486a95 100644 --- a/Tests/Demo.swift +++ b/Tests/Demo.swift @@ -19,12 +19,6 @@ struct Demo: App { Window(id: "main") { window in DemoContent(window: window, app: app) } - .overlay { - AboutWindow(id: "about", appName: "Demo", developer: "david-swift", version: "Test") - .icon(.default(icon: .applicationXExecutable)) - .website(.init(string: "https://david-swift.gitbook.io/adwaita")) - .issues(.init(string: "https://github.com/AparokshaUI/adwaita-swift/issues")) - } HelperWindows() } @@ -78,6 +72,7 @@ struct Demo: App { private var height = 450 @State("maximized") private var maximized = false + @State private var about = false var window: GTUIApplicationWindow var app: GTUIApp! @@ -130,6 +125,15 @@ struct Demo: App { } .toast("This is a toast!", signal: toast) } + .aboutDialog( + visible: $about, + app: "Demo", + developer: "david-swift", + version: "Test", + icon: .default(icon: .applicationXExecutable), + website: .init(string: "https://david-swift.gitbook.io/adwaita"), + issues: .init(string: "https://github.com/AparokshaUI/adwaita-swift/issues") + ) } var menu: View { @@ -143,7 +147,7 @@ struct Demo: App { } .keyboardShortcut("w".ctrl()) MenuSection { - MenuButton("About") { app.addWindow("about", parent: window) } + MenuButton("About") { about = true } MenuButton("Quit", window: false) { app.quit() } .keyboardShortcut("q".ctrl()) }