portico/Sources/Portico/Containers/EitherView.swift

70 lines
2.7 KiB
Swift

import Gtk
/// A view that switches between two branches based on a `Binding<Bool>` condition.
///
/// Internally uses `Gtk.Stack` with named children `"true"` and `"false"`.
/// Each branch's widgets are mounted at most once the first time that
/// branch becomes active. Subsequent condition changes only call
/// `setVisibleChildName`.
@MainActor public struct EitherView: View {
private let condition: Binding<Bool>
private let firstContent: [AnyView]
private let secondContent: [AnyView]
public var body: Never { fatalError() }
/// - Parameters:
/// - condition: The binding that selects the visible branch.
/// - first: The view for the `true` branch (mounted once, lazily).
/// - second: The view for the `false` branch (mounted once, lazily).
public init(
_ condition: Binding<Bool>,
@ViewBuilder first: () -> [AnyView],
@ViewBuilder second: () -> [AnyView]
) {
self.condition = condition
self.firstContent = first()
self.secondContent = second()
}
}
@_spi(Portico) extension EitherView: Mountable {
@_spi(Portico) public func mount(_ ctx: MountContext) -> Gtk.Widget {
let stack = Gtk.Stack()
// Branch widget caches nil until the branch is first mounted.
var trueWidgets: [Gtk.Widget]? = nil
var falseWidgets: [Gtk.Widget]? = nil
// Mount the initially-active branch first so it's visible immediately.
if condition.wrappedValue {
trueWidgets = firstContent.map { $0.makeWidget(ctx) }
for w in trueWidgets! { _ = stack.addNamed(child: w, name: "true") }
} else {
falseWidgets = secondContent.map { $0.makeWidget(ctx) }
for w in falseWidgets! { _ = stack.addNamed(child: w, name: "false") }
}
// Track changes re-evaluate condition, lazily mount the other
// branch on first flip.
let tracker = DependencyTracker { [stack] in
let isTrue = condition.wrappedValue
if isTrue {
if trueWidgets == nil {
trueWidgets = firstContent.map { $0.makeWidget(ctx) }
for w in trueWidgets! { _ = stack.addNamed(child: w, name: "true") }
}
stack.setVisibleChildName(name: "true")
} else {
if falseWidgets == nil {
falseWidgets = secondContent.map { $0.makeWidget(ctx) }
for w in falseWidgets! { _ = stack.addNamed(child: w, name: "false") }
}
stack.setVisibleChildName(name: "false")
}
}
tracker.run()
ctx.registry.add(tracker)
return stack
}
}