portico/Sources/Portico/Extensions/Toast+Extras.swift

62 lines
2.9 KiB
Swift

import Adw
/// Converts `duration` to the whole seconds `adw_toast_set_timeout` expects.
///
/// A positive duration never rounds down to `0`, because `0` means "never
/// auto-dismiss" in libadwaita and silently turning a brief toast into a
/// permanent one would be worse than rounding up. `.zero` and negative
/// durations map to `0` deliberately.
///
/// - Parameter duration: The requested on-screen time.
/// - Returns: Whole seconds, clamped to at least `1` for any positive duration.
private func toastTimeoutSeconds(_ duration: Duration) -> UInt32 {
let components = duration.components
guard components.seconds > 0 || (components.seconds == 0 && components.attoseconds > 0)
else { return 0 }
let rounded = components.seconds + (components.attoseconds >= 500_000_000_000_000_000 ? 1 : 0)
return UInt32(clamping: max(1, rounded))
}
extension Adw.Toast {
/// Creates a fully configured toast in one expression.
///
/// ```swift
/// toasts.addToast(Toast(title: "File saved", buttonLabel: "Undo") { undo() })
/// ```
///
/// `onClick` has no default value, so the inherited `Toast(title:)`
/// initializer stays reachable and unambiguous for the bare case.
///
/// - Parameters:
/// - title: The toast text, interpreted as Pango markup unless `useMarkup`
/// is `false`.
/// - timeout: How long the toast stays on screen. `nil` keeps libadwaita's
/// default. `.zero` (or a negative duration) means the toast never
/// dismisses itself. `AdwToast` counts whole seconds, so the value is
/// rounded to the nearest second and any positive duration is clamped to
/// at least one second rather than collapsing to "never".
/// - priority: How the toast behaves when one is already displayed. `nil`
/// keeps libadwaita's default.
/// - buttonLabel: Label for the toast's action button. `nil` shows no button.
/// - useMarkup: Whether `title` is Pango markup. `nil` keeps libadwaita's
/// default.
/// - onClick: Runs when the action button is clicked. The
/// `::button-clicked` handler is owned by the toast and released with it,
/// so there is nothing to unregister - but avoid capturing the toast
/// itself, which would keep it alive forever.
public convenience init(
title: String,
timeout: Duration? = nil,
priority: ToastPriority? = nil,
buttonLabel: String? = nil,
useMarkup: Bool? = nil,
_ onClick: @escaping () -> Void
) {
self.init(title: title)
if let timeout { setTimeout(timeout: toastTimeoutSeconds(timeout)) }
if let priority { setPriority(priority: priority) }
if let buttonLabel { setButtonLabel(buttonLabel: buttonLabel) }
if let useMarkup { setUseMarkup(useMarkup: useMarkup) }
_ = connectButtonClicked { _ in onClick() }
}
}