portico/Sources/Portico/Core/AnyView.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) } })
}
}