Add support for content dialogs
Some checks failed
Deploy Docs / publish (push) Waiting to run
SwiftLint / SwiftLint (push) Failing after 4s

This commit is contained in:
david-swift 2024-11-13 13:02:52 +01:00
parent 9f545190a1
commit 821ba1f006
3 changed files with 185 additions and 1 deletions

View File

@ -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<Bool>,
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>(
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<Data>(
_ 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) }
}
}

View File

@ -33,6 +33,7 @@ struct ContentView: View {
@State private var path: [NavigationSelection<NavigationItem>] = []
@State private var comboBoxSelection: NavigationItem = .items
@State private var switchState = false
@State private var dialog = false
var app: WinUIApp
var selectedItem: NavigationSelection<NavigationItem>? { 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.

View File

@ -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<Bool>,
title: String,
id: String = "",
@ViewBuilder body: () -> Body = { [] }
) -> ContentDialog {
.init(visible: visible, child: self, id: id, title: title, body: body)
}
}