portico/Sources/Portico/Presentation/View+Dialog.swift

74 lines
3.9 KiB
Swift

import Adw
import Gtk
extension View {
@MainActor private func mountDialog(_ view: Dialog, _ ctx: MountContext) -> Adw.Dialog {
let widget = AnyView(view).makeWidget(ctx)
guard let dialog = widget as? Adw.Dialog else {
preconditionFailure("Dialog mounted a \(type(of: widget)); expected Adw.Dialog.")
}
return dialog
}
/// Presents a configurable `Adw.Dialog` over this view while `isPresented` is true.
///
/// Each property accepts a fixed value or a live `Binding`. Width and height use `Int32`;
/// integer literals are accepted. Use `DialogPresentationMode.bottomSheet` explicitly for
/// `presentationMode`. Setting `canClose` to false vetoes user dismissal.
///
/// - Parameters:
/// - isPresented: Two-way control over the dialog visibility.
/// - title: Optional dialog title.
/// - contentWidth: Optional content width in pixels.
/// - contentHeight: Optional content height in pixels.
/// - presentationMode: Optional dialog presentation mode.
/// - canClose: Whether user dismissal is allowed.
/// - followsContentSize: Whether the dialog follows its content size.
/// - content: Builder whose first view becomes the dialog child.
/// - Returns: This view, unchanged, with the dialog presentation attached.
public func dialog(
isPresented: Binding<Bool>,
title: (any DialogPropertySource<String>)? = nil,
contentWidth: (any DialogPropertySource<Int32>)? = nil,
contentHeight: (any DialogPropertySource<Int32>)? = nil,
presentationMode: (any DialogPropertySource<DialogPresentationMode>)? = nil,
canClose: (any DialogPropertySource<Bool>)? = nil,
followsContentSize: (any DialogPropertySource<Bool>)? = nil,
@ViewBuilder content: () -> [AnyView]
) -> AnyView {
let contentViews = content()
var configured = Dialog()
configured = appliedDialogProperty(title, to: configured, constant: { $0.title($1) }, binding: { $0.title($1) })
configured = appliedDialogProperty(contentWidth, to: configured, constant: { $0.contentWidth($1) }, binding: { $0.contentWidth($1) })
configured = appliedDialogProperty(contentHeight, to: configured, constant: { $0.contentHeight($1) }, binding: { $0.contentHeight($1) })
configured = appliedDialogProperty(presentationMode, to: configured, constant: { $0.presentationMode($1) }, binding: { $0.presentationMode($1) })
configured = appliedDialogProperty(canClose, to: configured, constant: { $0.canClose($1) }, binding: { $0.canClose($1) })
configured = appliedDialogProperty(followsContentSize, to: configured, constant: { $0.followsContentSize($1) }, binding: { $0.followsContentSize($1) })
return AnyView(makeWidget: { ctx in
let host = AnyView(self).makeWidget(ctx)
let dialog = mountDialog(configured, ctx)
if let first = contentViews.first {
dialog.setChild(child: first.makeWidget(ctx))
}
bindDialogPresentation(dialog, isPresented: isPresented, host: host, ctx: ctx)
return host
})
}
/// Presents a pre-configured `Dialog` over this view while `isPresented` is true.
///
/// Use this overload for widget-slot properties such as `defaultWidget` and `focusWidget`.
/// - Parameters:
/// - isPresented: Two-way control over the dialog visibility.
/// - dialog: The configured dialog view to mount.
/// - Returns: This view, unchanged, with the dialog presentation attached.
public func dialog(isPresented: Binding<Bool>, _ dialog: Dialog) -> AnyView {
AnyView(makeWidget: { ctx in
let host = AnyView(self).makeWidget(ctx)
let mounted = mountDialog(dialog, ctx)
bindDialogPresentation(mounted, isPresented: isPresented, host: host, ctx: ctx)
return host
})
}
}