diff --git a/Sources/Core/View/ContentDialog.swift b/Sources/Core/View/ContentDialog.swift new file mode 100644 index 0000000..68f6dea --- /dev/null +++ b/Sources/Core/View/ContentDialog.swift @@ -0,0 +1,164 @@ +// +// AlertDialog.swift +// Adwaita +// +// Created by david-swift on 12.11.24. +// + +import WinUI + +/// The content dialog widget. +public struct ContentDialog: WinUIWidget { + + /// Whether the dialog is visible. + @Meta.Binding var visible: Bool + /// An identifier used if multiple dialogs are on one view. + var id: String + /// The dialog's title. + var title: String + /// The body text. + var body: Body + /// The primary response. + var primary: (String, () -> Void)? + /// The secondary response. + var secondary: (String, () -> Void)? + /// The close response. + var close: (String, () -> Void)? + /// The child view. + var child: AnyView + + /// Initialize a content dialog wrapper. + /// - Parameters: + /// - visible: Whether the dialog is visible. + /// - child: The child view. + /// - id: A unique identifier for dialogs on the view. + /// - title: The title. + /// - body: The body. + public init( + visible: Meta.Binding, + child: AnyView, + id: String, + title: String, + @ViewBuilder body: () -> Body = { [] } + ) { + self._visible = visible + self.child = child + self.id = id + self.title = title + self.body = body() + } + + /// The view storage. + /// - Parameters: + /// - modifiers: Modify views before being updated. + /// - type: The view render data type. + /// - Returns: The view storage. + public func container( + data: WidgetData, + type: Data.Type + ) -> ViewStorage where Data: ViewRenderData { + let storage = child.storage(data: data, type: type) + let dialog = WinUI.ContentDialog() + storage.fields["dialog-object"] = dialog + let body = body.storage(data: data, type: type) + dialog.content = body.pointer + storage.content["body"] = [body] + dialog.closeButtonClick.addHandler { _, _ in + (storage.fields["close-action"] as? () -> Void)?() + } + dialog.primaryButtonClick.addHandler { _, _ in + (storage.fields["primary-action"] as? () -> Void)?() + } + dialog.secondaryButtonClick.addHandler { _, _ in + (storage.fields["secondary-action"] as? () -> Void)?() + } + update(storage, data: data, updateProperties: true, type: type) + return storage + } + + /// Update the stored content. + /// - Parameters: + /// - storage: The storage to update. + /// - modifiers: Modify views before being updated + /// - updateProperties: Whether to update the view's properties. + /// - type: The view render data type. + public func update( + _ storage: ViewStorage, + data: WidgetData, + updateProperties: Bool, + type: Data.Type + ) where Data: ViewRenderData { + child.updateStorage(storage, data: data, updateProperties: updateProperties, type: type) + if let body = storage.content["body"]?.first { + self.body.updateStorage(body, data: data, updateProperties: updateProperties, type: type) + } + guard updateProperties, let dialog = storage.fields["dialog-object"] as? WinUI.ContentDialog else { + return + } + if storage.fields["dialog-presented"] as? Bool == true { + if !visible { + try? dialog.hide() + } + updateDialogProperties(dialog: dialog, storage: storage) + storage.fields["dialog-presented"] = visible + } else if visible { + dialog.xamlRoot = (storage.pointer as? UIElement)?.xamlRoot + updateDialogProperties(dialog: dialog, storage: storage) + _ = try? dialog.showAsync() + } + } + + /// Update the dialog's properties. + /// - Parameters: + /// - dialog: The dialog object. + /// - storage: The view storage. + func updateDialogProperties(dialog: WinUI.ContentDialog, storage: ViewStorage) { + dialog.title = title + if let close { + dialog.closeButtonText = close.0 + storage.fields["close-action"] = close.1 + } + if let primary { + dialog.primaryButtonText = primary.0 + storage.fields["primary-action"] = primary.1 + } + if let secondary { + dialog.secondaryButtonText = secondary.0 + storage.fields["secondary-action"] = secondary.1 + } + } + + /// Set the close response. + /// - Parameters: + /// - title: The response. + /// - action: The action. + public func closeResponse( + _ title: String, + action: @escaping () -> Void + ) -> Self { + modify { $0.close = (title, action) } + } + + /// Set the primary response. + /// - Parameters: + /// - title: The response. + /// - action: The action. + public func primaryResponse( + _ title: String, + action: @escaping () -> Void + ) -> Self { + modify { $0.primary = (title, action) } + } + + /// Set the secondary response. + /// - Parameters: + /// - title: The response. + /// - action: The action. + public func secondaryResponse( + _ title: String, + action: @escaping () -> Void + ) -> Self { + modify { $0.secondary = (title, action) } + } + +} diff --git a/Sources/Demo/Demo.swift b/Sources/Demo/Demo.swift index 7bbd498..2fdeff4 100644 --- a/Sources/Demo/Demo.swift +++ b/Sources/Demo/Demo.swift @@ -33,6 +33,7 @@ struct ContentView: View { @State private var path: [NavigationSelection] = [] @State private var comboBoxSelection: NavigationItem = .items @State private var switchState = false + @State private var dialog = false var app: WinUIApp var selectedItem: NavigationSelection? { path.last } @@ -70,10 +71,13 @@ struct ContentView: View { } .header(settingsLabel: "Settings") { AppBarButton("Delete", icon: .systemIcon(unicode: "\u{E74D}")) { - print("Delete") + dialog = true } .visible(selectedItem != .settings) } + .contentDialog(visible: $dialog, title: "Sample Dialog") + .closeResponse("Close") { dialog = false } + .primaryResponse("Delete") { dialog = false } } /// The settings page. diff --git a/Sources/winui-swift/AnyView+.swift b/Sources/winui-swift/AnyView+.swift index 9f1ffe0..6a8b087 100644 --- a/Sources/winui-swift/AnyView+.swift +++ b/Sources/winui-swift/AnyView+.swift @@ -73,4 +73,20 @@ extension AnyView { Card { self } } + /// Add a content dialog. + /// - Parameters: + /// - visible: The dialog's visibility. + /// - title: The dialog's title. + /// - id: An optional id. Use this for multiple dialogs on one view. + /// - body: The dialog's content. + /// - Returns: The view. + public func contentDialog( + visible: Binding, + title: String, + id: String = "", + @ViewBuilder body: () -> Body = { [] } + ) -> ContentDialog { + .init(visible: visible, child: self, id: id, title: title, body: body) + } + }