Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
608 changes: 390 additions & 218 deletions Sources/UntoldEditor/Editor/AssetBrowserView.swift

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Sources/UntoldEditor/Editor/ComponentEditorForm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public struct ComponentForm: View {
HStack {
Text(label)
.font(.caption)
.foregroundColor(.secondary)
.foregroundColor(.editorTextSecondary)
TextField(placeholder ?? "",
text: Binding(
get: { get(entityId) },
Expand Down
58 changes: 29 additions & 29 deletions Sources/UntoldEditor/Editor/DemoGalleryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,19 @@ struct DemoGalleryView: View {
.stroke(Color.editorDivider, lineWidth: 1)
)
.cornerRadius(14)
.shadow(color: .black.opacity(0.34), radius: 24, x: 0, y: 14)
.shadow(color: .editorShadowStrong, radius: 24, x: 0, y: 14)
}

private var header: some View {
HStack(alignment: .top, spacing: 16) {
VStack(alignment: .leading, spacing: 8) {
Text("Explore Untold Engine")
.font(.largeTitle.bold())
.foregroundColor(.white)
.foregroundColor(.editorTextPrimary)

Text("Open a ready-to-navigate scene. No project setup, asset import, or scene graph knowledge required.")
.font(.body)
.foregroundColor(.white.opacity(0.78))
.foregroundColor(.editorTextSecondary)
.fixedSize(horizontal: false, vertical: true)
}

Expand Down Expand Up @@ -105,7 +105,7 @@ struct DemoGalleryView: View {

Text("You can switch to the full editor after loading a scene.")
.font(.caption)
.foregroundColor(.white.opacity(0.58))
.foregroundColor(.editorTextTertiary)
}
}
}
Expand Down Expand Up @@ -150,19 +150,19 @@ struct PreviewImportGalleryView: View {
.stroke(Color.editorDivider, lineWidth: 1)
)
.cornerRadius(14)
.shadow(color: .black.opacity(0.34), radius: 24, x: 0, y: 14)
.shadow(color: .editorShadowStrong, radius: 24, x: 0, y: 14)
}

private var header: some View {
HStack(alignment: .top, spacing: 16) {
VStack(alignment: .leading, spacing: 8) {
Text("Try Your Own Scene")
.font(.largeTitle.bold())
.foregroundColor(.white)
.foregroundColor(.editorTextPrimary)

Text("Load an exported Untold scene file without creating a project. You can navigate immediately, then open the full editor when you are ready.")
.font(.body)
.foregroundColor(.white.opacity(0.78))
.foregroundColor(.editorTextSecondary)
.fixedSize(horizontal: false, vertical: true)
}

Expand All @@ -186,11 +186,11 @@ struct PreviewImportGalleryView: View {
VStack(alignment: .leading, spacing: 10) {
Label("Need to create one of these files?", systemImage: "wand.and.stars")
.font(.headline)
.foregroundColor(.white)
.foregroundColor(.editorTextPrimary)

Text("Export from Blender with the Untold exporter add-on, or use the CLI exporter to produce .untold runtime assets and tiled .json scene manifests. Gaussian splats can be loaded directly from .ply files.")
.font(.caption)
.foregroundColor(.white.opacity(0.68))
.foregroundColor(.editorTextSecondary)
.fixedSize(horizontal: false, vertical: true)

Link(
Expand All @@ -208,13 +208,13 @@ struct PreviewImportGalleryView: View {
Text("4. Load here")
}
.font(.caption2.weight(.semibold))
.foregroundColor(.white.opacity(0.72))
.foregroundColor(.editorTextSecondary)
}
.padding(12)
.background(Color.editorSurface.opacity(0.56))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.white.opacity(0.08), lineWidth: 1)
.stroke(Color.editorDivider, lineWidth: 1)
)
.cornerRadius(10)
}
Expand All @@ -233,34 +233,34 @@ private struct PreviewImportCard: View {

Image(systemName: mode.systemImageName)
.font(.system(size: 38, weight: .semibold))
.foregroundColor(.white.opacity(0.88))
.foregroundColor(.editorTextPrimary)
}
.frame(height: 96)

Text(mode.exploreTitle)
.font(.headline)
.foregroundColor(.white)
.foregroundColor(.editorTextPrimary)

Text(mode.exploreSubtitle)
.font(.caption)
.foregroundColor(.white.opacity(0.68))
.foregroundColor(.editorTextSecondary)
.lineLimit(3)
.fixedSize(horizontal: false, vertical: true)

Text(mode.exploreFileTypes)
.font(.caption2.weight(.semibold))
.foregroundColor(.white.opacity(0.74))
.foregroundColor(.editorTextSecondary)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Color.black.opacity(0.18))
.background(Color.editorBadgeBackground)
.cornerRadius(7)
}
.padding(12)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.editorSurface.opacity(0.72))
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(Color.white.opacity(0.08), lineWidth: 1)
.stroke(Color.editorDivider, lineWidth: 1)
)
.cornerRadius(12)
}
Expand All @@ -281,10 +281,10 @@ struct QuickPreviewSceneOverlayView: View {
VStack(alignment: .leading, spacing: 3) {
Text(title)
.font(.headline)
.foregroundColor(.white)
.foregroundColor(.editorTextPrimary)
Text(mode?.exploreLoadedSubtitle ?? "Your Scene")
.font(.caption)
.foregroundColor(.white.opacity(0.66))
.foregroundColor(.editorTextSecondary)
}

Spacer()
Expand All @@ -306,7 +306,7 @@ struct QuickPreviewSceneOverlayView: View {
.stroke(Color.editorDivider, lineWidth: 1)
)
.cornerRadius(10)
.shadow(color: .black.opacity(0.25), radius: 14, x: 0, y: 8)
.shadow(color: .editorShadow, radius: 14, x: 0, y: 8)
}
}

Expand Down Expand Up @@ -396,19 +396,19 @@ private struct DemoSceneCard: View {
} else {
Image(systemName: demo.systemImageName)
.font(.system(size: 42, weight: .semibold))
.foregroundColor(.white.opacity(0.88))
.foregroundColor(.editorTextPrimary)
}
}
.frame(height: 112)

VStack(alignment: .leading, spacing: 6) {
Text(demo.title)
.font(.headline)
.foregroundColor(.white)
.foregroundColor(.editorTextPrimary)

Text(demo.subtitle)
.font(.caption)
.foregroundColor(.white.opacity(0.68))
.foregroundColor(.editorTextSecondary)
.lineLimit(3)
.fixedSize(horizontal: false, vertical: true)
}
Expand All @@ -420,7 +420,7 @@ private struct DemoSceneCard: View {
.background(Color.editorSurface.opacity(0.72))
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(Color.white.opacity(0.08), lineWidth: 1)
.stroke(Color.editorDivider, lineWidth: 1)
)
.cornerRadius(12)
}
Expand All @@ -433,10 +433,10 @@ private struct DemoSceneCard: View {
ForEach(demo.tags.prefix(3), id: \.self) { tag in
Text(tag)
.font(.caption2.weight(.semibold))
.foregroundColor(.white.opacity(0.74))
.foregroundColor(.editorTextSecondary)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Color.black.opacity(0.18))
.background(Color.editorBadgeBackground)
.cornerRadius(7)
}
}
Expand All @@ -454,10 +454,10 @@ struct ExploreSceneOverlayView: View {
VStack(alignment: .leading, spacing: 3) {
Text(demo.title)
.font(.headline)
.foregroundColor(.white)
.foregroundColor(.editorTextPrimary)
Text("Explore Mode")
.font(.caption)
.foregroundColor(.white.opacity(0.66))
.foregroundColor(.editorTextSecondary)
}

Spacer()
Expand All @@ -479,6 +479,6 @@ struct ExploreSceneOverlayView: View {
.stroke(Color.editorDivider, lineWidth: 1)
)
.cornerRadius(10)
.shadow(color: .black.opacity(0.25), radius: 14, x: 0, y: 8)
.shadow(color: .editorShadow, radius: 14, x: 0, y: 8)
}
}
73 changes: 73 additions & 0 deletions Sources/UntoldEditor/Editor/EditorMenuCommands.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// EditorMenuCommands.swift
//
//
// Copyright (C) Untold Engine Studios
// Licensed under the GNU LGPL v3.0 or later.
// See the LICENSE file or <https://www.gnu.org/licenses/> for details.
//
// Bridges the native macOS menu bar (built in AppKit, see main.swift) with the
// SwiftUI editor. Menu items post these notifications; EditorView listens and
// runs the matching action. Shared toggle state lives in a singleton so both
// the menu (checkmarks) and SwiftUI can read/write it.
//
import Foundation

extension Notification.Name {
/// Posted when asynchronous asset/tile loading finishes, so the Scene Graph
/// can refresh to show the newly-loaded entities.
static let sceneGraphNeedsRefresh = Notification.Name("sceneGraphNeedsRefresh")
static let editorMenuNew = Notification.Name("editorMenuNew")
static let editorMenuOpen = Notification.Name("editorMenuOpen")
static let editorMenuNewScene = Notification.Name("editorMenuNewScene")
static let editorMenuSaveProject = Notification.Name("editorMenuSaveProject")
static let editorMenuSave = Notification.Name("editorMenuSave")
static let editorMenuSaveAs = Notification.Name("editorMenuSaveAs")
static let editorMenuReset = Notification.Name("editorMenuReset")
}

/// Playback-related settings that must be reachable from both the AppKit menu
/// bar and SwiftUI views. Currently holds the "use the scene camera while
/// playing" toggle that used to live in the top toolbar.
final class EditorPlaybackSettings: ObservableObject {
static let shared = EditorPlaybackSettings()

@Published var useSceneCameraDuringPlay: Bool = false

private init() {}
}

/// Which editor panels are visible. Shared between the AppKit View menu (for
/// checkmarks) and SwiftUI (to show/hide the panels).
final class EditorPanelVisibility: ObservableObject {
static let shared = EditorPanelVisibility()

@Published var showLeftPanel: Bool = true
@Published var showBottomPanel: Bool = true
@Published var showRightPanel: Bool = true

// Saved layout used by the "focus viewport" toggle (⌘F).
private var savedLayout: (left: Bool, bottom: Bool, right: Bool)?

private init() {}

private var anyPanelVisible: Bool {
showLeftPanel || showBottomPanel || showRightPanel
}

/// Hide every panel to show only the viewport; toggling again restores the
/// panels that were visible before (not necessarily all of them).
func toggleFocusViewport() {
if anyPanelVisible {
savedLayout = (showLeftPanel, showBottomPanel, showRightPanel)
showLeftPanel = false
showBottomPanel = false
showRightPanel = false
} else {
let layout = savedLayout ?? (true, true, true)
showLeftPanel = layout.left
showBottomPanel = layout.bottom
showRightPanel = layout.right
}
}
}
24 changes: 23 additions & 1 deletion Sources/UntoldEditor/Editor/EditorSceneView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,27 @@ struct EditorSceneView: View, UntoldRendererDelegate {
}
}

func didDraw(in _: MTKView) {}
func didDraw(in _: MTKView) {
// Detect the moment async asset/tile loading finishes and ask the Scene
// Graph to refresh, so streamed-in entities appear without a manual action.
SceneGraphLoadWatcher.shared.poll()
}
}

/// Watches the engine's async-loading gate on the render loop and posts a
/// refresh notification on the falling edge (loading → idle).
final class SceneGraphLoadWatcher {
static let shared = SceneGraphLoadWatcher()
private var wasLoading = false
private init() {}

func poll() {
let loading = AssetLoadingGate.shared.isLoadingAny
if wasLoading, loading == false {
DispatchQueue.main.async {
NotificationCenter.default.post(name: .sceneGraphNeedsRefresh, object: nil)
}
}
wasLoading = loading
}
}
Loading