268 lines
9.8 KiB
Swift
268 lines
9.8 KiB
Swift
import Adw
|
|
import GObject
|
|
import Gtk
|
|
|
|
/// A typed property setter installed on an ``Adw.Breakpoint``.
|
|
///
|
|
/// The target is resolved from a mounted ``WidgetRef`` or its projected binding.
|
|
/// Libadwaita restores the target property when the breakpoint is unapplied.
|
|
public struct Setter {
|
|
fileprivate let target: () -> GLibObject?
|
|
fileprivate let property: String
|
|
fileprivate let addTo: (Adw.Breakpoint, GLibObject, String) -> Void
|
|
|
|
private init(
|
|
target: @escaping () -> GLibObject?,
|
|
property: String,
|
|
addTo: @escaping (Adw.Breakpoint, GLibObject, String) -> Void
|
|
) {
|
|
self.target = target
|
|
self.property = property
|
|
self.addTo = addTo
|
|
}
|
|
|
|
/// Creates a boolean setter targeting a widget reference.
|
|
public init<W: Gtk.Widget>(_ ref: WidgetRef<W>, _ property: String, bool: Bool) {
|
|
self.init(
|
|
target: { ref.projectedValue.untrackedValue },
|
|
property: property,
|
|
addTo: { breakpoint, object, property in
|
|
breakpoint.addSetter(object: object, property: property, bool: bool)
|
|
}
|
|
)
|
|
}
|
|
|
|
/// Creates a boolean setter targeting a widget binding.
|
|
public init<W: Gtk.Widget>(_ ref: Binding<W?>, _ property: String, bool: Bool) {
|
|
self.init(
|
|
target: { ref.untrackedValue },
|
|
property: property,
|
|
addTo: { breakpoint, object, property in
|
|
breakpoint.addSetter(object: object, property: property, bool: bool)
|
|
}
|
|
)
|
|
}
|
|
|
|
/// Creates a string setter targeting a widget reference.
|
|
public init<W: Gtk.Widget>(_ ref: WidgetRef<W>, _ property: String, string: String) {
|
|
self.init(
|
|
target: { ref.projectedValue.untrackedValue },
|
|
property: property,
|
|
addTo: { breakpoint, object, property in
|
|
breakpoint.addSetter(object: object, property: property, string: string)
|
|
}
|
|
)
|
|
}
|
|
|
|
/// Creates a string setter targeting a widget binding.
|
|
public init<W: Gtk.Widget>(_ ref: Binding<W?>, _ property: String, string: String) {
|
|
self.init(
|
|
target: { ref.untrackedValue },
|
|
property: property,
|
|
addTo: { breakpoint, object, property in
|
|
breakpoint.addSetter(object: object, property: property, string: string)
|
|
}
|
|
)
|
|
}
|
|
|
|
/// Creates a signed integer setter targeting a widget reference.
|
|
public init<W: Gtk.Widget>(_ ref: WidgetRef<W>, _ property: String, int: Int32) {
|
|
self.init(
|
|
target: { ref.projectedValue.untrackedValue },
|
|
property: property,
|
|
addTo: { breakpoint, object, property in
|
|
breakpoint.addSetter(object: object, property: property, int: int)
|
|
}
|
|
)
|
|
}
|
|
|
|
/// Creates an unsigned integer setter targeting a widget reference.
|
|
public init<W: Gtk.Widget>(_ ref: WidgetRef<W>, _ property: String, uint: UInt32) {
|
|
self.init(
|
|
target: { ref.projectedValue.untrackedValue },
|
|
property: property,
|
|
addTo: { breakpoint, object, property in
|
|
breakpoint.addSetter(object: object, property: property, uint: uint)
|
|
}
|
|
)
|
|
}
|
|
|
|
/// Creates a double setter targeting a widget reference.
|
|
public init<W: Gtk.Widget>(_ ref: WidgetRef<W>, _ property: String, double: Double) {
|
|
self.init(
|
|
target: { ref.projectedValue.untrackedValue },
|
|
property: property,
|
|
addTo: { breakpoint, object, property in
|
|
breakpoint.addSetter(object: object, property: property, double: double)
|
|
}
|
|
)
|
|
}
|
|
|
|
/// Creates an object setter targeting a widget reference.
|
|
public init<W: Gtk.Widget>(_ ref: WidgetRef<W>, _ property: String, widget: GLibObject?) {
|
|
self.init(
|
|
target: { ref.projectedValue.untrackedValue },
|
|
property: property,
|
|
addTo: { breakpoint, object, property in
|
|
breakpoint.addSetter(object: object, property: property, widget: widget)
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Collects typed setters in a breakpoint modifier closure.
|
|
@resultBuilder
|
|
public struct BreakpointBuilder {
|
|
/// Combines the setters in a single builder block.
|
|
public static func buildBlock(_ components: [Setter]...) -> [Setter] {
|
|
components.flatMap { $0 }
|
|
}
|
|
|
|
/// Lifts a single setter into the builder's component type.
|
|
public static func buildExpression(_ setter: Setter) -> [Setter] {
|
|
[setter]
|
|
}
|
|
|
|
/// Combines setters produced by a loop.
|
|
public static func buildArray(_ components: [[Setter]]) -> [Setter] {
|
|
components.flatMap { $0 }
|
|
}
|
|
|
|
/// Handles an optional setter branch.
|
|
public static func buildOptional(_ component: [Setter]?) -> [Setter] {
|
|
component ?? []
|
|
}
|
|
|
|
/// Selects the first conditional branch.
|
|
public static func buildEither(first component: [Setter]) -> [Setter] {
|
|
component
|
|
}
|
|
|
|
/// Selects the second conditional branch.
|
|
public static func buildEither(second component: [Setter]) -> [Setter] {
|
|
component
|
|
}
|
|
}
|
|
|
|
/// Context for registering breakpoint apply and unapply handlers.
|
|
public struct BreakpointConfiguration {
|
|
/// The breakpoint being configured.
|
|
public let breakpoint: Adw.Breakpoint
|
|
let registry: NodeRegistry
|
|
|
|
init(breakpoint: Adw.Breakpoint, registry: NodeRegistry) {
|
|
self.breakpoint = breakpoint
|
|
self.registry = registry
|
|
}
|
|
|
|
/// Registers a handler that runs after breakpoint setters are applied.
|
|
public func onApply(_ handler: @escaping () -> Void) {
|
|
registry.add(breakpoint.connectApply { _ in handler() })
|
|
}
|
|
|
|
/// Registers a handler that runs before breakpoint setters are reset.
|
|
public func onUnapply(_ handler: @escaping () -> Void) {
|
|
registry.add(breakpoint.connectUnapply { _ in handler() })
|
|
}
|
|
}
|
|
|
|
extension WidgetView where Target: Adw.BreakpointBin {
|
|
/// Adds a breakpoint that writes `true` while its condition is active.
|
|
public func breakpoint(_ condition: Condition, isActive: Binding<Bool>) -> Self {
|
|
appending { widget, context in
|
|
let breakpoint = Adw.Breakpoint(condition: condition.condition)
|
|
context.registry.add(breakpoint.connectApply { _ in
|
|
isActive.wrappedValue = true
|
|
})
|
|
context.registry.add(breakpoint.connectUnapply { _ in
|
|
isActive.wrappedValue = false
|
|
})
|
|
widget.addBreakpoint(breakpoint: breakpoint)
|
|
}
|
|
}
|
|
|
|
/// Adds a breakpoint from a libadwaita condition string.
|
|
public func breakpoint(_ condition: String, isActive: Binding<Bool>) -> Self {
|
|
breakpoint(Condition(stringLiteral: condition), isActive: isActive)
|
|
}
|
|
|
|
/// Adds a breakpoint that identifies its matching layout value.
|
|
public func breakpoint<Layout: Equatable>(
|
|
_ condition: Condition,
|
|
isActive: Binding<Layout>,
|
|
matches: Layout
|
|
) -> Self {
|
|
appending { widget, context in
|
|
let breakpoint = Adw.Breakpoint(condition: condition.condition)
|
|
context.registry.add(breakpoint.connectApply { _ in
|
|
isActive.wrappedValue = matches
|
|
})
|
|
context.registry.add(breakpoint.connectUnapply { _ in
|
|
// A later breakpoint may already have replaced this value.
|
|
_ = isActive.untrackedValue == matches
|
|
})
|
|
widget.addBreakpoint(breakpoint: breakpoint)
|
|
}
|
|
}
|
|
|
|
/// Adds a breakpoint with typed widget-property setters.
|
|
public func breakpoint(
|
|
_ condition: Condition,
|
|
@BreakpointBuilder _ setters: () -> [Setter]
|
|
) -> Self {
|
|
let setterValues = setters()
|
|
return appending { widget, context in
|
|
let breakpoint = Adw.Breakpoint(condition: condition.condition)
|
|
for setter in setterValues {
|
|
guard let target = setter.target() else {
|
|
preconditionFailure(
|
|
"Setter target \(setter.property): WidgetRef is nil. "
|
|
+ "Ensure the widget is published with .ref() before the breakpoint mounts."
|
|
)
|
|
}
|
|
setter.addTo(breakpoint, target, setter.property)
|
|
}
|
|
widget.addBreakpoint(breakpoint: breakpoint)
|
|
}
|
|
}
|
|
|
|
/// Adds typed widget-property setters from a libadwaita condition string.
|
|
public func breakpoint(
|
|
_ condition: String,
|
|
@BreakpointBuilder _ setters: () -> [Setter]
|
|
) -> Self {
|
|
breakpoint(Condition(stringLiteral: condition), setters)
|
|
}
|
|
|
|
/// Adds a breakpoint with manually managed apply and unapply handlers.
|
|
public func breakpoint(
|
|
_ condition: Condition,
|
|
configure: @escaping (BreakpointConfiguration) -> Void
|
|
) -> Self {
|
|
appending { widget, context in
|
|
let breakpoint = Adw.Breakpoint(condition: condition.condition)
|
|
configure(BreakpointConfiguration(breakpoint: breakpoint, registry: context.registry))
|
|
widget.addBreakpoint(breakpoint: breakpoint)
|
|
}
|
|
}
|
|
|
|
/// Adds manually managed handlers from a libadwaita condition string.
|
|
public func breakpoint(
|
|
_ condition: String,
|
|
configure: @escaping (BreakpointConfiguration) -> Void
|
|
) -> Self {
|
|
breakpoint(Condition(stringLiteral: condition), configure: configure)
|
|
}
|
|
|
|
/// Binds the currently active breakpoint, or `nil` when none match.
|
|
public func currentBreakpoint(_ binding: Binding<Breakpoint?>) -> Self {
|
|
appending { widget, context in
|
|
context.registry.add(
|
|
widget.connectNotify(detail: "current-breakpoint") { _, _ in
|
|
binding.wrappedValue = widget.getCurrentBreakpoint()
|
|
}
|
|
)
|
|
binding.wrappedValue = widget.getCurrentBreakpoint()
|
|
}
|
|
}
|
|
}
|