112 lines
3.6 KiB
Swift
112 lines
3.6 KiB
Swift
// SPDX-FileCopyrightText: 2026 Kaede Akino
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import SwiftUI
|
|
|
|
/// The identity a Workspace contributes to `PersistentWorkspaceDeckLayout`.
|
|
private nonisolated struct PersistentWorkspaceDeckIDKey: LayoutValueKey {
|
|
static let defaultValue: Workspace.ID? = nil
|
|
}
|
|
|
|
/// Whether a Workspace is the one the deck currently presents.
|
|
private nonisolated struct PersistentWorkspaceDeckActiveKey: LayoutValueKey {
|
|
static let defaultValue = false
|
|
}
|
|
|
|
extension View {
|
|
func persistentWorkspaceDeckValue(id: Workspace.ID, isActive: Bool) -> some View {
|
|
layoutValue(key: PersistentWorkspaceDeckIDKey.self, value: id)
|
|
.layoutValue(key: PersistentWorkspaceDeckActiveKey.self, value: isActive)
|
|
}
|
|
}
|
|
|
|
/// Per-Workspace proposals for a deck whose children must remain mounted.
|
|
///
|
|
/// The visible Workspace tracks the deck bounds. Hidden Workspaces keep their last visible
|
|
/// size so a sidebar animation cannot recursively lay out every parked split
|
|
/// tree and native terminal surface. A Workspace that has never been visible captures
|
|
/// the first usable deck size as its parking size.
|
|
nonisolated struct PersistentWorkspaceDeckSizingState {
|
|
private var sizes: [Workspace.ID: CGSize] = [:]
|
|
|
|
mutating func size(
|
|
for id: Workspace.ID,
|
|
isActive: Bool,
|
|
containerSize: CGSize
|
|
) -> CGSize {
|
|
if isActive {
|
|
if containerSize.isUsableLayoutSize { sizes[id] = containerSize }
|
|
return containerSize
|
|
}
|
|
|
|
if let size = sizes[id] { return size }
|
|
if containerSize.isUsableLayoutSize { sizes[id] = containerSize }
|
|
return containerSize
|
|
}
|
|
|
|
mutating func prune(keeping ids: Set<Workspace.ID>) {
|
|
sizes = sizes.filter { ids.contains($0.key) }
|
|
}
|
|
|
|
func cachedSize(for id: Workspace.ID) -> CGSize? {
|
|
sizes[id]
|
|
}
|
|
}
|
|
|
|
nonisolated extension CGSize {
|
|
fileprivate var isUsableLayoutSize: Bool {
|
|
width.isFinite && height.isFinite && width > 0 && height > 0
|
|
}
|
|
}
|
|
|
|
/// A ZStack-like deck that measures only its active Workspace and parks every hidden
|
|
/// Workspace at its own stable size.
|
|
nonisolated struct PersistentWorkspaceDeckLayout: Layout {
|
|
typealias Cache = PersistentWorkspaceDeckSizingState
|
|
|
|
func makeCache(subviews _: Subviews) -> Cache {
|
|
Cache()
|
|
}
|
|
|
|
func updateCache(_ cache: inout Cache, subviews: Subviews) {
|
|
cache.prune(keeping: Set(subviews.compactMap { $0[PersistentWorkspaceDeckIDKey.self] }))
|
|
}
|
|
|
|
func sizeThatFits(
|
|
proposal: ProposedViewSize,
|
|
subviews: Subviews,
|
|
cache _: inout Cache
|
|
) -> CGSize {
|
|
guard let active = subviews.first(where: { $0[PersistentWorkspaceDeckActiveKey.self] }) else {
|
|
return proposal.replacingUnspecifiedDimensions()
|
|
}
|
|
|
|
let fitted = active.sizeThatFits(proposal)
|
|
return CGSize(
|
|
width: proposal.width ?? fitted.width,
|
|
height: proposal.height ?? fitted.height
|
|
)
|
|
}
|
|
|
|
func placeSubviews(
|
|
in bounds: CGRect,
|
|
proposal _: ProposedViewSize,
|
|
subviews: Subviews,
|
|
cache: inout Cache
|
|
) {
|
|
for subview in subviews {
|
|
guard let id = subview[PersistentWorkspaceDeckIDKey.self] else { continue }
|
|
let size = cache.size(
|
|
for: id,
|
|
isActive: subview[PersistentWorkspaceDeckActiveKey.self],
|
|
containerSize: bounds.size
|
|
)
|
|
subview.place(
|
|
at: bounds.origin,
|
|
anchor: .topLeading,
|
|
proposal: ProposedViewSize(width: size.width, height: size.height)
|
|
)
|
|
}
|
|
}
|
|
}
|