168 lines
5.1 KiB
Swift
168 lines
5.1 KiB
Swift
|
|
// WorkspaceManager.swift
|
||
|
|
// Controls which workspace layout is active and panel sizing/visibility.
|
||
|
|
|
||
|
|
import Observation
|
||
|
|
import SwiftUI
|
||
|
|
|
||
|
|
// MARK: - Workspace definitions
|
||
|
|
|
||
|
|
enum WorkspaceType: String, CaseIterable, Identifiable {
|
||
|
|
case modeling = "Modeling"
|
||
|
|
case uvTexture = "UV / Texture Bake"
|
||
|
|
case greasePencil = "Grease Pencil"
|
||
|
|
case animation = "Animation"
|
||
|
|
case rendering = "Rendering"
|
||
|
|
case sculpting = "Sculpting"
|
||
|
|
|
||
|
|
var id: String { rawValue }
|
||
|
|
|
||
|
|
var icon: String {
|
||
|
|
switch self {
|
||
|
|
case .modeling: "cube"
|
||
|
|
case .uvTexture: "square.grid.3x3.topleft.filled"
|
||
|
|
case .greasePencil: "pencil.tip"
|
||
|
|
case .animation: "play.rectangle"
|
||
|
|
case .rendering: "sun.max.fill"
|
||
|
|
case .sculpting: "paintbrush.pointed.fill"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Default panel configuration for each workspace.
|
||
|
|
var defaultPanels: [PanelSlot] {
|
||
|
|
switch self {
|
||
|
|
case .modeling:
|
||
|
|
[
|
||
|
|
.init(.toolSidebar, edge: .leading, size: 0.04),
|
||
|
|
.init(.viewport3D, edge: .center, size: 0.66),
|
||
|
|
.init(.outliner, edge: .trailing, size: 0.15),
|
||
|
|
.init(.properties, edge: .trailing, size: 0.15),
|
||
|
|
]
|
||
|
|
case .uvTexture:
|
||
|
|
[
|
||
|
|
.init(.toolSidebar, edge: .leading, size: 0.04),
|
||
|
|
.init(.viewport3D, edge: .center, size: 0.40),
|
||
|
|
.init(.uvEditor, edge: .center, size: 0.36),
|
||
|
|
.init(.textureBake, edge: .trailing, size: 0.20),
|
||
|
|
]
|
||
|
|
case .greasePencil:
|
||
|
|
[
|
||
|
|
.init(.brushPanel, edge: .leading, size: 0.06),
|
||
|
|
.init(.gpCanvas, edge: .center, size: 0.64),
|
||
|
|
.init(.gpLayers, edge: .trailing, size: 0.16),
|
||
|
|
.init(.onionSkin, edge: .trailing, size: 0.14),
|
||
|
|
]
|
||
|
|
case .animation:
|
||
|
|
[
|
||
|
|
.init(.toolSidebar, edge: .leading, size: 0.04),
|
||
|
|
.init(.viewport3D, edge: .center, size: 0.70),
|
||
|
|
.init(.outliner, edge: .trailing, size: 0.12),
|
||
|
|
.init(.properties, edge: .trailing, size: 0.14),
|
||
|
|
]
|
||
|
|
case .rendering:
|
||
|
|
[
|
||
|
|
.init(.viewport3D, edge: .center, size: 0.70),
|
||
|
|
.init(.renderSettings, edge: .trailing, size: 0.30),
|
||
|
|
]
|
||
|
|
case .sculpting:
|
||
|
|
[
|
||
|
|
.init(.brushPanel, edge: .leading, size: 0.06),
|
||
|
|
.init(.viewport3D, edge: .center, size: 0.76),
|
||
|
|
.init(.properties, edge: .trailing, size: 0.18),
|
||
|
|
]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// MARK: - Panel identity
|
||
|
|
|
||
|
|
enum PanelID: String, Identifiable, CaseIterable {
|
||
|
|
case toolSidebar = "Tools"
|
||
|
|
case viewport3D = "3D Viewport"
|
||
|
|
case uvEditor = "UV Editor"
|
||
|
|
case textureBake = "Texture Bake"
|
||
|
|
case brushPanel = "Brushes"
|
||
|
|
case gpCanvas = "GP Canvas"
|
||
|
|
case gpLayers = "Layers"
|
||
|
|
case onionSkin = "Onion Skin"
|
||
|
|
case outliner = "Outliner"
|
||
|
|
case properties = "Properties"
|
||
|
|
case renderSettings = "Render"
|
||
|
|
case timeline = "Timeline"
|
||
|
|
|
||
|
|
var id: String { rawValue }
|
||
|
|
}
|
||
|
|
|
||
|
|
enum PanelEdge {
|
||
|
|
case leading, center, trailing, bottom
|
||
|
|
}
|
||
|
|
|
||
|
|
struct PanelSlot: Identifiable {
|
||
|
|
let id: PanelID
|
||
|
|
let edge: PanelEdge
|
||
|
|
var size: CGFloat // proportion of total width (0…1)
|
||
|
|
var isVisible: Bool = true
|
||
|
|
var minSize: CGFloat = 0.04
|
||
|
|
|
||
|
|
init(_ id: PanelID, edge: PanelEdge, size: CGFloat, minSize: CGFloat = 0.04) {
|
||
|
|
self.id = id
|
||
|
|
self.edge = edge
|
||
|
|
self.size = size
|
||
|
|
self.minSize = minSize
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// MARK: - Manager
|
||
|
|
|
||
|
|
@Observable
|
||
|
|
final class WorkspaceManager {
|
||
|
|
var activeWorkspace: WorkspaceType {
|
||
|
|
didSet { panels = activeWorkspace.defaultPanels }
|
||
|
|
}
|
||
|
|
var panels: [PanelSlot]
|
||
|
|
|
||
|
|
// Bottom panels (shared across workspaces)
|
||
|
|
var showTimeline: Bool = false
|
||
|
|
var timelineHeight: CGFloat = 0.2
|
||
|
|
|
||
|
|
init(workspace: WorkspaceType = .modeling) {
|
||
|
|
self.activeWorkspace = workspace
|
||
|
|
self.panels = workspace.defaultPanels
|
||
|
|
}
|
||
|
|
|
||
|
|
// MARK: - Panel queries
|
||
|
|
|
||
|
|
func panel(_ id: PanelID) -> PanelSlot? {
|
||
|
|
panels.first { $0.id == id }
|
||
|
|
}
|
||
|
|
|
||
|
|
var leadingPanels: [PanelSlot] {
|
||
|
|
panels.filter { $0.edge == .leading && $0.isVisible }
|
||
|
|
}
|
||
|
|
|
||
|
|
var centerPanels: [PanelSlot] {
|
||
|
|
panels.filter { $0.edge == .center && $0.isVisible }
|
||
|
|
}
|
||
|
|
|
||
|
|
var trailingPanels: [PanelSlot] {
|
||
|
|
panels.filter { $0.edge == .trailing && $0.isVisible }
|
||
|
|
}
|
||
|
|
|
||
|
|
// MARK: - Panel mutation
|
||
|
|
|
||
|
|
func togglePanel(_ id: PanelID) {
|
||
|
|
guard let idx = panels.firstIndex(where: { $0.id == id }) else { return }
|
||
|
|
panels[idx].isVisible.toggle()
|
||
|
|
}
|
||
|
|
|
||
|
|
func resizePanel(_ id: PanelID, to newSize: CGFloat) {
|
||
|
|
guard let idx = panels.firstIndex(where: { $0.id == id }) else { return }
|
||
|
|
panels[idx].size = max(panels[idx].minSize, min(newSize, 0.8))
|
||
|
|
}
|
||
|
|
|
||
|
|
func switchWorkspace(to type: WorkspaceType) {
|
||
|
|
withAnimation(.easeInOut(duration: 0.25)) {
|
||
|
|
activeWorkspace = type
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|