98 lines
3.5 KiB
Swift
98 lines
3.5 KiB
Swift
import Gtk
|
|
|
|
/// Type-erased `View` that stores a mount thunk and its parent-node shape.
|
|
///
|
|
/// Uses runtime casts to dispatch: `Mountable` primitives/containers are
|
|
/// called directly; user structs evaluate `body` once and recurse.
|
|
/// ``AnyView`` deliberately does NOT conform to `Mountable` - the `as? AnyView`
|
|
/// branch handles nesting without name clashes.
|
|
public struct AnyView: View {
|
|
/// The mount thunk: builds this view's `Gtk.Widget` exactly once.
|
|
@_spi(Portico) public let makeWidget: (MountContext) -> Gtk.Widget
|
|
|
|
/// The structured node this view contributes to a parent container.
|
|
@_spi(Portico) public let kind: ViewNodeKind
|
|
|
|
public var body: Never { fatalError() }
|
|
|
|
/// Wraps a pre-built mount thunk directly.
|
|
@_spi(Portico) public init(
|
|
makeWidget: @escaping (MountContext) -> Gtk.Widget,
|
|
kind: ViewNodeKind = .widget
|
|
) {
|
|
self.makeWidget = makeWidget
|
|
self.kind = kind
|
|
}
|
|
|
|
/// Creates a type-erased view wrapping the given view.
|
|
///
|
|
/// Dispatch order: `AnyView` passthrough, ``Mountable`` direct mount, then
|
|
/// user-struct body evaluation once at mount.
|
|
public init<V: View>(_ view: V) {
|
|
if let any = view as? AnyView {
|
|
self.makeWidget = any.makeWidget
|
|
self.kind = any.kind
|
|
} else if let m = view as? Mountable {
|
|
self.makeWidget = { ctx in m.mount(ctx) }
|
|
self.kind = (view as? any DynamicViewGroup).map(ViewNodeKind.group) ?? .widget
|
|
} else {
|
|
self.makeWidget = { ctx in
|
|
_resolveDynamicProperties(view, in: ctx.environment)
|
|
return AnyView(view.body).makeWidget(ctx)
|
|
}
|
|
self.kind = (view as? any DynamicViewGroup).map(ViewNodeKind.group) ?? .widget
|
|
}
|
|
}
|
|
|
|
/// Creates a conditional node with a vertical-box fallback mount.
|
|
@_spi(Portico) public static func conditional(_ node: ConditionalNode) -> AnyView {
|
|
AnyView(
|
|
makeWidget: { ctx in mountConditionalFallback(node, ctx: ctx) },
|
|
kind: .conditional(node)
|
|
)
|
|
}
|
|
|
|
/// Creates a positional list node with a vertical-box fallback mount.
|
|
@_spi(Portico) public static func list(_ rows: [[AnyView]]) -> AnyView {
|
|
AnyView(
|
|
makeWidget: { ctx in mountListFallback(rows, ctx: ctx) },
|
|
kind: .list(rows)
|
|
)
|
|
}
|
|
|
|
/// Applies an environment derivation to this node and every descendant.
|
|
@_spi(Portico) public func deriving(
|
|
_ derive: @escaping (MountContext) -> MountContext
|
|
) -> AnyView {
|
|
AnyView(
|
|
makeWidget: { ctx in makeWidget(derive(ctx)) },
|
|
kind: derivingKind(kind, derive)
|
|
)
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private func derivingKind(
|
|
_ kind: ViewNodeKind,
|
|
_ derive: @escaping (MountContext) -> MountContext
|
|
) -> ViewNodeKind {
|
|
switch kind {
|
|
case .widget:
|
|
return .widget
|
|
case .group(let group):
|
|
return .group(EnvironmentGroup(base: group, derive: derive))
|
|
case .conditional(.taken(let index, let content)):
|
|
return .conditional(.taken(index: index, content: content.map { $0.deriving(derive) }))
|
|
case .conditional(.complete(let selector, let arms)):
|
|
return .conditional(.complete(
|
|
selector: selector,
|
|
arms: arms.map { arm in
|
|
ConditionalArm(kind: arm.kind, build: {
|
|
arm.build()?.map { $0.deriving(derive) }
|
|
}, track: arm.track)
|
|
}
|
|
))
|
|
case .list(let rows):
|
|
return .list(rows.map { $0.map { $0.deriving(derive) } })
|
|
}
|
|
}
|