From 2bd8fe819dde5e7b14a299fda63b75fae174e5b7 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Sun, 30 Nov 2025 22:31:16 -0700 Subject: [PATCH 01/41] [Docs] Update readme with new ecosystem --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 2f1e17f..472285b 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,52 @@ # Untold Editor -The **Untold Editor** is a companion tool for the [Untold Engine](https://github.com/untoldengine/UntoldEngine). -It provides a visual environment for managing assets, scenes, and entities in projects built with the engine. +The **Untold Editor** is the visual development environment for the [Untold Engine](https://github.com/untoldengine/UntoldEngine). +It provides a complete toolkit for building games with scripting support, asset management, scene editing, and more. -The editor is not required to use the engine, but it makes iteration faster by giving developers and designers a user-friendly interface. +## 🎮 For Game Developers + +**Want to make games? Download [Untold Engine Studio](https://github.com/untoldengine/UntoldEditor/releases)** — a standalone app that includes everything you need: + +- ✅ Complete visual editor +- ✅ Scripting system for game logic +- ✅ All engine features built-in +- ✅ No setup required — just download the DMG and start creating + +👉 **[Download Untold Engine Studio](https://github.com/untoldengine/UntoldEditor/releases)** + +## 🛠️ For Contributors + +This repository is for developers who want to **contribute to the editor itself**. If you're looking to improve the editor, fix bugs, or add features, you're in the right place! ![UntoldEditorScreenshot](images/editorscreenshot.png) --- +## 📚 Understanding the Ecosystem + +The Untold Engine project has three main components: + +### For Game Developers: +- **[Untold Engine Studio](https://github.com/untoldengine/UntoldEditor/releases)** (Download) + - Standalone app with everything included + - Scripting, visual editor, asset management + - No GitHub or build tools required + - **Start here if you want to make games** + +### For Engine/Editor Contributors: +- **[Untold Engine](https://github.com/untoldengine/UntoldEngine)** (Clone to contribute) + - Core engine repository + - Rendering, physics, ECS, animation systems + - Clone this if you want to contribute to the engine core + +- **[Untold Editor](https://github.com/untoldengine/UntoldEditor)** (This repo) + - Editor interface and tooling + - Clone this if you want to contribute to the editor + +### Additional Resources: +- **Examples / Starter Projects:** [UntoldEngineExamples](https://github.com/untoldengine/UntoldEngineExamples) + +--- + ## ✨ Features - **Scene Graph** – Organize and visualize entity hierarchies @@ -27,9 +66,11 @@ The editor is not required to use the engine, but it makes iteration faster by g --- -## 📦 Getting the Editor +## 📦 Development Setup + +**Note:** If you just want to make games, download [Untold Engine Studio](https://github.com/untoldengine/UntoldEditor/releases) instead. -Clone this repository: +For editor development, clone this repository: ```bash git clone https://github.com/untoldengine/UntoldEditor.git @@ -115,13 +156,6 @@ See **CONTRIBUTING.md** and **CODE_OF_CONDUCT.md** (coming soon). --- -## 📚 Related Repos - -- **Engine Core:** [Untold Engine](https://github.com/untoldengine/UntoldEngine) -- **Examples / Starter Projects:** [UntoldEngineExamples](https://github.com/untoldengine/UntoldEngineExamples) - ---- - ## 📜 License Licensed under **GNU LGPL v3.0**. From c24c30687ac242d837248621a716cf91fc7ae37d Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 2 Dec 2025 22:48:09 -0700 Subject: [PATCH 02/41] [Patch] Added basic primitives --- Sources/UntoldEditor/Editor/EditorView.swift | 62 ++++++++++++++++- .../Editor/SceneHierarchyView.swift | 4 +- Sources/UntoldEditor/Editor/ToolbarView.swift | 67 +++++++++++++++++++ 3 files changed, 130 insertions(+), 3 deletions(-) diff --git a/Sources/UntoldEditor/Editor/EditorView.swift b/Sources/UntoldEditor/Editor/EditorView.swift index 1619df5..b138d18 100644 --- a/Sources/UntoldEditor/Editor/EditorView.swift +++ b/Sources/UntoldEditor/Editor/EditorView.swift @@ -46,7 +46,12 @@ public struct EditorView: View { dirLightCreate: editor_createDirLight, pointLightCreate: editor_createPointLight, spotLightCreate: editor_createSpotLight, - areaLightCreate: editor_createAreaLight + areaLightCreate: editor_createAreaLight, + onCreateCube: editor_createCube, + onCreateSphere: editor_createSphere, + onCreatePlane: editor_createPlane, + onCreateCylinder: editor_createCylinder, + onCreateCone: editor_createCone ) Divider() HStack { @@ -328,4 +333,59 @@ public struct EditorView: View { translateTo(entityId: selectionManager.selectedEntity!, position: spawnPosition) } + + // MARK: - Primitive Creation Functions + + private func editor_createPrimitive(name: String, meshes: [Mesh]) { + removeGizmo() + + let entityId = createEntity() + // Append entity ID to make the name unique + let uniqueName = "\(name)-\(entityId)" + setEntityName(entityId: entityId, name: uniqueName) + + // Use setEntityMeshDirect which follows the same pattern as setEntityMesh + setEntityMeshDirect(entityId: entityId, meshes: meshes, assetName: name) + + // Spawn in front of camera + guard let camera = CameraSystem.shared.activeCamera, let cameraComponent = scene.get(component: CameraComponent.self, for: camera) else { + handleError(.noActiveCamera) + return + } + + var forward = forwardDirectionVector(from: cameraComponent.rotation) + forward *= -1.0 + let camPosition = cameraComponent.localPosition + let spawnPosition = camPosition + forward * spawnDistance + translateTo(entityId: entityId, position: simd_float3(0.0,0.0,0.0)) + + selectionManager.selectedEntity = entityId + editor_entities = getAllGameEntities() + sceneGraphModel.refreshHierarchy() + } + + private func editor_createCube() { + let meshes = BasicPrimitives.createCube() + editor_createPrimitive(name: "Cube", meshes: meshes) + } + + private func editor_createSphere() { + let meshes = BasicPrimitives.createSphere() + editor_createPrimitive(name: "Sphere", meshes: meshes) + } + + private func editor_createPlane() { + let meshes = BasicPrimitives.createPlane() + editor_createPrimitive(name: "Plane", meshes: meshes) + } + + private func editor_createCylinder() { + let meshes = BasicPrimitives.createCylinder() + editor_createPrimitive(name: "Cylinder", meshes: meshes) + } + + private func editor_createCone() { + let meshes = BasicPrimitives.createCone() + editor_createPrimitive(name: "Cone", meshes: meshes) + } } diff --git a/Sources/UntoldEditor/Editor/SceneHierarchyView.swift b/Sources/UntoldEditor/Editor/SceneHierarchyView.swift index b00dab4..b3e3d19 100644 --- a/Sources/UntoldEditor/Editor/SceneHierarchyView.swift +++ b/Sources/UntoldEditor/Editor/SceneHierarchyView.swift @@ -21,10 +21,10 @@ struct SceneHierarchyView: View { // MARK: - Header with Add/Remove Buttons HStack { - Image(systemName: "diagram.tree") + Image(systemName: "list.bullet.indent") .foregroundColor(.accentColor) Text("Scene Graph") - .font(.title2) + .font(.title3) .fontWeight(.bold) .foregroundColor(.primary) diff --git a/Sources/UntoldEditor/Editor/ToolbarView.swift b/Sources/UntoldEditor/Editor/ToolbarView.swift index 7e7729a..8fe668b 100644 --- a/Sources/UntoldEditor/Editor/ToolbarView.swift +++ b/Sources/UntoldEditor/Editor/ToolbarView.swift @@ -20,6 +20,11 @@ var pointLightCreate: () -> Void var spotLightCreate: () -> Void var areaLightCreate: () -> Void + var onCreateCube: () -> Void + var onCreateSphere: () -> Void + var onCreatePlane: () -> Void + var onCreateCylinder: () -> Void + var onCreateCone: () -> Void @State private var isPlaying = false @State private var showBuildSettings = false @@ -109,6 +114,68 @@ var leftSection: some View { HStack(spacing: 8) { + // Primitives Section + Text("Primitives:") + .font(.system(size: 11)) + .foregroundColor(.secondary) + + Button(action: onCreateCube) { + Image(systemName: "cube.fill") + .font(.system(size: 14)) + .foregroundColor(.white) + } + .padding(2) + .background(Color.gray.opacity(0.8)) + .cornerRadius(6) + .buttonStyle(.plain) + .help("Add Cube") + + Button(action: onCreateSphere) { + Image(systemName: "circle.fill") + .font(.system(size: 14)) + .foregroundColor(.white) + } + .padding(2) + .background(Color.gray.opacity(0.8)) + .cornerRadius(6) + .buttonStyle(.plain) + .help("Add Sphere") + + Button(action: onCreatePlane) { + Image(systemName: "rectangle.fill") + .font(.system(size: 14)) + .foregroundColor(.white) + } + .padding(2) + .background(Color.gray.opacity(0.8)) + .cornerRadius(6) + .buttonStyle(.plain) + .help("Add Plane") + +// Button(action: onCreateCylinder) { +// Image(systemName: "cylinder.fill") +// .font(.system(size: 14)) +// .foregroundColor(.white) +// } +// .padding(6) +// .background(Color.blue.opacity(0.8)) +// .cornerRadius(6) +// .buttonStyle(.plain) +// .help("Add Cylinder") +// +// Button(action: onCreateCone) { +// Image(systemName: "cone.fill") +// .font(.system(size: 14)) +// .foregroundColor(.white) +// } +// .padding(6) +// .background(Color.blue.opacity(0.8)) +// .cornerRadius(6) +// .buttonStyle(.plain) +// .help("Add Cone") + + Divider().frame(height: 24) + Text("Scripts:") .font(.system(size: 11)) .foregroundColor(.secondary) From b686d070002623ef0dfe2eb0eabf695b1910833e Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 2 Dec 2025 22:48:45 -0700 Subject: [PATCH 03/41] [Patch] Fixed retrieval of usdz textures --- .../UntoldEditor/Editor/InspectorView.swift | 37 ++++++--- .../UntoldEditor/Utils/EditorFuncUtils.swift | 80 +++++++++++++++++++ 2 files changed, 106 insertions(+), 11 deletions(-) diff --git a/Sources/UntoldEditor/Editor/InspectorView.swift b/Sources/UntoldEditor/Editor/InspectorView.swift index 39310a3..f456350 100644 --- a/Sources/UntoldEditor/Editor/InspectorView.swift +++ b/Sources/UntoldEditor/Editor/InspectorView.swift @@ -463,9 +463,8 @@ struct RenderingEditorView: View { HStack(alignment: .top, spacing: 24) { ForEach(TextureType.allCases) { type in let image: NSImage? = { - if let url = getMaterialTextureURL(entityId: entityId, type: type), - let img = NSImage(contentsOf: url) - { + // Use the helper function that handles both regular and embedded textures + if let img = getMaterialTextureImage(entityId: entityId, type: type) { return img } else { return NSImage(named: "Default Texture") @@ -494,15 +493,31 @@ struct RenderingEditorView: View { } .buttonStyle(PlainButtonStyle()) - // Remove texture - Button(action: { - removeMaterialTexture(entityId: entityId, textureType: type) - refreshView() - }) { - Image(systemName: "minus.circle.fill") - .foregroundColor(.red) + // Remove or Restore texture buttons + HStack(spacing: 8) { + // Remove texture button + Button(action: { + removeMaterialTexture(entityId: entityId, textureType: type) + refreshView() + }) { + Image(systemName: "minus.circle.fill") + .foregroundColor(.red) + } + .buttonStyle(BorderlessButtonStyle()) + + // Restore button (only show if there's an embedded texture to restore) + if canRestoreEmbeddedTexture(entityId: entityId, type: type) { + Button(action: { + restoreEmbeddedTexture(entityId: entityId, textureType: type) + refreshView() + }) { + Image(systemName: "arrow.counterclockwise.circle.fill") + .foregroundColor(.blue) + } + .buttonStyle(BorderlessButtonStyle()) + .help("Restore original embedded texture") + } } - .buttonStyle(BorderlessButtonStyle()) // Texture name Text(type.displayName) diff --git a/Sources/UntoldEditor/Utils/EditorFuncUtils.swift b/Sources/UntoldEditor/Utils/EditorFuncUtils.swift index 358b3b6..34495eb 100644 --- a/Sources/UntoldEditor/Utils/EditorFuncUtils.swift +++ b/Sources/UntoldEditor/Utils/EditorFuncUtils.swift @@ -8,6 +8,7 @@ // import Foundation +import ModelIO import SwiftUI import UntoldEngine @@ -66,3 +67,82 @@ func bindingForMaterialRoughness(entityId: EntityID, onChange: @escaping () -> V } ) } + +// Helper function to get NSImage from material texture, handling both file-based and embedded USDZ textures +func getMaterialTextureImage(entityId: EntityID, type: TextureType) -> NSImage? { + // First, check if we have a URL + guard let url = getMaterialTextureURL(entityId: entityId, type: type) else { + return nil + } + + // Check if this is an embedded USDZ texture + if isEmbeddedUSDZTexture(url) { + // For embedded textures, we need to extract the image from MDLTexture + guard let mdlTexture = getMaterialMDLTexture(entityId: entityId, type: type) else { + print("Warning: Could not get MDLTexture for embedded texture: \(url)") + return nil + } + + // Convert MDLTexture to NSImage via CGImage + if let image = nsImageFromMDLTexture(mdlTexture) { + return image + } else { + print("Warning: Failed to convert MDLTexture to NSImage for: \(url)") + return nil + } + } else { + // For file-based textures, load directly from URL + return NSImage(contentsOf: url) + } +} + +// Convert MDLTexture to NSImage +func nsImageFromMDLTexture(_ mdlTexture: MDLTexture) -> NSImage? { + // Get the texture data from MDLTexture + guard let texelData = mdlTexture.texelDataWithTopLeftOrigin() else { + print("Error: texelDataWithTopLeftOrigin() returned nil") + return nil + } + + let width = Int(mdlTexture.dimensions.x) + let height = Int(mdlTexture.dimensions.y) + let channelCount = Int(mdlTexture.channelCount) + + // Validate dimensions + guard width > 0 && height > 0 && channelCount > 0 else { + print("Error: Invalid texture dimensions - width: \(width), height: \(height), channels: \(channelCount)") + return nil + } + + let bytesPerRow = width * channelCount + + // Create a CGImage from the texture data + guard let dataProvider = CGDataProvider(data: texelData as CFData) else { + return nil + } + + let colorSpace = CGColorSpaceCreateDeviceRGB() + let bitmapInfo: CGBitmapInfo = mdlTexture.channelCount == 4 + ? CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue) + : CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue) + + guard let cgImage = CGImage( + width: width, + height: height, + bitsPerComponent: 8, + bitsPerPixel: channelCount * 8, + bytesPerRow: bytesPerRow, + space: colorSpace, + bitmapInfo: bitmapInfo, + provider: dataProvider, + decode: nil, + shouldInterpolate: true, + intent: .defaultIntent + ) else { + print("Error: Failed to create CGImage with dimensions \(width)x\(height), channels: \(channelCount)") + return nil + } + + let size = NSSize(width: CGFloat(width), height: CGFloat(height)) + return NSImage(cgImage: cgImage, size: size) +} From 89ef49658cc64e1e5529c2142ce5c5c307332847 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 2 Dec 2025 23:10:56 -0700 Subject: [PATCH 04/41] [Test] Added test to toolbar for primitives --- .../UntoldEditorTests/ToolbarViewTests.swift | 148 +++++++++++++++++- 1 file changed, 144 insertions(+), 4 deletions(-) diff --git a/Tests/UntoldEditorTests/ToolbarViewTests.swift b/Tests/UntoldEditorTests/ToolbarViewTests.swift index afacaba..1424699 100644 --- a/Tests/UntoldEditorTests/ToolbarViewTests.swift +++ b/Tests/UntoldEditorTests/ToolbarViewTests.swift @@ -26,7 +26,12 @@ import XCTest onDirLightCalled: UnsafeMutablePointer, onPointLightCalled: UnsafeMutablePointer, onSpotLightCalled: UnsafeMutablePointer, - onAreaLightCalled: UnsafeMutablePointer + onAreaLightCalled: UnsafeMutablePointer, + onCreateCubeCalled: UnsafeMutablePointer, + onCreateSphereCalled: UnsafeMutablePointer, + onCreatePlaneCalled: UnsafeMutablePointer, + onCreateCylinderCalled: UnsafeMutablePointer, + onCreateConeCalled: UnsafeMutablePointer ) -> ToolbarView { ToolbarView( selectionManager: selectionManager, @@ -37,7 +42,12 @@ import XCTest dirLightCreate: { onDirLightCalled.pointee = true }, pointLightCreate: { onPointLightCalled.pointee = true }, spotLightCreate: { onSpotLightCalled.pointee = true }, - areaLightCreate: { onAreaLightCalled.pointee = true } + areaLightCreate: { onAreaLightCalled.pointee = true }, + onCreateCube: { onCreateCubeCalled.pointee = true }, + onCreateSphere: { onCreateSphereCalled.pointee = true }, + onCreatePlane: { onCreatePlaneCalled.pointee = true }, + onCreateCylinder: { onCreateCylinderCalled.pointee = true }, + onCreateCone: { onCreateConeCalled.pointee = true } ) } @@ -51,6 +61,11 @@ import XCTest var onPoint = false var onSpot = false var onArea = false + var onCube = false + var onSphere = false + var onPlane = false + var onCylinder = false + var onCone = false let sut = makeSUT( onSaveCalled: &onSave, @@ -61,7 +76,12 @@ import XCTest onDirLightCalled: &onDir, onPointLightCalled: &onPoint, onSpotLightCalled: &onSpot, - onAreaLightCalled: &onArea + onAreaLightCalled: &onArea, + onCreateCubeCalled: &onCube, + onCreateSphereCalled: &onSphere, + onCreatePlaneCalled: &onPlane, + onCreateCylinderCalled: &onCylinder, + onCreateConeCalled: &onCone ) // We cannot programmatically tap SwiftUI Buttons without a host and introspection. @@ -73,6 +93,11 @@ import XCTest sut.pointLightCreate() sut.spotLightCreate() sut.areaLightCreate() + sut.onCreateCube() + sut.onCreateSphere() + sut.onCreatePlane() + sut.onCreateCylinder() + sut.onCreateCone() XCTAssertTrue(onSave, "onSave should be wired.") XCTAssertTrue(onLoad, "onLoad should be wired.") @@ -81,6 +106,11 @@ import XCTest XCTAssertTrue(onPoint, "pointLightCreate should be wired.") XCTAssertTrue(onSpot, "spotLightCreate should be wired.") XCTAssertTrue(onArea, "areaLightCreate should be wired.") + XCTAssertTrue(onCube, "onCreateCube should be wired.") + XCTAssertTrue(onSphere, "onCreateSphere should be wired.") + XCTAssertTrue(onPlane, "onCreatePlane should be wired.") + XCTAssertTrue(onCylinder, "onCreateCylinder should be wired.") + XCTAssertTrue(onCone, "onCreateCone should be wired.") // For play toggle, verify the closure records values we pass. // Since @State is internal, we mimic the button behavior by calling the closure directly. @@ -103,7 +133,12 @@ import XCTest dirLightCreate: {}, pointLightCreate: {}, spotLightCreate: {}, - areaLightCreate: {} + areaLightCreate: {}, + onCreateCube: {}, + onCreateSphere: {}, + onCreatePlane: {}, + onCreateCylinder: {}, + onCreateCone: {} ) // Wrap in a hosting controller to ensure SwiftUI can build the body. @@ -112,5 +147,110 @@ import XCTest _ = host // keep alive _ = playValues // keep referenced to avoid 'never read' warning } + + // MARK: - Primitive Creation Tests + + func test_primitiveButtons_callCorrectClosures() { + // Given: A ToolbarView with tracked primitive creation callbacks + var cubeCreated = false + var sphereCreated = false + var planeCreated = false + var cylinderCreated = false + var coneCreated = false + + let sut = ToolbarView( + selectionManager: SelectionManager(), + onSave: {}, + onLoad: {}, + onClear: {}, + onPlayToggled: { _ in }, + dirLightCreate: {}, + pointLightCreate: {}, + spotLightCreate: {}, + areaLightCreate: {}, + onCreateCube: { cubeCreated = true }, + onCreateSphere: { sphereCreated = true }, + onCreatePlane: { planeCreated = true }, + onCreateCylinder: { cylinderCreated = true }, + onCreateCone: { coneCreated = true } + ) + + // When: Invoking the primitive creation closures + sut.onCreateCube() + sut.onCreateSphere() + sut.onCreatePlane() + sut.onCreateCylinder() + sut.onCreateCone() + + // Then: Each closure should be called + XCTAssertTrue(cubeCreated, "Cube creation callback should be invoked") + XCTAssertTrue(sphereCreated, "Sphere creation callback should be invoked") + XCTAssertTrue(planeCreated, "Plane creation callback should be invoked") + XCTAssertTrue(cylinderCreated, "Cylinder creation callback should be invoked") + XCTAssertTrue(coneCreated, "Cone creation callback should be invoked") + } + + func test_primitivesSection_existsInView() { + // Given: A ToolbarView instance + var callCount = 0 + let sut = ToolbarView( + selectionManager: SelectionManager(), + onSave: {}, + onLoad: {}, + onClear: {}, + onPlayToggled: { _ in }, + dirLightCreate: {}, + pointLightCreate: {}, + spotLightCreate: {}, + areaLightCreate: {}, + onCreateCube: { callCount += 1 }, + onCreateSphere: { callCount += 1 }, + onCreatePlane: { callCount += 1 }, + onCreateCylinder: {}, + onCreateCone: {} + ) + + // When: Calling the primitive closures + sut.onCreateCube() + sut.onCreateSphere() + sut.onCreatePlane() + + // Then: All three active primitives should have been called + XCTAssertEqual(callCount, 3, "All three active primitive buttons (cube, sphere, plane) should be functional") + } + + func test_primitiveCallbacks_areIndependent() { + // Given: Separate tracking for each primitive + var cubeCount = 0 + var sphereCount = 0 + var planeCount = 0 + + let sut = ToolbarView( + selectionManager: SelectionManager(), + onSave: {}, + onLoad: {}, + onClear: {}, + onPlayToggled: { _ in }, + dirLightCreate: {}, + pointLightCreate: {}, + spotLightCreate: {}, + areaLightCreate: {}, + onCreateCube: { cubeCount += 1 }, + onCreateSphere: { sphereCount += 1 }, + onCreatePlane: { planeCount += 1 }, + onCreateCylinder: {}, + onCreateCone: {} + ) + + // When: Calling specific primitive closures multiple times + sut.onCreateCube() + sut.onCreateCube() + sut.onCreateSphere() + + // Then: Each closure should track independently + XCTAssertEqual(cubeCount, 2, "Cube should be created twice") + XCTAssertEqual(sphereCount, 1, "Sphere should be created once") + XCTAssertEqual(planeCount, 0, "Plane should not be created") + } } #endif From 0b3808ca0f0528b9ce41bd797e6b001b501f9225 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 2 Dec 2025 23:11:08 -0700 Subject: [PATCH 05/41] [Patch] Formatted files --- Sources/UntoldEditor/Editor/EditorView.swift | 2 +- .../UntoldEditor/Editor/InspectorView.swift | 2 +- .../UntoldEditor/Utils/EditorFuncUtils.swift | 22 +++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Sources/UntoldEditor/Editor/EditorView.swift b/Sources/UntoldEditor/Editor/EditorView.swift index b138d18..14679fc 100644 --- a/Sources/UntoldEditor/Editor/EditorView.swift +++ b/Sources/UntoldEditor/Editor/EditorView.swift @@ -357,7 +357,7 @@ public struct EditorView: View { forward *= -1.0 let camPosition = cameraComponent.localPosition let spawnPosition = camPosition + forward * spawnDistance - translateTo(entityId: entityId, position: simd_float3(0.0,0.0,0.0)) + translateTo(entityId: entityId, position: simd_float3(0.0, 0.0, 0.0)) selectionManager.selectedEntity = entityId editor_entities = getAllGameEntities() diff --git a/Sources/UntoldEditor/Editor/InspectorView.swift b/Sources/UntoldEditor/Editor/InspectorView.swift index f456350..9712076 100644 --- a/Sources/UntoldEditor/Editor/InspectorView.swift +++ b/Sources/UntoldEditor/Editor/InspectorView.swift @@ -504,7 +504,7 @@ struct RenderingEditorView: View { .foregroundColor(.red) } .buttonStyle(BorderlessButtonStyle()) - + // Restore button (only show if there's an embedded texture to restore) if canRestoreEmbeddedTexture(entityId: entityId, type: type) { Button(action: { diff --git a/Sources/UntoldEditor/Utils/EditorFuncUtils.swift b/Sources/UntoldEditor/Utils/EditorFuncUtils.swift index 34495eb..a42cc3a 100644 --- a/Sources/UntoldEditor/Utils/EditorFuncUtils.swift +++ b/Sources/UntoldEditor/Utils/EditorFuncUtils.swift @@ -74,7 +74,7 @@ func getMaterialTextureImage(entityId: EntityID, type: TextureType) -> NSImage? guard let url = getMaterialTextureURL(entityId: entityId, type: type) else { return nil } - + // Check if this is an embedded USDZ texture if isEmbeddedUSDZTexture(url) { // For embedded textures, we need to extract the image from MDLTexture @@ -82,7 +82,7 @@ func getMaterialTextureImage(entityId: EntityID, type: TextureType) -> NSImage? print("Warning: Could not get MDLTexture for embedded texture: \(url)") return nil } - + // Convert MDLTexture to NSImage via CGImage if let image = nsImageFromMDLTexture(mdlTexture) { return image @@ -103,29 +103,29 @@ func nsImageFromMDLTexture(_ mdlTexture: MDLTexture) -> NSImage? { print("Error: texelDataWithTopLeftOrigin() returned nil") return nil } - + let width = Int(mdlTexture.dimensions.x) let height = Int(mdlTexture.dimensions.y) let channelCount = Int(mdlTexture.channelCount) - + // Validate dimensions - guard width > 0 && height > 0 && channelCount > 0 else { + guard width > 0, height > 0, channelCount > 0 else { print("Error: Invalid texture dimensions - width: \(width), height: \(height), channels: \(channelCount)") return nil } - + let bytesPerRow = width * channelCount - + // Create a CGImage from the texture data guard let dataProvider = CGDataProvider(data: texelData as CFData) else { return nil } - + let colorSpace = CGColorSpaceCreateDeviceRGB() - let bitmapInfo: CGBitmapInfo = mdlTexture.channelCount == 4 + let bitmapInfo: CGBitmapInfo = mdlTexture.channelCount == 4 ? CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue) : CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue) - + guard let cgImage = CGImage( width: width, height: height, @@ -142,7 +142,7 @@ func nsImageFromMDLTexture(_ mdlTexture: MDLTexture) -> NSImage? { print("Error: Failed to create CGImage with dimensions \(width)x\(height), channels: \(channelCount)") return nil } - + let size = NSSize(width: CGFloat(width), height: CGFloat(height)) return NSImage(cgImage: cgImage, size: size) } From 882a8d9c3c5696041dc1fa3147a72d30818b9118 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Thu, 4 Dec 2025 06:55:14 -0700 Subject: [PATCH 06/41] [Patch] fixed child-parent selection --- .../Editor/SelectionManager.swift | 52 +++++++++++++++++-- .../Renderer/EditorRenderPasses.swift | 5 +- .../Systems/EditorInputSystemAppKit.swift | 15 +++++- 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/Sources/UntoldEditor/Editor/SelectionManager.swift b/Sources/UntoldEditor/Editor/SelectionManager.swift index 4ee069c..bad14d6 100644 --- a/Sources/UntoldEditor/Editor/SelectionManager.swift +++ b/Sources/UntoldEditor/Editor/SelectionManager.swift @@ -7,6 +7,7 @@ // See the LICENSE file or for details. // import Foundation +import simd import UntoldEngine protocol SelectionDelegate: AnyObject { @@ -42,15 +43,60 @@ class SelectionManager: ObservableObject { func selectEntity(entityId: EntityID) { selectedEntity = entityId - if hasComponent(entityId: entityId, componentType: RenderComponent.self), hasComponent(entityId: entityId, componentType: LocalTransformComponent.self) { + // Check if entity or any of its children have a render component + let hasRenderCapability = entityOrChildrenHaveRenderComponent(entityId: entityId) + + if hasRenderCapability, hasComponent(entityId: entityId, componentType: LocalTransformComponent.self) { activeEntity = entityId - guard let localTransform = scene.get(component: LocalTransformComponent.self, for: activeEntity) else { return } - updateBoundingBoxBuffer(min: localTransform.boundingBox.min, max: localTransform.boundingBox.max) + // Get bounding box from hierarchy (entity or children) + let hierarchyBoundingBox = getHierarchyBoundingBox(entityId: entityId) + updateBoundingBoxBuffer(min: hierarchyBoundingBox.min, max: hierarchyBoundingBox.max) createGizmo(name: "translateGizmo") } else { activeEntity = .invalid } } + + // Helper: Check if entity or any children have RenderComponent + private func entityOrChildrenHaveRenderComponent(entityId: EntityID) -> Bool { + // Check entity itself + if hasComponent(entityId: entityId, componentType: RenderComponent.self) { + return true + } + + // Check children + let children = getEntityChildren(parentId: entityId) + for childId in children { + if hasComponent(entityId: childId, componentType: RenderComponent.self) { + return true + } + } + + return false + } + + // Helper: Get combined bounding box for entity hierarchy + private func getHierarchyBoundingBox(entityId: EntityID) -> (min: simd_float3, max: simd_float3) { + var minBounds = simd_float3(Float.greatestFiniteMagnitude, Float.greatestFiniteMagnitude, Float.greatestFiniteMagnitude) + var maxBounds = simd_float3(-Float.greatestFiniteMagnitude, -Float.greatestFiniteMagnitude, -Float.greatestFiniteMagnitude) + + // Check entity itself + if let localTransform = scene.get(component: LocalTransformComponent.self, for: entityId) { + minBounds = simd_min(minBounds, localTransform.boundingBox.min) + maxBounds = simd_max(maxBounds, localTransform.boundingBox.max) + } + + // Check children + let children = getEntityChildren(parentId: entityId) + for childId in children { + if let childTransform = scene.get(component: LocalTransformComponent.self, for: childId) { + minBounds = simd_min(minBounds, childTransform.boundingBox.min) + maxBounds = simd_max(maxBounds, childTransform.boundingBox.max) + } + } + + return (min: minBounds, max: maxBounds) + } } diff --git a/Sources/UntoldEditor/Renderer/EditorRenderPasses.swift b/Sources/UntoldEditor/Renderer/EditorRenderPasses.swift index d2e224e..0769e70 100644 --- a/Sources/UntoldEditor/Renderer/EditorRenderPasses.swift +++ b/Sources/UntoldEditor/Renderer/EditorRenderPasses.swift @@ -572,10 +572,7 @@ extension RenderPasses { return } - guard let renderComponent = scene.get(component: RenderComponent.self, for: activeEntity) else { - handleError(.noRenderComponent) - return - } + let renderComponent = scene.get(component: RenderComponent.self, for: activeEntity) renderEncoder.setVertexBytes( &cameraComponent.viewSpace, length: MemoryLayout.stride, index: 1 diff --git a/Sources/UntoldEditor/Systems/EditorInputSystemAppKit.swift b/Sources/UntoldEditor/Systems/EditorInputSystemAppKit.swift index 7e45d17..4c9a3b9 100644 --- a/Sources/UntoldEditor/Systems/EditorInputSystemAppKit.swift +++ b/Sources/UntoldEditor/Systems/EditorInputSystemAppKit.swift @@ -175,7 +175,20 @@ activeHitGizmoEntity = .invalid if hit { - activeEntity = entityId + // If hit a child mesh, select the parent instead (except for gizmos) + var hitEntityId = entityId + if hasComponent(entityId: entityId, componentType: GizmoComponent.self) { + // Gizmo hit - select the gizmo directly + hitEntityId = entityId + } else if let parentId = getEntityParent(entityId: entityId) { + // Non-gizmo child hit - select parent + hitEntityId = parentId + } else { + // Entity with no parent - select it directly + hitEntityId = entityId + } + + activeEntity = hitEntityId selectionDelegate?.didSelectEntity(activeEntity) selectionDelegate?.resetActiveAxis() From 80439f5b06ffc7b598626337c2067bd1a3dbc4e1 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Sat, 6 Dec 2025 13:51:56 -0700 Subject: [PATCH 07/41] [Patch] User can double click on usdz to render a model --- .../Editor/AssetBrowserView.swift | 34 +++++++++++++++++-- Sources/UntoldEditor/Editor/EditorView.swift | 1 + 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Sources/UntoldEditor/Editor/AssetBrowserView.swift b/Sources/UntoldEditor/Editor/AssetBrowserView.swift index 41d9011..a4e4eb7 100644 --- a/Sources/UntoldEditor/Editor/AssetBrowserView.swift +++ b/Sources/UntoldEditor/Editor/AssetBrowserView.swift @@ -46,6 +46,7 @@ struct AssetBrowserView: View { @State private var selectedAssetName: String? @ObservedObject var editorBaseAssetPath = EditorAssetBasePath.shared @ObservedObject var selectionManager: SelectionManager + @ObservedObject var sceneGraphModel: SceneGraphModel @State private var folderPathStack: [URL] = [] var editor_addEntityWithAsset: () -> Void private var currentFolderPath: URL? { @@ -199,7 +200,7 @@ struct AssetBrowserView: View { // For Scripts, we never navigate into folders (we won't list folders anyway) assetRow(asset) .onTapGesture(count: 2) { - // Optional double-click behavior can go here. + handle_add_model_double_click(asset: asset) } .onTapGesture(count: 1) { if asset.isFolder { @@ -505,7 +506,7 @@ struct AssetBrowserView: View { ForEach(items) { asset in assetRow(asset) .onTapGesture(count: 2) { - // Intentionally left blank for now + handle_add_model_double_click(asset: asset) } .onTapGesture(count: 1) { if asset.isFolder { @@ -532,4 +533,33 @@ struct AssetBrowserView: View { selectedAsset = asset selectedAssetName = asset.name } + + // MARK: - Add Model with Double Click + + private func handle_add_model_double_click(asset: Asset) { + // Don't handle folders + guard !asset.isFolder else { return } + + // Only handle model files (usdz) + guard asset.category == AssetCategory.models.rawValue, + asset.path.pathExtension.lowercased() == "usdz" else { return } + + // Create entity + let entityId = createEntity() + + // Set entity name based on asset filename + let entityName = asset.path.deletingPathExtension().lastPathComponent + setEntityName(entityId: entityId, name: entityName) + + // Add mesh to entity + let filename = asset.path.deletingPathExtension().lastPathComponent + let withExtension = asset.path.pathExtension + setEntityMesh(entityId: entityId, filename: filename, withExtension: withExtension) + + // Refresh the scene hierarchy to show the new entity + sceneGraphModel.refreshHierarchy() + + // Select the newly created entity in the editor + selectionManager.selectedEntity = entityId + } } diff --git a/Sources/UntoldEditor/Editor/EditorView.swift b/Sources/UntoldEditor/Editor/EditorView.swift index 14679fc..6ff8a4f 100644 --- a/Sources/UntoldEditor/Editor/EditorView.swift +++ b/Sources/UntoldEditor/Editor/EditorView.swift @@ -69,6 +69,7 @@ public struct EditorView: View { assets: $assets, selectedAsset: $selectedAsset, selectionManager: selectionManager, + sceneGraphModel: sceneGraphModel, editor_addEntityWithAsset: editor_addEntityWithAsset ) .frame(width: 400) From c77c2532316c9c54c1169f878d476d03a49d9a0f Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Sat, 6 Dec 2025 13:53:57 -0700 Subject: [PATCH 08/41] [Patch] User can double click ply to render gaussian --- .../Editor/AssetBrowserView.swift | 56 +++++++++++++------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/Sources/UntoldEditor/Editor/AssetBrowserView.swift b/Sources/UntoldEditor/Editor/AssetBrowserView.swift index a4e4eb7..6dc1176 100644 --- a/Sources/UntoldEditor/Editor/AssetBrowserView.swift +++ b/Sources/UntoldEditor/Editor/AssetBrowserView.swift @@ -540,26 +540,46 @@ struct AssetBrowserView: View { // Don't handle folders guard !asset.isFolder else { return } - // Only handle model files (usdz) - guard asset.category == AssetCategory.models.rawValue, - asset.path.pathExtension.lowercased() == "usdz" else { return } - - // Create entity - let entityId = createEntity() - - // Set entity name based on asset filename - let entityName = asset.path.deletingPathExtension().lastPathComponent - setEntityName(entityId: entityId, name: entityName) - - // Add mesh to entity let filename = asset.path.deletingPathExtension().lastPathComponent let withExtension = asset.path.pathExtension - setEntityMesh(entityId: entityId, filename: filename, withExtension: withExtension) - // Refresh the scene hierarchy to show the new entity - sceneGraphModel.refreshHierarchy() - - // Select the newly created entity in the editor - selectionManager.selectedEntity = entityId + // Handle model files (usdz) + if asset.category == AssetCategory.models.rawValue, + withExtension.lowercased() == "usdz" { + + // Create entity + let entityId = createEntity() + + // Set entity name based on asset filename + setEntityName(entityId: entityId, name: filename) + + // Add mesh to entity + setEntityMesh(entityId: entityId, filename: filename, withExtension: withExtension) + + // Refresh the scene hierarchy to show the new entity + sceneGraphModel.refreshHierarchy() + + // Select the newly created entity in the editor + selectionManager.selectedEntity = entityId + } + // Handle Gaussian files (ply) + else if asset.category == AssetCategory.gaussians.rawValue, + withExtension.lowercased() == "ply" { + + // Create entity + let entityId = createEntity() + + // Set entity name based on asset filename + setEntityName(entityId: entityId, name: filename) + + // Add Gaussian component to entity + setEntityGaussian(entityId: entityId, filename: filename, withExtension: withExtension) + + // Refresh the scene hierarchy to show the new entity + sceneGraphModel.refreshHierarchy() + + // Select the newly created entity in the editor + selectionManager.selectedEntity = entityId + } } } From 10f19f90db53ab235e8cdf5b2f49022d8bc68d18 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Sat, 6 Dec 2025 14:06:51 -0700 Subject: [PATCH 09/41] [Patch] Double click to add animation --- .../Editor/AssetBrowserView.swift | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Sources/UntoldEditor/Editor/AssetBrowserView.swift b/Sources/UntoldEditor/Editor/AssetBrowserView.swift index 6dc1176..b269219 100644 --- a/Sources/UntoldEditor/Editor/AssetBrowserView.swift +++ b/Sources/UntoldEditor/Editor/AssetBrowserView.swift @@ -581,5 +581,32 @@ struct AssetBrowserView: View { // Select the newly created entity in the editor selectionManager.selectedEntity = entityId } + // Handle Animation files (usdz in Animations category) + else if asset.category == AssetCategory.animations.rawValue, + withExtension.lowercased() == "usdz" { + + // Animations require a selected entity to work with + guard let entityId = selectionManager.selectedEntity, + entityId != .invalid else { + print("⚠️ Please select an entity first to add animation") + return + } + + // Add AnimationComponent if not already present + if !hasComponent(entityId: entityId, componentType: AnimationComponent.self) { + registerComponent(entityId: entityId, componentType: AnimationComponent.self) + } + + // Add the animation to the entity + setEntityAnimations(entityId: entityId, filename: filename, withExtension: withExtension, name: filename) + + // Store the animation file URL in the component + if let animationComponent = scene.get(component: AnimationComponent.self, for: entityId) { + animationComponent.animationsFilenames.append(asset.path) + } + + // Refresh view + selectionManager.objectWillChange.send() + } } } From f224e49b3f33f116b53251b397764d1c989e6904 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Sat, 6 Dec 2025 14:17:50 -0700 Subject: [PATCH 10/41] [Patch] Double click to add script --- .../Editor/AssetBrowserView.swift | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Sources/UntoldEditor/Editor/AssetBrowserView.swift b/Sources/UntoldEditor/Editor/AssetBrowserView.swift index b269219..f998ccc 100644 --- a/Sources/UntoldEditor/Editor/AssetBrowserView.swift +++ b/Sources/UntoldEditor/Editor/AssetBrowserView.swift @@ -608,5 +608,49 @@ struct AssetBrowserView: View { // Refresh view selectionManager.objectWillChange.send() } + // Handle Script files (uscript) + else if asset.category == AssetCategory.scripts.rawValue, + withExtension.lowercased() == "uscript" { + + // Scripts require a selected entity to work with + guard let entityId = selectionManager.selectedEntity, + entityId != .invalid else { + print("⚠️ Please select an entity first to add script") + return + } + + // Get or create ScriptComponent + let scriptComponent: ScriptComponent + if let existing = scene.get(component: ScriptComponent.self, for: entityId) { + scriptComponent = existing + } else { + guard let newComp = scene.assign(to: entityId, component: ScriptComponent.self) else { + print("❌ Failed to create ScriptComponent") + return + } + scriptComponent = newComp + } + + // Load and append the script + do { + let jsonData = try Data(contentsOf: asset.path) + let decoder = JSONDecoder() + let loadedScript = try decoder.decode(USCScript.self, from: jsonData) + + // Append script and its path + scriptComponent.scripts.append(loadedScript) + if scriptComponent.scriptFilePaths == nil { + scriptComponent.scriptFilePaths = [] + } + scriptComponent.scriptFilePaths?.append(asset.path.path) + + print("✅ Script added: \(loadedScript.name)") + + // Refresh view + selectionManager.objectWillChange.send() + } catch { + print("❌ Failed to load script: \(error.localizedDescription)") + } + } } } From 9db3a1a44b5a68c6ec76bc9a16da125245d1260a Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Sat, 6 Dec 2025 14:34:32 -0700 Subject: [PATCH 11/41] [Patch] Double click to load scene --- .../Editor/AssetBrowserView.swift | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/Sources/UntoldEditor/Editor/AssetBrowserView.swift b/Sources/UntoldEditor/Editor/AssetBrowserView.swift index f998ccc..5931b4a 100644 --- a/Sources/UntoldEditor/Editor/AssetBrowserView.swift +++ b/Sources/UntoldEditor/Editor/AssetBrowserView.swift @@ -48,6 +48,8 @@ struct AssetBrowserView: View { @ObservedObject var selectionManager: SelectionManager @ObservedObject var sceneGraphModel: SceneGraphModel @State private var folderPathStack: [URL] = [] + @State private var showSceneLoadConfirmation = false + @State private var pendingSceneToLoad: URL? var editor_addEntityWithAsset: () -> Void private var currentFolderPath: URL? { folderPathStack.last @@ -244,6 +246,19 @@ struct AssetBrowserView: View { .onReceive(NotificationCenter.default.publisher(for: .assetBrowserReload)) { _ in loadAssets() } + .alert("Load Scene?", isPresented: $showSceneLoadConfirmation) { + Button("Cancel", role: .cancel) { + pendingSceneToLoad = nil + } + Button("Load Scene", role: .destructive) { + if let sceneURL = pendingSceneToLoad { + loadScene(from: sceneURL) + } + pendingSceneToLoad = nil + } + } message: { + Text("Loading a new scene will replace the current scene. Any unsaved changes will be lost.") + } } // MARK: - Select Resource Directory @@ -652,5 +667,44 @@ struct AssetBrowserView: View { print("❌ Failed to load script: \(error.localizedDescription)") } } + // Handle Scene files (json) + else if asset.category == AssetCategory.scenes.rawValue, + withExtension.lowercased() == "json" { + + // Show confirmation dialog before loading scene + pendingSceneToLoad = asset.path + showSceneLoadConfirmation = true + } + } + + // MARK: - Load Scene Helper + + private func loadScene(from url: URL) { + guard let sceneData = loadGameScene(from: url) else { + print("❌ Failed to load scene from \(url.lastPathComponent)") + return + } + + // Clear current scene + destroyAllEntities() + removeGizmo() + EditorComponentsState.shared.clear() + + // Load new scene + deserializeScene(sceneData: sceneData) + + // Reset editor state + selectionManager.selectedEntity = nil + activeEntity = .invalid + gizmoActive = false + + // Refresh UI + selectionManager.objectWillChange.send() + sceneGraphModel.refreshHierarchy() + + // Reset camera + CameraSystem.shared.activeCamera = findSceneCamera() + + print("✅ Scene loaded: \(url.lastPathComponent)") } } From 6256fb58645005ee11fecc1c88921f3c194d9ab1 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Sat, 6 Dec 2025 14:41:47 -0700 Subject: [PATCH 12/41] [Patch] Double click to add HDR --- Sources/UntoldEditor/Editor/AssetBrowserView.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Sources/UntoldEditor/Editor/AssetBrowserView.swift b/Sources/UntoldEditor/Editor/AssetBrowserView.swift index 5931b4a..3b65853 100644 --- a/Sources/UntoldEditor/Editor/AssetBrowserView.swift +++ b/Sources/UntoldEditor/Editor/AssetBrowserView.swift @@ -675,6 +675,17 @@ struct AssetBrowserView: View { pendingSceneToLoad = asset.path showSceneLoadConfirmation = true } + // Handle HDR files (hdr) + else if asset.category == AssetCategory.hdr.rawValue, + withExtension.lowercased() == "hdr" { + + // Load HDR as environment IBL + let filename = asset.path.lastPathComponent + let directoryURL = asset.path.deletingLastPathComponent() + generateHDR(filename, from: directoryURL) + + print("✅ HDR environment loaded: \(filename)") + } } // MARK: - Load Scene Helper From fce6527db1dbeba1017b99f433f05749d18efedd Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Sat, 6 Dec 2025 15:03:59 -0700 Subject: [PATCH 13/41] [Patch] AssetBrowserView shows usdz not folder --- .../Editor/AssetBrowserView.swift | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/Sources/UntoldEditor/Editor/AssetBrowserView.swift b/Sources/UntoldEditor/Editor/AssetBrowserView.swift index 3b65853..765d206 100644 --- a/Sources/UntoldEditor/Editor/AssetBrowserView.swift +++ b/Sources/UntoldEditor/Editor/AssetBrowserView.swift @@ -399,7 +399,7 @@ struct AssetBrowserView: View { if category == .scripts { // Flat list of .uscript files anywhere under Scripts - let uscriptURLs = findUScriptFilesRecursively(at: categoryPath) + let uscriptURLs = findFilesRecursively(at: categoryPath, withExtension: "uscript") for url in uscriptURLs { categoryAssets.append( Asset(name: url.lastPathComponent, @@ -413,6 +413,23 @@ struct AssetBrowserView: View { groupedAssets[category.rawValue] = categoryAssets continue } + + // Flat list for Models and Animations - show .usdz files directly + if category == .models || category == .animations { + let usdzURLs = findFilesRecursively(at: categoryPath, withExtension: "usdz") + for url in usdzURLs { + categoryAssets.append( + Asset(name: url.lastPathComponent, + category: category.rawValue, + path: url, + isFolder: false) + ) + } + // Sort for stable UI + categoryAssets.sort { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending } + groupedAssets[category.rawValue] = categoryAssets + continue + } // Non-Scripts categories: list immediate children, with folder navigation support if let contents = try? FileManager.default.contentsOfDirectory( @@ -462,8 +479,8 @@ struct AssetBrowserView: View { assets = groupedAssets } - // Recursively find all .uscript files under a root directory - private func findUScriptFilesRecursively(at root: URL) -> [URL] { + // Recursively find all files with a specific extension under a root directory + private func findFilesRecursively(at root: URL, withExtension ext: String) -> [URL] { var results: [URL] = [] let fm = FileManager.default @@ -472,7 +489,7 @@ struct AssetBrowserView: View { } for case let url as URL in enumerator { - if url.pathExtension.lowercased() == "uscript" { + if url.pathExtension.lowercased() == ext.lowercased() { results.append(url) } } From 360dbde6a1269d315eceaf77cc750147ebaad1e3 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Mon, 8 Dec 2025 22:16:06 -0700 Subject: [PATCH 14/41] [Patch] enable game camera in game mode in editor --- Sources/UntoldEditor/Editor/EditorView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/UntoldEditor/Editor/EditorView.swift b/Sources/UntoldEditor/Editor/EditorView.swift index 6ff8a4f..3ba8dd3 100644 --- a/Sources/UntoldEditor/Editor/EditorView.swift +++ b/Sources/UntoldEditor/Editor/EditorView.swift @@ -249,7 +249,7 @@ public struct EditorView: View { self.isPlaying = isPlaying gameMode = !gameMode // For now, during "play" mode, the camera will keep being the scene camera - // CameraSystem.shared.activeCamera = gameMode ? findGameCamera() : findSceneCamera() + CameraSystem.shared.activeCamera = gameMode ? findGameCamera() : findSceneCamera() AnimationSystem.shared.isEnabled = isPlaying // Start/stop USC System From 7fefa422de25fa888d8e8dedd783100f0325caab Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Mon, 8 Dec 2025 22:30:04 -0700 Subject: [PATCH 15/41] [Patch] Numeric input field commits on tab --- .../Editor/NumericInputView.swift | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Sources/UntoldEditor/Editor/NumericInputView.swift b/Sources/UntoldEditor/Editor/NumericInputView.swift index e3f1959..a4c22fd 100644 --- a/Sources/UntoldEditor/Editor/NumericInputView.swift +++ b/Sources/UntoldEditor/Editor/NumericInputView.swift @@ -15,6 +15,7 @@ @Binding var value: SIMD3 @State private var tempValues: [String] = ["0", "0", "0"] @FocusState private var focusedField: Int? + @State private var lastFocusedField: Int? public init(label: String, value: Binding>) { self.label = label @@ -44,6 +45,15 @@ } focusedField = nil } + .onChange(of: focusedField) { oldValue, newValue in + // Commit when this field loses focus (e.g., via Tab) + if oldValue == index, newValue != index { + if let newValue = Float(tempValues[index]) { + value[index] = newValue + } + } + lastFocusedField = newValue + } } } } @@ -59,6 +69,7 @@ @Binding var value: Float @State private var tempValues: String = "0" @FocusState private var focusedField: Int? + @State private var wasFocused: Bool = false public init(label: String, value: Binding) { self.label = label @@ -87,6 +98,15 @@ } focusedField = nil } + .onChange(of: focusedField) { _, newValue in + // Commit when focus leaves (e.g., Tab) + if wasFocused, newValue != 1 { + if let newValue = Float(tempValues) { + value = newValue + } + } + wasFocused = (newValue == 1) + } } } .padding(.vertical, 4) From 45a46d32a6f2370f4654943b378277e76ca92fe6 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Mon, 8 Dec 2025 22:30:34 -0700 Subject: [PATCH 16/41] [Patch] Formatted files --- .../Editor/AssetBrowserView.swift | 92 ++++++++++--------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/Sources/UntoldEditor/Editor/AssetBrowserView.swift b/Sources/UntoldEditor/Editor/AssetBrowserView.swift index 765d206..438bf5f 100644 --- a/Sources/UntoldEditor/Editor/AssetBrowserView.swift +++ b/Sources/UntoldEditor/Editor/AssetBrowserView.swift @@ -413,7 +413,7 @@ struct AssetBrowserView: View { groupedAssets[category.rawValue] = categoryAssets continue } - + // Flat list for Models and Animations - show .usdz files directly if category == .models || category == .animations { let usdzURLs = findFilesRecursively(at: categoryPath, withExtension: "usdz") @@ -565,92 +565,94 @@ struct AssetBrowserView: View { selectedAsset = asset selectedAssetName = asset.name } - + // MARK: - Add Model with Double Click - + private func handle_add_model_double_click(asset: Asset) { // Don't handle folders guard !asset.isFolder else { return } - + let filename = asset.path.deletingPathExtension().lastPathComponent let withExtension = asset.path.pathExtension - + // Handle model files (usdz) if asset.category == AssetCategory.models.rawValue, - withExtension.lowercased() == "usdz" { - + withExtension.lowercased() == "usdz" + { // Create entity let entityId = createEntity() - + // Set entity name based on asset filename setEntityName(entityId: entityId, name: filename) - + // Add mesh to entity setEntityMesh(entityId: entityId, filename: filename, withExtension: withExtension) - + // Refresh the scene hierarchy to show the new entity sceneGraphModel.refreshHierarchy() - + // Select the newly created entity in the editor selectionManager.selectedEntity = entityId } // Handle Gaussian files (ply) else if asset.category == AssetCategory.gaussians.rawValue, - withExtension.lowercased() == "ply" { - + withExtension.lowercased() == "ply" + { // Create entity let entityId = createEntity() - + // Set entity name based on asset filename setEntityName(entityId: entityId, name: filename) - + // Add Gaussian component to entity setEntityGaussian(entityId: entityId, filename: filename, withExtension: withExtension) - + // Refresh the scene hierarchy to show the new entity sceneGraphModel.refreshHierarchy() - + // Select the newly created entity in the editor selectionManager.selectedEntity = entityId } // Handle Animation files (usdz in Animations category) else if asset.category == AssetCategory.animations.rawValue, - withExtension.lowercased() == "usdz" { - + withExtension.lowercased() == "usdz" + { // Animations require a selected entity to work with guard let entityId = selectionManager.selectedEntity, - entityId != .invalid else { + entityId != .invalid + else { print("⚠️ Please select an entity first to add animation") return } - + // Add AnimationComponent if not already present if !hasComponent(entityId: entityId, componentType: AnimationComponent.self) { registerComponent(entityId: entityId, componentType: AnimationComponent.self) } - + // Add the animation to the entity setEntityAnimations(entityId: entityId, filename: filename, withExtension: withExtension, name: filename) - + // Store the animation file URL in the component if let animationComponent = scene.get(component: AnimationComponent.self, for: entityId) { animationComponent.animationsFilenames.append(asset.path) } - + // Refresh view selectionManager.objectWillChange.send() } // Handle Script files (uscript) else if asset.category == AssetCategory.scripts.rawValue, - withExtension.lowercased() == "uscript" { - + withExtension.lowercased() == "uscript" + { // Scripts require a selected entity to work with guard let entityId = selectionManager.selectedEntity, - entityId != .invalid else { + entityId != .invalid + else { print("⚠️ Please select an entity first to add script") return } - + // Get or create ScriptComponent let scriptComponent: ScriptComponent if let existing = scene.get(component: ScriptComponent.self, for: entityId) { @@ -662,22 +664,22 @@ struct AssetBrowserView: View { } scriptComponent = newComp } - + // Load and append the script do { let jsonData = try Data(contentsOf: asset.path) let decoder = JSONDecoder() let loadedScript = try decoder.decode(USCScript.self, from: jsonData) - + // Append script and its path scriptComponent.scripts.append(loadedScript) if scriptComponent.scriptFilePaths == nil { scriptComponent.scriptFilePaths = [] } scriptComponent.scriptFilePaths?.append(asset.path.path) - + print("✅ Script added: \(loadedScript.name)") - + // Refresh view selectionManager.objectWillChange.send() } catch { @@ -686,53 +688,53 @@ struct AssetBrowserView: View { } // Handle Scene files (json) else if asset.category == AssetCategory.scenes.rawValue, - withExtension.lowercased() == "json" { - + withExtension.lowercased() == "json" + { // Show confirmation dialog before loading scene pendingSceneToLoad = asset.path showSceneLoadConfirmation = true } // Handle HDR files (hdr) else if asset.category == AssetCategory.hdr.rawValue, - withExtension.lowercased() == "hdr" { - + withExtension.lowercased() == "hdr" + { // Load HDR as environment IBL let filename = asset.path.lastPathComponent let directoryURL = asset.path.deletingLastPathComponent() generateHDR(filename, from: directoryURL) - + print("✅ HDR environment loaded: \(filename)") } } - + // MARK: - Load Scene Helper - + private func loadScene(from url: URL) { guard let sceneData = loadGameScene(from: url) else { print("❌ Failed to load scene from \(url.lastPathComponent)") return } - + // Clear current scene destroyAllEntities() removeGizmo() EditorComponentsState.shared.clear() - + // Load new scene deserializeScene(sceneData: sceneData) - + // Reset editor state selectionManager.selectedEntity = nil activeEntity = .invalid gizmoActive = false - + // Refresh UI selectionManager.objectWillChange.send() sceneGraphModel.refreshHierarchy() - + // Reset camera CameraSystem.shared.activeCamera = findSceneCamera() - + print("✅ Scene loaded: \(url.lastPathComponent)") } } From e323be8d7936bb0a8efde986d5763c0048e3fe34 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 9 Dec 2025 11:41:21 -0700 Subject: [PATCH 17/41] [Patch] Added clear flag to console --- Sources/UntoldEditor/Editor/LogConsoleView.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Sources/UntoldEditor/Editor/LogConsoleView.swift b/Sources/UntoldEditor/Editor/LogConsoleView.swift index 8bf6510..b172bec 100644 --- a/Sources/UntoldEditor/Editor/LogConsoleView.swift +++ b/Sources/UntoldEditor/Editor/LogConsoleView.swift @@ -16,6 +16,7 @@ struct LogConsoleView: View { @State private var selectedLevel: LogLevel? = nil @State private var search = "" @State private var autoScroll = true + @State private var clearLog = false private func passes(_ e: LogEvent) -> Bool { (selectedLevel == nil || e.level == selectedLevel!) && @@ -38,6 +39,15 @@ struct LogConsoleView: View { Toggle("Auto‑scroll", isOn: $autoScroll) .toggleStyle(.checkbox) + + Toggle("Clear", isOn: $clearLog) + .toggleStyle(.checkbox) + .onChange(of: clearLog) { _, newValue in + if newValue { + LogStore.shared.clear() + clearLog = false + } + } } .padding(.horizontal, 10) .padding(.vertical, 6) From bb11c675bb2bb85642891ee0b81f2c380b915323 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 9 Dec 2025 15:50:41 -0700 Subject: [PATCH 18/41] [Patch] Added CodeEditorView --- Sources/UntoldEditor/Editor/ToolbarView.swift | 263 ++++++++++++++++++ .../Systems/ScriptProjectManager.swift | 33 +++ 2 files changed, 296 insertions(+) diff --git a/Sources/UntoldEditor/Editor/ToolbarView.swift b/Sources/UntoldEditor/Editor/ToolbarView.swift index 8fe668b..cd4db96 100644 --- a/Sources/UntoldEditor/Editor/ToolbarView.swift +++ b/Sources/UntoldEditor/Editor/ToolbarView.swift @@ -8,6 +8,9 @@ // #if canImport(AppKit) import SwiftUI + import CodeEditorView + import LanguageSupport + import AppKit struct ToolbarView: View { @ObservedObject var selectionManager: SelectionManager @@ -30,6 +33,7 @@ @State private var showBuildSettings = false @State private var showingNewScriptDialog = false @State private var newScriptName = "" + @State private var showingScriptEditor = false var body: some View { HStack { @@ -65,6 +69,9 @@ } ) } + .sheet(isPresented: $showingScriptEditor) { + ScriptEditorSheet(isPresented: $showingScriptEditor) + } } var rightSection: some View { @@ -209,6 +216,21 @@ .cornerRadius(5) } .buttonStyle(.plain) + + // Open in-app script editor + Button(action: { showingScriptEditor = true }) { + HStack(spacing: 4) { + Image(systemName: "chevron.left.forwardslash.chevron.right") + Text("Script Editor") + } + .font(.system(size: 12)) + .foregroundColor(.white) + .padding(.vertical, 4) + .padding(.horizontal, 8) + .background(Color.gray.opacity(0.9)) + .cornerRadius(5) + } + .buttonStyle(.plain) } } @@ -330,4 +352,245 @@ .frame(width: 400) } } + + struct ScriptEditorSheet: View { + @Binding var isPresented: Bool + + @State private var scriptFiles: [URL] = [] + @State private var selectedFile: URL? + @State private var scriptText: String = "" + @State private var editPosition: CodeEditor.Position = .init() + @State private var messages: Set> = [] + @State private var statusMessage: String? + @State private var isBuilding = false + @State private var buildOutput: String = "" + @State private var keyMonitor: Any? + + @Environment(\.colorScheme) private var colorScheme + + private let manager = ScriptProjectManager.shared + + var body: some View { + VStack(spacing: 0) { + header + Divider() + HStack(spacing: 0) { + scriptList + Divider() + editorPanel + } + Divider() + buildLog + } + .frame(minWidth: 1100, minHeight: 700) + .onAppear { + prepareScripts() + installControlCopyPasteShortcuts() + } + .onDisappear { + removeControlCopyPasteShortcuts() + } + } + + private var header: some View { + HStack { + VStack(alignment: .leading, spacing: 4) { + Text("Script Editor") + .font(.headline) + if let statusMessage { + Text(statusMessage) + .font(.caption) + .foregroundColor(.secondary) + } + } + + Spacer() + + Button("Build Scripts") { + runBuild() + } + .buttonStyle(.borderedProminent) + .disabled(isBuilding) + + Button("Save") { + saveCurrentScript() + } + .disabled(selectedFile == nil) + + Button("Close") { + isPresented = false + } + } + .padding() + } + + private var scriptList: some View { + List(selection: $selectedFile) { + ForEach(scriptFiles, id: \.self) { url in + Text(url.lastPathComponent) + .lineLimit(1) + .tag(Optional(url)) + } + } + .frame(minWidth: 150, idealWidth: 175, maxWidth: 200, maxHeight: .infinity) + .onChange(of: selectedFile) { _ in + loadSelectedScript() + } + } + + private var editorPanel: some View { + VStack(alignment: .leading, spacing: 8) { + if let selectedFile { + Text(selectedFile.lastPathComponent) + .font(.subheadline) + .foregroundColor(.secondary) + CodeEditor( + text: $scriptText, + position: $editPosition, + messages: $messages, + language: .swift() + ) + .environment( + \.codeEditorTheme, + colorScheme == .dark ? Theme.defaultDark : Theme.defaultLight + ) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(NSColor.textBackgroundColor)) + .cornerRadius(6) + } else { + VStack { + Text("Select a script to edit") + .foregroundColor(.secondary) + Spacer() + } + } + } + .padding() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .layoutPriority(1) + } + + private var buildLog: some View { + VStack(alignment: .leading, spacing: 8) { + HStack { + Text("Build Output") + .font(.subheadline.bold()) + if isBuilding { + ProgressView() + .progressViewStyle(.circular) + } + } + ScrollView { + Text(buildOutput.isEmpty ? "No builds yet." : buildOutput) + .font(.system(.body, design: .monospaced)) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(4) + } + .frame(maxWidth: .infinity) + } + .padding() + .frame(minHeight: 160, maxHeight: 200) + } + + private func prepareScripts() { + do { + if !manager.isProjectInitialized() { + try manager.initializeProject() + } + statusMessage = nil + } catch { + statusMessage = "Failed to initialize scripts: \(error.localizedDescription)" + } + + reloadScripts() + } + + private func reloadScripts() { + scriptFiles = manager.listScriptFiles().sorted { $0.lastPathComponent < $1.lastPathComponent } + if selectedFile == nil { + selectedFile = scriptFiles.first + } + loadSelectedScript() + } + + private func loadSelectedScript() { + guard let selectedFile else { + scriptText = "" + return + } + + do { + scriptText = try String(contentsOf: selectedFile, encoding: .utf8) + statusMessage = nil + } catch { + scriptText = "" + statusMessage = "Failed to load \(selectedFile.lastPathComponent)" + } + } + + private func saveCurrentScript() { + guard let selectedFile else { return } + + do { + try scriptText.write(to: selectedFile, atomically: true, encoding: .utf8) + statusMessage = "Saved \(selectedFile.lastPathComponent)" + } catch { + statusMessage = "Failed to save \(selectedFile.lastPathComponent)" + } + } + + private func runBuild() { + // Ensure current edits are saved before building + saveCurrentScript() + + isBuilding = true + buildOutput = "Running swift run...\n" + + manager.buildScripts { result in + isBuilding = false + + switch result { + case .success(let output): + buildOutput += output + statusMessage = "Scripts built successfully" + case .failure(let error): + buildOutput += error.localizedDescription + statusMessage = "Build failed" + } + } + } + + // Map Cmd+C/V/X/A to standard copy/paste/cut/select-all while the sheet is active. + private func installControlCopyPasteShortcuts() { + keyMonitor = NSEvent.addLocalMonitorForEvents(matching: [.keyDown]) { event in + guard event.modifierFlags.contains(.command), + let character = event.charactersIgnoringModifiers?.lowercased() + else { return event } + + switch character { + case "c": + NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: nil) + return nil + case "v": + NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: nil) + return nil + case "x": + NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: nil) + return nil + case "a": + NSApp.sendAction(#selector(NSText.selectAll(_:)), to: nil, from: nil) + return nil + default: + return event + } + } + } + + private func removeControlCopyPasteShortcuts() { + if let monitor = keyMonitor { + NSEvent.removeMonitor(monitor) + keyMonitor = nil + } + } + } #endif diff --git a/Sources/UntoldEditor/Systems/ScriptProjectManager.swift b/Sources/UntoldEditor/Systems/ScriptProjectManager.swift index c4c284e..36d56c1 100644 --- a/Sources/UntoldEditor/Systems/ScriptProjectManager.swift +++ b/Sources/UntoldEditor/Systems/ScriptProjectManager.swift @@ -126,6 +126,9 @@ class ScriptProjectManager { let scriptContent = generateScriptTemplate(name: name) try scriptContent.write(to: scriptPath, atomically: true, encoding: .utf8) + // Ensure GenerateScripts main triggers this generator + try addScriptInvocationToMain(name: name) + print("✅ Created script: \(scriptPath.path)") } @@ -200,6 +203,35 @@ class ScriptProjectManager { // MARK: - Template Generators + /// Adds a generate(to:) invocation into GenerateScripts.swift if not present. + private func addScriptInvocationToMain(name: String) throws { + guard let sourcesDir = sourcesDirectory() else { return } + let generateScriptsPath = sourcesDir.appendingPathComponent("GenerateScripts.swift") + + var contents = try String(contentsOf: generateScriptsPath, encoding: .utf8) + + let callLine = "generate\(name)(to: outputDir)" + if contents.contains(callLine) { + return + } + + let anchor = "print(\"✅ All scripts generated in Generated/\")" + if let range = contents.range(of: anchor) { + // Preserve existing indentation from the anchor line + let lineStart = contents[.. String { """ // swift-tools-version: 5.10 @@ -254,6 +286,7 @@ class ScriptProjectManager { // TODO: Call your script generation functions here // Example: generatePlayerController(to: outputDir) + // New scripts created in the editor are auto-added below. print("✅ All scripts generated in Generated/") } From bf0de639a9295b1157da0eb9dad7792f5f150922 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 9 Dec 2025 22:41:21 -0700 Subject: [PATCH 19/41] [Patch] Added SPM for CodeEditorView --- Package.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Package.swift b/Package.swift index ed0655c..1eecfba 100644 --- a/Package.swift +++ b/Package.swift @@ -10,6 +10,7 @@ let package = Package( .executable(name: "UntoldEditor", targets: ["UntoldEditor"]), ], dependencies: [ + .package(url: "https://github.com/mchakravarty/CodeEditorView.git", branch: "main"), // Use a branch during active development: // .package(url: "https://github.com/untoldengine/UntoldEngine.git", branch: "develop"), // Or pin to a release: @@ -20,6 +21,8 @@ let package = Package( name: "UntoldEditor", dependencies: [ .product(name: "UntoldEngine", package: "UntoldEngine"), + .product(name: "CodeEditorView", package: "CodeEditorView"), + .product(name: "LanguageSupport", package: "CodeEditorView"), ], path: "Sources/UntoldEditor", resources: [ From 6a674807acd62e1cac7f55bc247903120b38feec Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 9 Dec 2025 23:55:50 -0700 Subject: [PATCH 20/41] [Patch] Added delete file to code view editor --- Sources/UntoldEditor/Editor/ToolbarView.swift | 82 +++++++++++++++++++ .../Systems/ScriptProjectManager.swift | 17 ++++ 2 files changed, 99 insertions(+) diff --git a/Sources/UntoldEditor/Editor/ToolbarView.swift b/Sources/UntoldEditor/Editor/ToolbarView.swift index cd4db96..a40ea20 100644 --- a/Sources/UntoldEditor/Editor/ToolbarView.swift +++ b/Sources/UntoldEditor/Editor/ToolbarView.swift @@ -365,6 +365,9 @@ @State private var isBuilding = false @State private var buildOutput: String = "" @State private var keyMonitor: Any? + @State private var undoStack: [String] = [] + @State private var lastSavedText: String = "" + @State private var showDeleteConfirm = false @Environment(\.colorScheme) private var colorScheme @@ -417,6 +420,35 @@ } .disabled(selectedFile == nil) + Button("Revert to Saved") { + revertToLastSaved() + } + .disabled(selectedFile == nil) + + Button("Undo Last Change") { + undoLastChange() + } + .disabled(undoStack.isEmpty) + + Button("Delete") { + showDeleteConfirm = true + } + .disabled(selectedFile == nil || isProtectedFile(selectedFile)) + .confirmationDialog( + "Delete script?", + isPresented: $showDeleteConfirm, + titleVisibility: .visible + ) { + Button("Delete", role: .destructive) { + deleteSelectedScript() + } + Button("Cancel", role: .cancel) { showDeleteConfirm = false } + } message: { + if let selectedFile { + Text("Are you sure you want to delete \(selectedFile.lastPathComponent)? This cannot be undone.") + } + } + Button("Close") { isPresented = false } @@ -522,6 +554,8 @@ do { scriptText = try String(contentsOf: selectedFile, encoding: .utf8) statusMessage = nil + lastSavedText = scriptText + undoStack.removeAll() } catch { scriptText = "" statusMessage = "Failed to load \(selectedFile.lastPathComponent)" @@ -532,8 +566,12 @@ guard let selectedFile else { return } do { + if scriptText != lastSavedText { + undoStack.append(lastSavedText) + } try scriptText.write(to: selectedFile, atomically: true, encoding: .utf8) statusMessage = "Saved \(selectedFile.lastPathComponent)" + lastSavedText = scriptText } catch { statusMessage = "Failed to save \(selectedFile.lastPathComponent)" } @@ -560,6 +598,50 @@ } } + private func revertToLastSaved() { + guard selectedFile != nil else { return } + scriptText = lastSavedText + statusMessage = "Reverted to last saved." + } + + private func undoLastChange() { + guard let previous = undoStack.popLast() else { + statusMessage = "Nothing to undo." + return + } + scriptText = previous + statusMessage = "Reverted last change." + } + + private func deleteSelectedScript() { + guard let file = selectedFile else { return } + guard !isProtectedFile(file) else { + statusMessage = "Cannot delete protected file." + return + } + do { + try FileManager.default.removeItem(at: file) + statusMessage = "Deleted \(file.lastPathComponent)" + showDeleteConfirm = false + manager.removeScriptInvocationFromMain(name: file.deletingPathExtension().lastPathComponent) + reloadScripts() + if scriptFiles.isEmpty { + selectedFile = nil + scriptText = "" + } else { + selectedFile = scriptFiles.first + loadSelectedScript() + } + } catch { + statusMessage = "Failed to delete \(file.lastPathComponent)" + } + } + + private func isProtectedFile(_ url: URL?) -> Bool { + guard let url else { return true } + return url.lastPathComponent == "GenerateScripts.swift" + } + // Map Cmd+C/V/X/A to standard copy/paste/cut/select-all while the sheet is active. private func installControlCopyPasteShortcuts() { keyMonitor = NSEvent.addLocalMonitorForEvents(matching: [.keyDown]) { event in diff --git a/Sources/UntoldEditor/Systems/ScriptProjectManager.swift b/Sources/UntoldEditor/Systems/ScriptProjectManager.swift index 36d56c1..1c1453a 100644 --- a/Sources/UntoldEditor/Systems/ScriptProjectManager.swift +++ b/Sources/UntoldEditor/Systems/ScriptProjectManager.swift @@ -329,4 +329,21 @@ class ScriptProjectManager { .DS_Store """ } + + /// Removes a generate(to:) invocation from GenerateScripts.swift if present. + func removeScriptInvocationFromMain(name: String) { + guard let sourcesDir = sourcesDirectory() else { return } + let generateScriptsPath = sourcesDir.appendingPathComponent("GenerateScripts.swift") + + guard FileManager.default.fileExists(atPath: generateScriptsPath.path), + var contents = try? String(contentsOf: generateScriptsPath, encoding: .utf8) + else { return } + + let callLine = "generate\(name)(to: outputDir)" + if contents.contains(callLine) { + contents = contents.replacingOccurrences(of: callLine + "\n", with: "") + contents = contents.replacingOccurrences(of: callLine, with: "") + try? contents.write(to: generateScriptsPath, atomically: true, encoding: .utf8) + } + } } From edce83f8725483485b469328ad7df3ece1d2f198 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Sun, 14 Dec 2025 06:56:35 -0700 Subject: [PATCH 21/41] [Patch] Made the editor sheet a window instead --- Sources/UntoldEditor/Editor/ToolbarView.swift | 139 ++++++++++++++++-- .../Systems/ScriptProjectManager.swift | 1 + 2 files changed, 127 insertions(+), 13 deletions(-) diff --git a/Sources/UntoldEditor/Editor/ToolbarView.swift b/Sources/UntoldEditor/Editor/ToolbarView.swift index a40ea20..1455046 100644 --- a/Sources/UntoldEditor/Editor/ToolbarView.swift +++ b/Sources/UntoldEditor/Editor/ToolbarView.swift @@ -33,7 +33,7 @@ @State private var showBuildSettings = false @State private var showingNewScriptDialog = false @State private var newScriptName = "" - @State private var showingScriptEditor = false + @State private var scriptEditorWindow: NSWindow? var body: some View { HStack { @@ -69,9 +69,6 @@ } ) } - .sheet(isPresented: $showingScriptEditor) { - ScriptEditorSheet(isPresented: $showingScriptEditor) - } } var rightSection: some View { @@ -218,7 +215,7 @@ .buttonStyle(.plain) // Open in-app script editor - Button(action: { showingScriptEditor = true }) { + Button(action: { toggleScriptEditorWindow() }) { HStack(spacing: 4) { Image(systemName: "chevron.left.forwardslash.chevron.right") Text("Script Editor") @@ -291,6 +288,27 @@ NSWorkspace.shared.open(packageSwiftPath) print("✅ Opening Scripts project in Xcode") } + + private func toggleScriptEditorWindow() { + if let window = scriptEditorWindow { + window.makeKeyAndOrderFront(nil) + NSApp.activate(ignoringOtherApps: true) + return + } + + let editorView = ScriptEditorSheet { self.scriptEditorWindow?.close(); self.scriptEditorWindow = nil } + let hosting = NSHostingController(rootView: editorView) + let window = NSWindow(contentViewController: hosting) + window.title = "Script Editor" + window.styleMask = [.titled, .closable, .resizable, .miniaturizable] + window.setContentSize(NSSize(width: 1200, height: 900)) + window.minSize = NSSize(width: 400, height: 300) + window.isReleasedWhenClosed = false + window.center() + window.makeKeyAndOrderFront(nil) + NSApp.activate(ignoringOtherApps: true) + self.scriptEditorWindow = window + } } // MARK: - Toolbar Button Component @@ -354,7 +372,7 @@ } struct ScriptEditorSheet: View { - @Binding var isPresented: Bool + var onClose: (() -> Void)? @State private var scriptFiles: [URL] = [] @State private var selectedFile: URL? @@ -368,11 +386,20 @@ @State private var undoStack: [String] = [] @State private var lastSavedText: String = "" @State private var showDeleteConfirm = false - + @State private var pendingSelection: URL? + @State private var lastKnownSelection: URL? + @State private var showUnsavedAlert = false + @State private var pendingClose = false + @State private var showingNewScriptDialogInSheet = false + @State private var newScriptNameInSheet = "" @Environment(\.colorScheme) private var colorScheme private let manager = ScriptProjectManager.shared + private var isDirty: Bool { + selectedFile != nil && scriptText != lastSavedText + } + var body: some View { VStack(spacing: 0) { header @@ -385,7 +412,7 @@ Divider() buildLog } - .frame(minWidth: 1100, minHeight: 700) + .frame(minWidth: 600, idealWidth: 1100, minHeight: 500, idealHeight: 800) .onAppear { prepareScripts() installControlCopyPasteShortcuts() @@ -393,6 +420,33 @@ .onDisappear { removeControlCopyPasteShortcuts() } + .interactiveDismissDisabled(isDirty) // Prevent accidental ESC dismissal with unsaved edits (if presented modally elsewhere) + .alert("Unsaved changes", isPresented: $showUnsavedAlert) { + Button("Save") { + saveCurrentScript() + continuePendingAction() + } + Button("Discard", role: .destructive) { + continuePendingAction() + } + Button("Cancel", role: .cancel) { + cancelPendingAction() + } + } message: { + Text("You have unsaved changes. Do you want to save before continuing?") + } + .sheet(isPresented: $showingNewScriptDialogInSheet) { + NewScriptDialog( + scriptName: $newScriptNameInSheet, + onCancel: { + showingNewScriptDialogInSheet = false + newScriptNameInSheet = "" + }, + onCreate: { + createNewScriptInSheet() + } + ) + } } private var header: some View { @@ -409,12 +463,17 @@ Spacer() - Button("Build Scripts") { + Button("Build All") { runBuild() } .buttonStyle(.borderedProminent) .disabled(isBuilding) + Button("New Script") { + showingNewScriptDialogInSheet = true + } + .buttonStyle(.bordered) + Button("Save") { saveCurrentScript() } @@ -450,7 +509,12 @@ } Button("Close") { - isPresented = false + if isDirty { + pendingClose = true + showUnsavedAlert = true + } else { + onClose?() + } } } .padding() @@ -464,9 +528,9 @@ .tag(Optional(url)) } } - .frame(minWidth: 150, idealWidth: 175, maxWidth: 200, maxHeight: .infinity) - .onChange(of: selectedFile) { _ in - loadSelectedScript() + .frame(minWidth: 180, idealWidth: 220, maxWidth: 260, maxHeight: .infinity) + .onChange(of: selectedFile) { newSelection in + handleSelectionChange(newSelection) } } @@ -542,6 +606,7 @@ if selectedFile == nil { selectedFile = scriptFiles.first } + lastKnownSelection = selectedFile loadSelectedScript() } @@ -674,5 +739,53 @@ keyMonitor = nil } } + + private func handleSelectionChange(_ newSelection: URL?) { + guard let newSelection else { return } + + if isDirty { + pendingSelection = newSelection + selectedFile = lastKnownSelection + showUnsavedAlert = true + return + } + + lastKnownSelection = newSelection + loadSelectedScript() + } + + private func continuePendingAction() { + if let target = pendingSelection { + selectedFile = target + lastKnownSelection = target + loadSelectedScript() + } else if pendingClose { + onClose?() + } + pendingSelection = nil + pendingClose = false + } + + private func cancelPendingAction() { + pendingSelection = nil + pendingClose = false + } + + private func createNewScriptInSheet() { + do { + try manager.createNewScript(name: newScriptNameInSheet) + reloadScripts() + if let created = scriptFiles.first(where: { $0.deletingPathExtension().lastPathComponent == newScriptNameInSheet }) { + selectedFile = created + lastKnownSelection = created + loadSelectedScript() + } + statusMessage = "Created \(newScriptNameInSheet).swift" + } catch { + statusMessage = "Failed to create script: \(error.localizedDescription)" + } + newScriptNameInSheet = "" + showingNewScriptDialogInSheet = false + } } #endif diff --git a/Sources/UntoldEditor/Systems/ScriptProjectManager.swift b/Sources/UntoldEditor/Systems/ScriptProjectManager.swift index 1c1453a..de84840 100644 --- a/Sources/UntoldEditor/Systems/ScriptProjectManager.swift +++ b/Sources/UntoldEditor/Systems/ScriptProjectManager.swift @@ -340,6 +340,7 @@ class ScriptProjectManager { else { return } let callLine = "generate\(name)(to: outputDir)" + if contents.contains(callLine) { contents = contents.replacingOccurrences(of: callLine + "\n", with: "") contents = contents.replacingOccurrences(of: callLine, with: "") From c89c775d3706246aea052d9d2fec72b8f7edcfa9 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Sun, 14 Dec 2025 06:56:59 -0700 Subject: [PATCH 22/41] [Patch] added simd to the generated template script --- Sources/UntoldEditor/Systems/ScriptProjectManager.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/UntoldEditor/Systems/ScriptProjectManager.swift b/Sources/UntoldEditor/Systems/ScriptProjectManager.swift index de84840..4e3ffed 100644 --- a/Sources/UntoldEditor/Systems/ScriptProjectManager.swift +++ b/Sources/UntoldEditor/Systems/ScriptProjectManager.swift @@ -306,6 +306,7 @@ class ScriptProjectManager { import Foundation import UntoldEngine + import simd extension GenerateScripts { static func generate\(name)(to dir: URL) { From 7a9b7bc47d1929b5c49109c3ed71098c37e66ec0 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Sun, 14 Dec 2025 08:00:33 -0700 Subject: [PATCH 23/41] [Patch] Added link to scripting api docs --- Sources/UntoldEditor/Editor/ToolbarView.swift | 26 ++++++++++++------- .../Systems/ScriptProjectManager.swift | 2 ++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Sources/UntoldEditor/Editor/ToolbarView.swift b/Sources/UntoldEditor/Editor/ToolbarView.swift index 1455046..9425a5b 100644 --- a/Sources/UntoldEditor/Editor/ToolbarView.swift +++ b/Sources/UntoldEditor/Editor/ToolbarView.swift @@ -395,6 +395,7 @@ @Environment(\.colorScheme) private var colorScheme private let manager = ScriptProjectManager.shared + private let uscDocsURL = URL(string: "https://untoldengine.github.io/UntoldEngine/docs/Scripting/usc-scripting-api")! private var isDirty: Bool { selectedFile != nil && scriptText != lastSavedText @@ -459,6 +460,8 @@ .font(.caption) .foregroundColor(.secondary) } + Link("View Untold Engine API Docs", destination: uscDocsURL) + .font(.caption) } Spacer() @@ -472,27 +475,30 @@ Button("New Script") { showingNewScriptDialogInSheet = true } - .buttonStyle(.bordered) + .buttonStyle(.borderedProminent) + .tint(.green) Button("Save") { saveCurrentScript() } .disabled(selectedFile == nil) - Button("Revert to Saved") { - revertToLastSaved() - } - .disabled(selectedFile == nil) - - Button("Undo Last Change") { - undoLastChange() - } - .disabled(undoStack.isEmpty) +// Button("Revert to Saved") { +// revertToLastSaved() +// } +// .disabled(selectedFile == nil) +// +// Button("Undo Last Change") { +// undoLastChange() +// } +// .disabled(undoStack.isEmpty) Button("Delete") { showDeleteConfirm = true } .disabled(selectedFile == nil || isProtectedFile(selectedFile)) + .buttonStyle(.borderedProminent) + .tint(.red) .confirmationDialog( "Delete script?", isPresented: $showDeleteConfirm, diff --git a/Sources/UntoldEditor/Systems/ScriptProjectManager.swift b/Sources/UntoldEditor/Systems/ScriptProjectManager.swift index 4e3ffed..3adb61c 100644 --- a/Sources/UntoldEditor/Systems/ScriptProjectManager.swift +++ b/Sources/UntoldEditor/Systems/ScriptProjectManager.swift @@ -307,6 +307,8 @@ class ScriptProjectManager { import Foundation import UntoldEngine import simd + + // To view the Untold Engine API click here: https://untoldengine.github.io/UntoldEngine/docs/Scripting/usc-scripting-api extension GenerateScripts { static func generate\(name)(to dir: URL) { From d74fc3ba57d780efd29a4056a0b518acec0ffba9 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Sun, 14 Dec 2025 17:43:58 -0700 Subject: [PATCH 24/41] [Feature] Added Untold Engine Studio App Bundle --- .github/workflows/release.yml | 63 +++++++++++++++++++ .gitignore | 1 + Resources/AppIcon.icns | Bin 0 -> 284558 bytes create_app_bundle.sh | 113 ++++++++++++++++++++++++++++++++++ create_dmg.sh | 72 ++++++++++++++++++++++ 5 files changed, 249 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 Resources/AppIcon.icns create mode 100755 create_app_bundle.sh create mode 100755 create_dmg.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c63e347 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,63 @@ +name: Build and Release + +on: + push: + tags: + - 'v*' # Triggers on version tags like v0.2.0, v0.3.0, etc. + workflow_dispatch: # Allows manual trigger from GitHub UI + +jobs: + build-and-release: + runs-on: macos-14 # macOS 14 (Sonoma) with Apple Silicon support + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Swift + uses: swift-actions/setup-swift@v2 + with: + swift-version: "5.10" + + - name: Extract version from tag + id: get_version + run: | + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + VERSION=${GITHUB_REF#refs/tags/v} + else + VERSION="0.2.0-dev" + fi + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + + - name: Build app bundle + run: | + chmod +x ./create_app_bundle.sh + # Update version in script + sed -i '' "s/VERSION=\".*\"/VERSION=\"${{ steps.get_version.outputs.VERSION }}\"/" create_app_bundle.sh + ./create_app_bundle.sh + + - name: Create DMG + run: | + chmod +x ./create_dmg.sh + # Update version in script + sed -i '' "s/VERSION=\".*\"/VERSION=\"${{ steps.get_version.outputs.VERSION }}\"/" create_dmg.sh + ./create_dmg.sh + + - name: Create Release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + files: | + UntoldEngineStudio-${{ steps.get_version.outputs.VERSION }}.dmg + draft: false + prerelease: false + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload DMG artifact (for manual builds) + if: github.event_name == 'workflow_dispatch' + uses: actions/upload-artifact@v4 + with: + name: UntoldEngineStudio-${{ steps.get_version.outputs.VERSION }} + path: UntoldEngineStudio-${{ steps.get_version.outputs.VERSION }}.dmg diff --git a/.gitignore b/.gitignore index 92061f4..f62522d 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ DerivedData/ *.exe *.out *.app +*.dmg *.ipa # --------------------------------------- diff --git a/Resources/AppIcon.icns b/Resources/AppIcon.icns new file mode 100644 index 0000000000000000000000000000000000000000..8d17567069b9b1baef224748a19957b42c9fa6f8 GIT binary patch literal 284558 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≈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 z zQ;M@hP|Mg*;COaR+Kq^Vfz=$-X~GiB_aK1_LJ1eU^v4cuAc2a1LinCv?Qq$7Ai=&o zsP@cY`R*?V{6&jK30AS$z*TY~1@10@F4Ct3NsxcSjhfpP{4HNK_$iv|`M-2S8cI&Y zK~v22Ys$GEKj|G2%Ta;zc{7!m3zZ!&eVHZy&hgfb2r}hL?V6s{QxtsU!X$tyi=otH zO!Unw4EV23EZHCAfgd$~pIoe2#rbl&P7t+$ACUxz!=U`Xi1w1hc{j-XW`cr_>v$z| zuD;FrcZ4Z6m-!$hOaE()WLxOTP8-9ykG5BAEWzuwtEu_807taKiRIbX1_BP5Zb+`p zfLzK);7+UZLEi$xW%Eg6Qi}mE+7s#sat#Ghp~=i-=3IwisOw>p9Qprnlt8VP1;i-| z#?&YtD?Ai*1&=wdl*Yur8f+P-4)GCVZ;0kwjHjqBlk2Dgmi}trJ%emc?n3EDy}Qcu zdG^m!?FlUf7+~bfSg6}?Zd%xTeVeQh5oP!@-q{$3OzV5FsjDPQm!NvEPTh6}Ag#_{ zx6Eq0%*_Yc7!uZ04$3R|C z=nYmTy}u**_S?q7lFnD9x67!IMeQMNW)$~=HopA+L#y!|qF!&Z8j? zsvtDG;NN|_JuVn%w^I1QJlr4%fv7}kfTJAD?UMhx10|ciVYs`8>lb?<4YLGC5KVAI zw(o_IJ5+QlY5+J3-$ze_r9ZmK;(WTX*6+Xl?{Rv1J2v6jXpzXQg@iU90R8;Rs-g++o@cI(?mQq9*$IWk} zDR9n9l?+OsgB0{GQZy~hm)7!PVf-wzGt$sbGcvUbT}_QTOU#+hKq^-t)*h0Lk^%8K z=oMd$%jR*JBvem!ontBt+rMTXfT4rgVuS6=ujxR!q5FH3#^r1H>FC?jj;1Xwq(Zmt zxv;^tjH$vkeh^i2-V>H{t0}?^k3^e?A=-VjTh)TIUVnMD^vntT!Wp*rVKcni913BlPy0qjjYEE`LSs?I z--Pa#MIYxj15?8c0YI=)3cFE387j9 zQgKN>pI9`9bN-IR_&`^LaOuG&#mxVct=^{57;IQ3Fk$zzumIyj_E!RwLi5!hu(qLk z#!}zz4DYjkvcgf2v*fWjEK+*ckaHjoKAQ#J)%=UAx(SAu)1`{0fHe$?H@yx=QkvwM zjMG*hLvcjP>nq;eyLJD7!0R-8uwcb11))i=b_?$8r8} zUn9T>aZm`cqZ>t-T3lo!+9pUWShmoUkpnNRL^2vre0?O9l{W1yMMPnNhyYfta<1lU-2c>LX%gAs zhaq&ad8_PDpknU(H|U~sS;Y<%P*?V!NId8b4FQcO9N)9xG5YG{we#0>nY~;(9M~YP;<}z=JMCRSk(e$) zFzla)0ZPz-B?d9&^0`1Mz!N1EolXtof#K?~>lud-BO5q**PSf7ymIYo!ZAJIkWXW? zKy?Y%P=xR7VxwEVRTz2<+|+yO2@-hza(jT|vE#WDIB?(HLfg8kg78AOi9~~reLk}7 zq(>n+7yh7u30{mEJa($GAB0M9wLyoY#BdQ6|A~L)VI75Er?BeI1zzdTcB=h-QE0_^ zfDec1J@slBPN5u~fY)-{&E_{#1iYpEpCA9jgabvG+P?Tn`TsZBRO!oK0hYO zP!bjX_fOkov($LWTv*>GwO~?D9pKKg5x45nOzpDj0 z-0Ku$dGKq221aSJNW*s+Vqk*Pz7511t+EKxAKSV?*vR|1>}sbg!%q<30k*m$S$bS$ z6t0hRNYVlS3S6M4%GSOwDSUZ1dLRkj!vXnq{guz{S?YGjJtZF~DEa=?$i2P;UN2iK zhG*W;l~7T77WjTIwDOmI$t~B0QhpEk{q)iSh0^UWIVr|JBtI2Gy(D29qz8u#6Bu12 zROXJ@)8AeMXG39U4;M4hmfNE(+h-%}oOAlz`n7G{h;&rvLOUs3$F5hn8$a>Z-Nv9- z%21Ku&jN>?SBoN}XMes;|04Hy7{xj1gW$~nP&hruVZYpOSO^DW`1`T_s)X9nF2owl z=7U-VyLTXEjFP*1~sNpI02I}p8X3Y%rXJuT$b9xXv|9qxLsi0v%7H$X@C>pS=HD}mr3IwB$O<1ai- zR%@wWo?BL+wfJAQeB!ABrwaOdH7v%nk!B75AaDZ#PMzc0(-zBY>kGJCr~pRL($WG| z*p(SV5Gzbt%YhD}FOlQ)GDh?bu6DI?(#B;!Sv*E`gshN$iL$*7-l*LhsPUVe%yG`a zMk^v_bxAA*6fAO%6h@Ug#wYBGcOTNm7Cq^f@+P08G2nWi4c|cyG{Jy?TWTbo-EpO2 zSQlOMdimSjJRI&1`7C@|#L1SYJr(v0wZbQRV1BpiIF0WvmWti(DeC$8^^0g6vLL3x zM%R!2;d`LiJdg{Fxdp{7y&DCxmf)_`I0(pA02YFzgdm+mr$6x zRA)l`&}QGrC}XZ4OA;$^8hpCz-^G6sERM6TI-c!bH{gspDh@PNN@`Kw;A=5hktJ&; zc>&R{(Q6GBU#t{ig%7570*XC3J|2@EbD(L&=eAZA$#N@A_@{^aQnzdW%NNE`ZKkC* zKJ!#*&{~c%On$lstSULLoz{&m-{xhZ&1 zj0A#$>z_aakN9F!3b7Fg;um&95XyguWz2k$oA~he{mdeUO3GgIWx1-Y(7pb{oT}earZzb*PC$+ zYKmwFqw5B?&vu-cl%KWg@zFoFoBg z9PSZwAoI{Z9w|%5a$THEzqIwAf{?8MpRt3`v*#U7K2tFM^;2zCjbr|I$+J+gwdEG{ zt)hwh@h`0fJmianQeC|8rCaU9+dm*b%QR$;aFO$X03ag!pEA(34>DN`djDpJJi}6` zBqm&3YsZagO`DR5SupFAWuDS&?)^EvvS%yudHi$X!SMXp@v`p|%%l_N`ewP!cmI-Dpja?s3M@@`P2NJ?%O z<27R$qF{e@w=?nDJI)M+7$O3`MB*HX-g20e@}B{nE+IMHGDD3& z==p4ann3naFF38xsT+^E-dT!Da<%8{NVS}M6+e`_s5VvdA08YrqzMTbViO4LlUsx) zkQ)#f$DP&km$vB=nm!hq3g?8GX_#RGB^UHighD0Ff0i~WfttBuMO0&^Oao=6(cX51 z{3pHysdDzm??Fh#cp)dqZ^TaIdZ-pcsGm?xxH2GaQT!)(8Q}^Q0Mn zllfCoI*1c2Odni5=J@ckC@1(kQsaOJprAeeJ;DpW-eINZ-+ao{9&B~w+R$O<90;fp zLDwnyJ%2nTpbfZ`ES^#R;*o(J?dNY7-aBn9w|U2cp81!SRLgSC#rq6QGQQ~cQb;Ys1wwaF45u5;2T?zU$m#7!VS4WG9_N;qvIgM;Dba_aZvsQuY(&N=#W!Wm`c=4 zu|x;IzALl)Fjq;hC{||~|F5UFo2(!$HtmZ^LtwWcdX+Dzgw+qr7aruKjC7@$8A{=Mxj9=HEzG?R-zr>HuTN@(~faut0s2&CE8BKnV$i z#dfWu{WA88*WEymuoHGxCNL>d|JP|n2G7P9MN)Np-kEsxJJhgDUCLHgdn{t3+ziwC zuO73Q!3f}LT(B`40Qon zmsJ{)zP1UiizLopENx=lJKsjyuJ0}x@NRR;V#!)Hrrr`Cj(|y0hgob)BjU}ka^1?> za!wk;9`d|nR;{=LH`D@oit-B^Km&rq_6~XvsbC zQfPk`>@-J0EaFqyjg;x2vbcCvXn2K?A-Gd?4rmWyuy1ylfmG}!z6u99+YUaBC*?ZM zMK-o8G3qKla-5MuHa2nk?}1J{PNCi}sx8F7DS+U=^8T^*3V0w{B>@+Cg=RRi`CL%z z4t1MuC*`7!CJ#y%3bD;Z2u*K5s+$CphdN4MLWC}9;O#-#zpGzr0#>eX3 z;mN)6)1Tg?>0k`m1gzdL>XX{rEx)t?EkuIgEu5f*9o2OYa4=M9ReW|ga`2rEoiK&m z{0TvS#c|wr{_$P*9P>tb>!AoBZ9*=Q2jkrKEx+b}FBMKo6jv4eaSe5vmquDo zgInjA7^3=~)0+JQ;w07?@F`<&-d{D1$IA9WXX){jQLIT5VWc{B`S4wy6|37yQN5+Y)j58t{$R_S= zO13m)L{?=ad)(PHj3_%oh%!^QvqIS;lD$_(_Bj4;efxfY{~iyIQyusDyg%>p8qX1= z1|zhFahYm?R_h6VB#q^xC$juj3Zw6Z%6$Cq_Mc#XBU87x7xrmM2<9B`6im*T zl0l~+1(HtA(hKUaPHof=&8L3`>d$x~0w7!OD$B#k)$pf>0Oq#-5L-h*aZ)X z_RnEudCC+;X??BRetQA``>|3rd=KWetG%s=IDK<22(2YpZbo}^#t4xR*a0jjy4TO- zEyPUHZFb@BQvolLZ15YaUeZL_kTp=**kBtyO1dBr2_vLNP*yTl~LSLmLgtuS)YHBtSsl{u`eN-lp z#|#PXeUYp-2L^Bgw2t~B{i#p!J3vf)RFr9Qi^Vhf-(g4bBfiE82^H>Fg&w~7tV~fk zlW^ZyZOWw$>P!VXKGz@bUv7T*hGa+RE<^VJn?k?UAnuV=1rqIGT<@3p8bECUBux2T zqt}Z}{Z!C4HLIBVT@v>~(lPnR%<3>PZOFz(_uqB#TNTR12~ps(g90QL{7*Yn>iTa_ z-&rpk^}GwMt{kunZrXcMjS-2BZ}G+NuuL_%mokaM@8 zsaN&p&}reo2;)jSB(%Ly^52tBLXBa6sjVD=X8hBTVOrdd(&)!tNfNvWCvC9vS~?}+ zLje$HvSVP*ihu9(_bMHqLke4wpf4hCV@Hw<5 z%_vtTk6#}8gz%L~so>eG`F93H3LwWmh48Y~;PTUsj(IP>#J%0{&PB5r1`_NU(AW|I zAo()ud}uj{A{#&k)aiCi|6A-RH-zvO01d~NHhIZimc4##eO%$tRTJRQ4OWE@uxzE} zJuEQtf>>d*0jw}v|NovOx*cgo(qHM}+_CnUfUxp&zYOpV5obzGilokf$p>vPgOYLV z4N$Hf%R&k0SRi&3v{wG!a5DnIHes4)EX_4z^0Re5<;D>Z?cMOa|GJ;N4$=|b4Z&@s zbB5`r7)6okK}%35Zi%00{#vd_=+xhx*)J45hQd*eRduVE<3~WFta`$qGiKP>j0&Qz z1ibJw=8;C=hTB>YsV9*+0lgpon@8$Q@P?||Tcbj2YE|KF(2Fg!^8(^4g#GAHy=xUl z3w@p_k$M#=43(;ynf`AVXf{Bgwm(1k(ap=V)nu^@5WZ#kHaOC8Mya4P^R&j4(j*6$ zV8U>ODt=Pn`uiISzxqf2Jw*J}xv__*Lor#dn7gZuN8&)5@^i`t@l7)X?I{IlViVMyhUebL@IB8l%hrZGHq)Xn(|>ZR~K~9zJ zQhC(j96lSmpz)iR@qRywj9?DXEZ2JBzKW<2>i6IC|G5^F0|K>4PY7?2_thq%r$UL+ zyQ5A`>@RrGr<&i!z*3cZvb3382%w7=&FCQ2jRydUFfoGA>`ThjO_sl-yoSa1NbBAQ zxQGCl6lm^#ejjhTjPE7?20)~>jVil z3q0^M3wY8KjJ$0K9Zqx_Mxh9wg-<~%_x^7SZ4QITuQd=6*|`9hbrAEuf#oW$lB{_t zWDdiA<{=f7tP*n6K(8GK;RKf4ar8-*XE z?rgm%Y*gL`3IYOY-jLQOsb>w~ zAqP)@s z*5A27i$IPjO>q3~h!N0Neel&^YE5=7^@-{zZt}Tg!eV}nJgP!6YuR#y2NWpiLf3(R z^?i9Dkw-9q(_m?MASTzpXbt)gDzvqlR9I?u!;_y)bfYRD2IN=c-oM|1hM;478)f`_mF#K@!7~6Va2_Co;&4d1W<~J3d zWP~*lwhBPH5};m3p9!g=mnPK7-NSjf@G$H($YP>WRuh(UqL;j~?zN5i_MOE)A&?TQw8U;JvM=1|L7R(NBC=5du{tg~A{iAk>W4l~i|P{4%E!u6$|h?QWXq2OANN%=n~~mvqpXf9}Iwi6D(%qfrRi zSzoT)87&J=VCP<`^zurm$^Ee}y%|mEK2TsjvyhwrsuXr;ca?aDr4f=^MZdru5x3g_ z*M|3kuqM8KO}am0PmW)oQ0#n^vamI`{^$G%JpnHZ^ISgCAJ%@oQo6;p%pXPj>M;KL zr57+qEMJT;EO5kYMXnf_YILnliK7FxrRZ;YA1F{Fq^Jl*- zRe}2Z0%(+q0eJ#kD`4x}>`>81Ug^X4(EK+gIpN{iDQ5D(i!X9Y4#e$OE2gAxW?0lh zQs_uh6dxX~$V^@L!TfC6ggZIczdz;p6mK!4HUw!M90$_#ple`nSctQg(c#|RY+xtjOx{?7+KOaR7U3yO1Mr<0U(@3ufv z?qfNUxI=GI*QFbv2>&>Y2X6pbK_~S7okUNd$F*xC+`iC{J@vt!3O9!qT9i@X-8N{7 z?IWNQBI$+A>kt73kN~XFNub5!!@v1ms3aWaqqPU-GX=1URiax5`M%M?t}bU&l_fRF z7!_|omHn`csPl}b=)*y=mdmBivs&>;VG^xWidoNpZ@bwWf!D{Pl%a*xFt>!#jorDcF}76ewL^X9n1~S_Xn}Bsn48+hAH4QF;IG0jtgd3+dFSPZ3~i=1caz3?|=g`7mQZ9#-z$K6A*2GCl00J+7&?G#2GEHbApdEkW(BM9N55d12i*) z$!>qP7Y9x8PJ=p9rR*BP<4YZ5g*_-1?&c{r1-BHG4YZVg0+l?Yp$#_fjxfw=s%>ME z*@?O%+TL^qnwSe-eh~Php`xXnr)cJHYauKRLCvr*9W6C#N>)nnU(_1v%6bq42j9XW zBpGo6QN6P*JfT?pLg^F$W9GCV4Fj z?B@}8hsxZRR0PqERpsQd6qIX^JaP`5m zHn?~1-pps<5>i&3$#xcrg!|*sIEO%74l~DR8&-gfZqX-rf~&9s9m3~YKI#VIyjc=asl#8 z&RU?7%pjDoZhshs0Uw11pJ>ZPFzNGf;`%<2l*CrQ0i-?h06p=3APf=U?+@=wSEKDK zbGGJTYxXo2-2f`WwfDe$5B>pw12@)g0oSA-eX+5zxgeEKK=WdOktQJ?_&T2-53@>i(OE*B@}%};ALun zGY)WM&;~MEw}H7^L-xjxA0#J~d7qhNhW)wV7A3rRlK2Q6m6psrZ~7T5l3C<#Agj1t z%)-yl`6BGREA#&IPTwgYpr9+ZZ~%-t1x!FdBbhBriZxb2p%4haQZyb* zkcrSCSc@UzPM{E4($fsUBSdG?0u_50+!gVj4jP!(j=!V=ItCgSZhdSXScNgqp424| zQm}hOtAGW+WFi2c&V@x|W$_(^owdono7TzqT1kJ5>DU07a6!(#!=DZ{9{>^a4bbko zo2?z^>&^FOL;26AXVYa1Ql+vGtjCg^+ePXiI`U5v=YT^ev0|k^>VY`y5-A>N{WBW^4E3QKeUo+_SqSoyTr*98wPW&(i210oT&n)g}rBoJ6xvN{+T#_?mS~QNVcXmO-hngdR7)vz{BvAXDycy{h~jfrd{(?v znVaX7JSyGaBk+LHmNR-VBWVXt1*?9}kB`5YnWZ~1*QmXpa{TAhVJGPD{i&zMg5II$ zW_an!L8$gT+RWbGzVq{{OVtfv+BapL;&WYYYrq0@Oz!x3Y1i$DoD)X=H;ibcff}R$ zeJHlz&OY>#IZC$;CLc$;ueV9&9vmDzzR1*E76r`G4nTZ#qiVJ8#EhT6!LBU+#3XIt z^(pQ@3&4^ZzRI1n?LVavu8|5do&|vPe#0tqS2@F7RS+^;sR2OPUI1VsG2E@+x!1%g zaHpm1&(tb_!_-jNb0~uV3@E>A4L|O=ws5fCLAy}&I-fos!p4tOESF!sU{X22aAS37 zomI=xfQUbrbI1z4$p7>FPzY}>JcPCk!*7aCa%U8Yjo5%L~Cu@HRP9;wN*OctO+iU*3t+Caxa&JNI9 zZAn6&aAwl3Gr80xzIkQw_hS8(!AYus9`00V^1i0<=C*xVV;q_PlJ&!W%4-mQ_vm+Q zjluNvw5=$PHxnFOEbrcZB9C7Ay9yD0nmni&2?8DbV%3HoaJ&2*%#8@-hIHzfN^q!F zPT88JJ<)63k9_`43?Zahw^qL-cz-7XjR1R0!&RosoTt6NrrX`w0gC0%1~GSLXoZ*d zJl1X|{8^mfMo~S^wU>BnSbvQPa`^zz0RW z8svP5Ar!60{#}n~WCI=Sp!=+Ogz(sBX+;7UiLAqoEX><8I=%fN&*CAFWtm@M|8S>5 z;zMsW^fix@3;W;aMj{Zx}UC@KUk1Y+m`8SLCF3fNSUO7Xb z+9LknXq2O{7SCq}^IKT2K|*#@fD^IkGW>Ph!S4ig>eQ(PKXMM86O8B8&$VcmkL{`| zYyCMikIxl27Cn6JJRRAp;sH!OmFe*b0|3vBWfm`d0GyA*U(hHF5Jum@*l| zsZ;OvS87auja11Nr~v{V(0$7DoQhI=Rz*?~l)~3KN^AEh{GXx64$)zeXhlN4k}ORS zC+?tdZ$0H`>1TebjH(|J0>OzPt^7`^;JI__cVutI;Wpny9sM1N2a3>4N;nEH4PDp| zq@aiUop}f&6z&UwGxc*%itrKki{3@9*r%(@ar-J^dxgy z*0j|QvJcrEjvjL=w=*5@OUFa?!XK-p6>bMgqC(XznY+(K+l6nsf>>)|KdPr2dT&xS z*l_mlPt?qc7uA&VKO^RMUWwpDwGqt^;qWcd{JCKFk+{(7eFDnnIxakw_!2!$ZGAo5 zGnoIkuq3##ub~=m6m^~H(VkHW5V{=g++~ECHI*wd^E6cwO>}h;QjDrjW-t|Gg1}pA-);RryfbA1q)7 zRO>Q5Q~RBzM~Xp^i(7b?#lRAV)L0#id@RhVPXF)CBVhL=dLBF@+BB}!fOF;0%Du?w zyxa*0sAm{}0%@jDA5LbACZCqC>2Dx_gli}eoG3R2O(iC5rq{?<1+nfYp-7!Wu&&Qi z9=dPtH7&37|EN8O{@;y6iXjGg_t(e?Oc7I9lM`efC!rDF{fEvZm~fmhE)e=}kU;P% z3imyL4MY};;P&RTg(+Ew1+1H zC0SdXNWJpx9miVIymprlF!Ykc?(gZ>=ZIkoqkU4%3jc0$cm+t!BR>=R2<^}n=(^n& z>3j(lJp3uKIq_7;^V9x1Z`vNy6UKjs2t)=UZfT@9N*&*L0?Bsc;ntTBzW)dW*Ox0 z@o?^hM{>A>^6BPCfk|GtU~R|psCLuhuMcd;(ehMi?`-1rt3+mfr8j{bdKb`%VcP|IS??A~$KKZ4b83eFH(iP-U^~J$ z;Z>`5KSEQaH$FXxXO(g^d+ykm0|OEkK`nc;C1bRDA(jWZboY1lj>FXBHMSZAX>FQF z^hqtR3=}GXL{+wAB>y@Ln#E{sO%SzbBlR*U{<+iRK+X}F`f~fLY0Ka~5E~Lq+qJV3 zr-Z;3N6ZmPT-$ae0(F_(p#NDkV-26`O^ z3dRw0S}5p~{jgoq(ZSMTmDJg!PWoNNfA0^4SWqV9zGFA321+)p&hR7@nG~pC@Cdlm zL92UN`X*vzF7j^&X+%dqt`uImra2^BSl_;Ko>e?(Yr*ybo4eCQW721UXb{cUl|1P? zRVkaYn=&a?ZC7)HHi^l{SzgxXl^Yl^bxxT3#r4G9=}eK8cPC3h!Rcj63T`825`jOq zqVrgv@pyD-;glxY4IVK;7H$~tWvIF!yfxF7c+Ldmj(Q-s6v+om;2ro%=HRxMNaETKv^z7DpK`RLo;9j~i0M|Se=O!FC( zzJE|5c;SL!4E1a>6Ia7~{@ycgfux;I2zVC8o=JG6`74V*z6uG#xg3qS{P}k`NrXuV zqxVli>WBJzdJ`Iuqbrs8ut7DL#C{e?!j0VJICEd&1;AR#2AZ5X;q0>67w?oCRF^M= z4c-*pbIk#TenFx9vaf9q?ZQLC`iJ!1DYC0Bv(+Cpv9?w%95wkm$#)4cQZX{|ugBGA z>#vjyb6%)ZNEaEXs!)6xyCPOc0E5s6Fm=a%-5dL!B>>XMa~_Re`cd43WDPf)K%Ubw zow@^NkCrkt&IZUqlRLXU6NT-P@8!S%=>bw=R&su?6DxF?G(OU|_oyB2wDI+~0;>5g zB1hDV`Lyf?U<~GhDo*xsI1N&Z#cg=~>W?m-<1A|f(HqVtZAE5wvLbnv-?({_$#=P4 z2G7VXDQV;aY>6=zDU-`j@c8S%aOsySyT-HMz@{j3!bYRz)&;f)ml8-ml8K3tax<`z zzd7JRGGAkbL;m>NBi}C9Q5D?pznCt9_>vBXm=xv4w(cgi!u_6fev?k(zlJKHhRsA2 zS6Isvmh_)hV>cK3wW*mqM}?JKH<#uLyNn(Rd*nH-niaL#zNc`ZpfL;3ATiHUi+G(= z;F#n$zB;aS%RIVn9?AwJVyV-$IhNYuno?#2M4lOgR)0L7j0+s{ZN7G zE>MiH$Z{Ck$w`Q|L!d1g?x3S06grIhE(|$$^!YcK^Dv-lD(^O4sq(sc zOt5_4|IW}3keR>q51PP0w!q_iPu%DP`MFlNIBMq8YDzjktERjZ2xJI0 zj-`7S%ze$tb|#o=Y|v6*BQCNGi^?7zJd!>Q!zcF8fh&24%;0_SU^(RZoeHmQd(}rk z<>5W6ym#QI(e3QIReiX>asxt*RGflT*p2NBAT#H+%kQ(Xn-_HKoGkE-S~-Z;p0O#e zy+R*E*wx}ycC31Fjsm`>KoF5FA1)Qe#c}S8qm4OFLD?)zK~Y(ZYK2ElAq*M(3h1$= z-(`#y-b>p-K|yv0J5H)q5%i|@!P$vH$B_JH_?cY^aQ1C4PTSQ zz4pBTKD|YrHj6j4Y?s|5S?cq>r_Wgx)@urT{I+C=i~lrG<%y3%l1S((Y{@@WItpAY zA8$m~Nd2r^iJ*k0j+!U{D$nmJ+NHG2e2eD!Wsf8}N1zcGI1hX%6_Z`Q$9raBMgr&F zR&FHUss4O2$;o$FbMwN*`>@HcPrt~hbknzzrtG#_x3a2b^n`F~br1TzQwxHL&|5wc zoGOo3g;?D**X)<4*&sTK zZL^Y(tFv#bY@^cj1xrZCUo>q@)&+`+xccT%a2U3cB9D{wYTx86b*;b}g4aq_763I^ z)ul@@M%aBm&4uxK;f(N0rNi6b?5XU$pmii>0Y|ftw_nxzF0_7?9hVe#rnQD9aw=;8 zO+YA^smML^JOWpCJ!P*q>zSKJPiC2|sE>JNAM@h6rY1N1HdhF z$ORh0^ETOf#c^pYk>Yt;>eEfkXu{s43WU`$>(RdDzyn1d_J8A^84@PzxOfMJuv3Ou zT|OFsLr0?G;Dpe@M^-t{k6H~qGiQ66o*LiU;&Rkvwn-yvf`%wXC}@%RY_`j; zWP=_^Li5=!N=r^3-uWbLfz52{JnG-{z27rV)XZ2hrADE!4PtEli- zI|olMmUm|FYE(VlmXF^GoNs#Ao3_*;A_}`d2H-O#^y8?_zI>B=b(E%Fzg)!cePx)$ z;3rdmzQ3<4A2lRffx?IIbe2EtEQ@w2rYk#gf^P^yrBPx-uIeC)>Q=!I%SYpdt9rCC z*Y0@EzkT9P1pAAfx%f*$h($&tyWd>elN&uWY-SeD*sOEyqSf%Yr;c%+>unS>D&1Uj zR3GrM;-bshSC3vihs5Sz@hg2+t~1+nP*#MlxRtx0$1GiM4r%gXi^BNkt3t@wGfi}K zQja@AE-h!65(kJ$NvcIZx8(?3BIcuawAPicU1=&&3UMSGw*FpPEhE0R*-xu;jFSpS z@90$ROaP@(vrihif-`=TX3;oqEMdIG-l1&`Nu?sQiQ~z#`pjgttTO2MQ$Sh^<@gk9 zm*q;xwg{o+%*-pDp#0V9QvGRq=JZeBE2kqL|MV3PpJGf3lX$*}BW*C8{-P$K5)yYmxG zu8bCyyA;DbDdL6uAkg@J@>ebX!G4`P568^F$m{2{LI+U0)Z}BAuL4ZFq;j-X%Y=l2{42iu@ zGl?N?^psd{y_At#kz(%{HITjkYg>7B(?x6aN&!M(C<326HL~p?oEX#|Q=vn6 zrwj1!Dc}ds*z3b(`yG@Kc%~LV`YMi1b3%} zObtuhoa?$X?7jYcVLhcrkvMuJXC#1p&&25`Mg8in&zeO-N7u{tWdaM~Ap`VOlqFCZ zSrGGt+xI;4yQL+(4l3AL!7YQfmY3v7($2Z+IswiY%! zOT`_RDlR?APnEux?6T-^ipBfUWCIt3vD(%)coO?c^wCtaEWlOFYCvY>d~iHd-1NX6 z3SU<2{-SdB`)vo^{yT2T0yW><>UI{DgzRh7n(*n0Y2_3xbO_K|$R%EH7t_iG^Gw8W z09mEld`bbG(^a%yEsX1c<6X_l>ZM3*vLH$7I#m+%H53vKbG<6;?z-D=imNK0e#II0 z)!Xj8z{O zok0LEd5U7!M30K!oMCcU4RgywpQ`zrg%dr(R*W}_2-8r%Ws!iC$TdzpEi zk^=+R0xS;d0@>eB%gG}P?2E?z$D>PrPenH+hWdfp`C!Jqm+$@8SaLHq8-P#xd5D*q zdEUVrg&po_$F;_FEy#}UIz%^+-?njlh8eQH=1(bOto)$jwiQByaS_zqaw;n?Z$5YM z9*!e>EcFR&L$SRen7k8v?=$s7kJqbo&46>;zz!U)^Vs!n5TeV?LTK}c=nzTC+F4rc zeLxdGy=vb9Q@*HncJ*fcpd;yizer%oF&0rFCPZ>g2Z-meyvif>;VQ zGTWD@Czqbw#9JBxVvAA7<#pUb3xA3M=zvL{kg3t2M_ls5BcXV#VV=k1{?U9>j7yt% ze>T{?o00G|xkp#o>K+eszwDjNu`B2#Lo-M|OvtSUSi`s%-)S)ip?M;!ufl@q_-ix5 zA64#O;KRE|Xmfit<#8JJ#?~C%a{-6s!g=4xXE~zmE;unPl0KYldv*ENONtP;{=t>0 zI8vF%bE)le+s4s19YOc{$+HhHWK&&-rkL(MfTmI6bE5Hh+K1ezNMtzuI2BCp;V>3?`-Z&xRwzfv~i6<+%}-Mrp{&BM+&0k^Pt7S4_sZOe8Wlpbq<0cP%MxQ0%2bdS1dQjAVyV&eU} z{_M-D)%zg#6zJ`HXXLex8U1Y2H8fCMc*j|8_AuO02P5++FaSTvyYr*N1n|fKysF0V zSZtN9K==fxk7*@!EDNulfl4brvfYz~4{Y2V!tDR($N}hAh0UcOJxCJYFcHei9MSQD zJ;+{KnmS_8#r#>^m9JmF_JV-QK%+&5p5~M5uUq=p8Lc89JR0vvs_*EiC9-4?#UkHh z=jmplWH8DGnv!45?+>7YQ!HG!8G?QkXVooD^}oNF3+mVJUJ03I zK6!F|UTT#(p)1xlxS~neV{b|L#z=BU{H={L-@fa_qnsMUqh|y>V_IdUF;^*@{6QEU zsuNgp*>t4^X%UCIkO-S+d)q6yl$4(H44u0(k{?NhWe`{M8m*7rir@678*eQ~& zRIK6RMrE`3kr|o#Y(`Cgi%krAH=`7O^eUNLmM%W2Y4XK|quR>Q zN&YP9X-^k%4U5}(cG}_mjN_N+nfa-ntFQ!LH>uWNnY-HC{q|~KUC@a>!04flwrq;1 zdc7N?N95sUbDn$3|7ygmalN)TnW>VU6CUV)aL``PY+#zqW*5o|a)5>5irDWAA0?~F zlE{54GuR^_t9UR8b1=ktkxfvDY}>)%-+f$C!h0xMwSY|DYod)CvgG`*<9jy{L zvU_T?YUNJuL&C%7d;}tC{k^*7{Zp+`ORahp1NG%w`xo0*jdB5UFXu-wMi)r4cIO;y zX7XKbZS5i>Y*Z~316$@@C;gwPSt zV#($_+EMdVlTiG*x9z1T8ZyKC*9F)E@vZ^arbt5G2esVn;|U zE}Q~dauQzZ$%JOn$F_l~xbnH^pzoJYXVoe1BeO^zCtx-QuL#NSb(*6$TiX z4q(2#!Eni*LoPl!m#6c2=uvUux1wyz+qvaEA@ljhH!a!nf#dai!~LhvXXT}&C2--f zuUHVNEiqWUYwEkO%UnHE$KCMciQsr)<<*Ys=1^_)EDqFcpKF)_9rwE@o2dl8EabeQ zgrqJyDcNpu6W~wuDf;%Sw@UI^LM~K)%T$fykf&|vWjb{#H$pz)J&?2_`PJ~bS*p%I zsD7iEBDVDMx%-jn>}WxMVE{suGmPz*`-vn04b<@D4lMHk7i4ylusy|8vlEmc=}?V? znM0i8a%?at>y}XEk7#;yU?YuA8pO>ukFsxLj`msN$LvPQfT-ExbK-BsF_UrsFKZd9 z)x)Ju=?O9}ITxFMe=^auPRx)s){d(-G7!ai8=6BkFg9|`<~!$)!j9pr!l^Z3XR&yf zBx=!79tXE(#5EoUUES5#no6~*-I+wyxS;B{KSH35`rvB_X#a~uFMr(+2LMW3n8nm6 z0e0KR?*oUL=Wo1`w<$?{BNJ4ZK9PdKRhtO&XVc2Z*(h$Bu?`qDzBZ-)7^6HF?|>^yNoK%QUB_nqoEAv*bH;6~r)ljKOiZy)~ASPCvLLzc4OTkr_#ThVXM- zbd0y7R@rgHW0sY*$sR;P!id&>%F)h% zDV$uBRwA=IwYC5f&z>kmR$Q~cR3f>SH|xqMc72Dm5^Tx~;j+Hw-B;IZ#AHz@7$Mw= ziYRbnN4@9D?#R*hQMHw8y;Dha=85oPhl#6wU8F>@s{^!v@ra`euRsI^B(!o#ma zWKsZE!an_|>^oIb7GN6LAP~3bQ*>*E0IA;N*0*hX`cDb(V!GEdp?yz&INrx1VU1sr z*kV7YhS}px9-4iWE*KQ~0HhlHYH}VuDIPO70r=-}`ivU&`AoEnD71!Uy_=L+IMRb%yTzqPcr(iX3Yr-YaggIt>udagnX_! zU9P>Qes$%(624GvvHt!s`XIgs^rfx8p34$s`XsqA9@lJFq19b&O`T;aT1GEnKa{$t zcV0xXpfxDt`P3Tm-iP&^E;`tlJ5})n<}yGKt>kmKkNHMlqxQQP{Y>hC&dugHr29tA z+Ly!yAY;%K-*sqrc~8HPShrKp5nVdk;S}?;%sH!zri!>Fn|aFu4Ehsih4&S)*zu1Qq}hZoPTG_R&_f7hvCW z7p0dvt^DlyriGt<5-k3(CvA)5?i4mfMl?vDtYLnwCI-_ut#R+m1u+;_6Kdf>c~Y%6 zEdtkum3~q~PZrhqR(|Qi{y_1<)N8K~qRbB>Fo%IZfZBZ1RmKu}k!T0IW8g!%0t(H- zAkB4S8=S`p*)W?6Ev=&-k6!!DUY?FwiR7V6sDV}wwCcq zim}RE(sW=L%#Fajwi65DM(|oFt3c{sq=LGyGIIAG9K%@64|{dBW!A0N?ul@}I zm+duS4sE*=6nE|xQw6=B;$wx`P_Lg{w*~S|d=(ri0$62*S0KB28>Szl*g%)wmiqBh zSd_3wL3k)Cawam^u^b6;;gbSyOUWKxz38`lRA&%MnXnsD*5l+#G?3W;<$%jtOu}S; zTW4f3#3=fK9G?=w2D+OHCl^{1#J>q&VV2*|c&#+1LNY|$nvx?q`o%4a|!sx{=jPp{|^kiN3K^f9VDb`dr_4YaUyi~^(QtC_i--=bTEuB z4c@#E_w2`g9@)fLb;l?U6pih?p_JwqM4+y(MRQ?D!yjjNb&;hi()QrUoNuD2$M5xK z)7>hyeZP*-6pxLOWccJUdnW3-+~k zAb#grG7?&2dD`jeZ$Tsb6!3fql{{4L4z)`sD@P?=$O3O)zn-&5e{j9V*FvzkC2v$H z;gQJ@3LDy(p^`@gsG>!&AyOGPY(&E}HP#tcFLa(QcadjFtR=G3CKQFDeL@dIb4;Rc z37H-4?j}DU)%$%pFAHC$`0m+O$6++3a%Kk9 zj}NV1bKCkQpfO`{6g#YJz{9;wO#+*Hou&xH^(7i}P(v-9VZj9Lr*~;M<-Hq_%e^$m zE$Z-IflGDIQx3mL*K(oZSjQV*_O3`x6*%v-X!}}TBA&6Gr7hSmx3Jp0lCy z%hhDClun}@duN_9UeE(XAjy}~UK?|^m5b?SbIZaQ-_HcrnAm>fej4UKlabJ!+fOt=E-!{AX!<7!s&Bh?1do597 zvp0b4&dzvvcekEVI2-ChPR^4jPa>6WUY3h4A-BNQFXI3JG6zsKf)P@5?6(UBHnFMn ze40AI9p<4O(345~{rQ#NbnOr~e)XH@-grp#M~h(^anFsjK&-+3?n&}m+VaOOlDD3L z7)z|9&N!CwB7Buh40?lvULem7)w(PPha7p#CTIB`uBR;2+UtGec6v51Gpj;u=MI^f zSZhk@WT9Mn*E;8>*E{@T{{*}cOE8a9rT}IDtp<~Tl$GpIgD>Eu@z5(e7~eHEG06iQAon+n&oMXR6%#A z_%zg@=pP`9KPBNZ)7Bit(>g?P1%+IY>2sPXNA3dd=5^ys*|Pf zO`ehqa;`Xb@zefNl6(E2-!AUHivG|T_uh4RNZ?jYB$*}xABm?~lsFH?8b2zu%J@8Y zTcUGo{j8zi=ICizzG+WWk7+{?EH+UZ@+k{k0}bj*p0T)ZKqB?61kkiATA7~tf>A`D zV(CDcxh3nCR8?U{VzmJrB*x;36o*c>S-b*V# z00-f_<;r#Y$(;}xonuJcjvDkO3|(MW3kNuJL2=&$DHV}7yf$z$syT;`%u?dNskjk4 zStOFy!F-T92P9cPe#Fc>C-Lys{h6by;x?VwQ*K!p>qkc`NAh{+PRiTtouk1XHkr0F zAaZyJY(VFuOe4+k#*J)HF^&!uRybe+r48G<(?8Ja->5)WMWo8JjRehd`jRL|b^cc@ zw?Onftu4kbt7eZQFepqOv(Z_<0F1*w+>EhI>kmmdxQKKIxN$#Hjud>N{hptopx~2a zkLNC_3s0X@QBlwzs{uckApF$-))lV`QVLC;rM11Kt$ff3*#sh^0$Z{;X1VACV78Uc z;W4GVo`FOhKRmQ35{-xyMsh*VA*)Y0peoZLE+^uvNlGf`vaoXd@h^PkK;lI^PqM_C z=sp?M^a=pn5cl43beVkKpSa4x=gfAR=-(w;#_2xRztDm6tpm~{I5xGbdzPo+NZ7IO zp&}GdGP!K?lyMyt<*y&(Y)_hhDtK+3UcEY%nGu$>`i%-d+9@!P(IHR|nv zU7j1jbcS|5`AENLJp%SiovG+FOpV?Vz(wD^JU2PZe8c6jFpJ*g*xq8ky~}J_^9WFBoz!e?-<>;sQ4`t}^2kx~yozHcpfnvS zeuWH5xCUP*X6X?#anok`SkAQ_+(a0EAJ~v1tK9{qH45CKa*r`grii!{hVfbkM*wwe zl(~uWIb*mA_=eX$={Rw0$u2at2gL4ZAb4+=S}+G6QrPZHJjJBH9shRs+X3t?z?3YE z9{H*gw?5bO`=?y0IO?o@ahekDk4;9J(d_I@zFPn_f_Na58?va*9j6v2qzMC}s=CK+ zdM)&Xs<$cuAjj?+vo|;*B_&-bU(6Gy?{l#0(9|~0_zGGj`omr;`I-YM>Wg%A&xSys z!Vgj;fb1-5xqOkM#BIfd=Y9WY)zZ=jV<82*wJY;aO%MDfNZF7Fdyf>{kfG2RO%^ud zu3{Ql#n#|T4&V&2w?!I`Du0;Y#>!s*b@x0UU?O(C4P7zFz-4y)SqdXa^v4=N<$lRp z5fd+BFK2Zjoo-jT-1#HCyG{)(sZ|llb|EH{9ot`2&KeK&i7(6_jMp`m=Ztn7`@ufz zw@2u-TmZO00uOv=3GiI9R5fv-ehf?okmMbqNlt9>Q)1pit2@ zh&Q@>nq@2wezE32FN_iyuUZPCEjs++2L~Ue%|)h;3FhqGu-~QY?G=yP*R(Z5brGmP z*M`1^i2F9hTm}kf&z#YGRjm@edm|=5_UHRxSd0L0pfq`bK3h!kxUNqHl7hWx^{KVw zRi1TDso;JkA!HDusa{DO^;~?|@%D^pNRshQutOi9@e=tTA17{2?gm}_R#B(C;eM}s zIbyp<>0X>K(6s9PTtobjuJ!aVkJs8m1>A9d=@(# zjL}~>hS_TQy$s(ebQk{4`yG9dj~Xsp;H3{epGVYVV+M_UhX&V`Isym9l*?yQW|`>eQfX1i;3%{^;GhTPiNW z0l3q`iM-!6ZR6Eab!Qx+hTZU8#fe|mgfJM4t2u6o%4`p~K1K)%-a4a&6X7#zRrjS= zZQLn5`9pk0%$Z-K?T7BsI4y0iyw6^?Xvbwa*M%*Ru+6909K8WWKDjD^n*!raEPTRr z&?PEiyzNCpV3kF(7`GYz_)zlThn{uCUgoRW#^j%f~M2|SjhP_sVaee1-fBldGJ{BwO16Vc5!2VbSOgv~Izq6PtJ(dZUu z2FO+Yciq+8(=bN>c8)He3{S}cfIimQn(~g-{aiOg_GpSzGe-v<5#stc&F%)IKfdy8 zZv>qZc!y09n%p|v=bV!+Rig%4EjIS4CD|ganfoAXP`ul- zy=uDW-?F0yPn9Ymqh`$k4UKE37R73O_BJGZA*Th?I3IJmp+bu+iij)zD10WF?7T&U zTxN2{tK6UV6^2YPf}Ir^MDw~=o7>%%>0cr^v%Xm#$rj!ypgRld-)#^rYZd{BquUM2 zFg%@ANX@_TN6>K`)QC-@NudoN+{I5ke+ z8Y33(2=s}Z_1z5=cx?^@q~*OECiXYc#I^8J7Y85C`^yy8LDV=t+iz-YSZuSVIrQU|&G zH#R8!;GtFg&%yQ;f)AzI>cR^`4oM7QCW=~QtX%~Ky8C_Pfq9*OSB`gACUyI$Oftta z{@YQJk;HEh@jSGh-T{`#7nKmmEDP=Rv!e>Wz9N?RS`smhf#DvC{#;SGYY;<&hE*vegp2x&oYfWz9kZ(qf(Hix;GUd*5h3PDS-o=aR{`| zryD&3cE#9O#>krm5cb#zEWEsm`CfMX^bqmw?d{n+OZGKJn>OtsK)c-+rYgip{1_Q%JQ$xxQHJ*m&qFE?NbsuY^MS&BBtVJ!}Z}g^W4ua zGJZS_wQ|z=}7Wl7`XtRnns9iB-n2= zc1`f65|8Sf_-ags1IrT;^7E$_3%gW~Yk8rOQPLfxT#^c#nqUP8;IzEe=@SYzTqr}}v;#tmCE=1z#S{~8f#hoZjqHHofB@V}$wPNpGuJbr(k{g>?WMSqSYRdT*CK@|*ha0k~v0eXh*z`?zQr3maVEN(-b*WY%`bh3*BUpe^$4KVOs9KWFD+ zkjwaRXIl)lnJrFd9LxHI;-gh)Uu!CD`a1*8?Ki-JJ5Ahq;V09E8ZGn9cCz%2X z2dep;#s!>tnIJypn-!)zMTW!cbBhq1POI+2x$5d`BovfR;8KPtf6WEilcjE>Wr0`K zVb<(lTm3dj(%-?c^OE01ir|~mQiGTsaAm~m?!TK3mP23mJviFW|3JlUr%Q<=osAfX zuYya>gOr?XpixqCOxkVU_ohUr6)TvK!>R$6)_FN0E=6TM_wR(cI9AEJ=qQF~$(+=X zpPv$8pFY7M(cmlZ^G>1`g17U&R3V`8wn@0N7hKC;{=MeP!tDKNScMFfa3amx$sSbSWXT zSs?c4_D=nYWrlIFrV04Yt|QU?=+I@|&`6e~B0eU*jfQ z(R$V3R-i@9=RkeZ#kUdcy0CHMU!n-EQ2hO;I2E}y2)xf|4 zjKp7V6l7=r-C=DRZ`1?cv{$z1Z0%8LEcE)H+HZubHB>!9se=xNB6`QR1;|rw9}4(y zN9SuHDOpg!&^7Hb7hv|!kQ`cvp>{*=VOvsDGd^$bG&w4bSLp^Q1mZ>~TcTKS8Qgx+;UKo~mvr!?F% z;z_!&TPm0^4`!ZMx7+6!>y$Ur>ixZf#UP`df6?*_^|S>L`2Nj~84qKTJBjxZ%=a&9 zz7;4O-fx&vA8Ov4F&<0uXn;W^`lGo(_m>#`dHVEza{O;m8H6pWKdyDkl+vCE7}X8l zNLNgD#Q4iKzG$91`toS-uexrEknQSY7L>Fl;}h{4(hvCf>M|_xz;KHK`wFsCM7#@@n5IRmEpj>Uw(7l^vDZRvYFe6u-F4m`!ByHe zWzc_VuATd9U1l!(siyy*1+Z&PdE9~X8#@cx;uUCBcq|9mH@bwx#Qm(){w^9HGoEv= z8DK@wS#T^0Jm%r0{~NYd*-#(~`0SAP#G#FKw8`6)P%}-)LJm5jSMq86P4(k#>Ow%j zWu4$l5UBNKFhwn_4hXiMd(o{bFaIU=o~knbVhZH6)AMYetGPXYNe};+oBnIXq&fl@ z{ZyE-9IQ&2>aDrq|`+g7t>945$ww_mGbN^mF=GG8Kt-C^J!-H_S_d zx_8HpjiEx1up-mBRsQ~FOd~y;NL0$sgC4R1FO+auhdggogna;c0D6zt{gRfvoa0DY zKoDL05}y0XM;4G-t0z1JZ6Jv5=n#Br;IDRjNN-Ukqhnv);3ICFhHjW#(%#=A4R-(r&F#R`1Y)4hBy8D$A4$_TVY zcXEOS1NgkysM*D05jSW(GY=y4Xv%dSa-cv}pDS$p*k){U-1 z!2gbxGPnYQ-((%LiuX7sw+|m4PvZm20F=8)WX<3~@vmekem~XNSsOs6GmJsr41Xe? z@}&um4cgiEK$Smy9Kj*`#=sT$fCS_w0cPNaezX(OPU*zQ*FccqVrw!0B4&bhkIG1v zf2>UgdJq?#BkQ4Nt9@a3x)RnaO^XuQ zG~(s?8(ORP*BcDU!Y*3_8-0p&xJF!@V~Kl~h6E>+U(sz=+iw)qa`ybf0|I6VC%*y; zMk+ht(lAjJ`OjUt>A!;|8_Hz;nU{t{i8~K_6b<$mj5Rt%AU4N4QO|{Pzu5JNz!*|i z>-(4@+&W~9{spiBfU|#;5^BD`x5 zM{v0^W4TVHUK=^Tkku{n$tnmJsuxK^68+!G0n#G)h-7e$Amv1hIel&KSkW%^c;{e; ze=c9amLx|R)wfxyhsA6e(h*>rnG&rVtRf9-u>F#;^|} z|H!_6zND`|APzO(4X+yn`CT51w1@1je4iDNxagzU@L6pt2v{Cymzq+_iA}vHY>~>P z6Z*Q+Q`)=6<$mqdOXSiawV|H}WI_yR>dQ9l{^e?wjRihnjN_ zPm&Xhb->4Hbo#;;i?z7|{>QhD)~Z?m zZJAu`9zMj#>gP&y#2$x!7Hx4?xK1zJD+C7mb`woZeF`;h7E0~|)CZYiL1S_Uvj*cMZ5JIvb9&#a=x}|qv53LjR_85p{_6v0G)6b*D)rLw{V$h--(H7p`o*CtfUWr{ zGdpI!e8WP*ndx|cJC*BD-;K=p0b-3Lxi4ruW}!G$uKWBS!!ZGw=8?>kM+pLw4gtoX z!Q>L#?Ky*~XH4GuzZT!&_R!nePoP{IraJ_QdYTnt_#QRRsj*{(LO4~$cRyHkKI!z^ z&M2|TM(gHGrGqp}#jUKBmE;tF_AY8+dh@R)Ao^zTHqAML3`?-KT|?sLzt+<}<7vzz z<_J%zMQ)V&U+d@)y-9n8?g>`VBPb*KakUc21FFNTCO?gRJWh>yYoXBLOSr4^qvLh0 z-M~|O7(`yaqCwzKh`}(vONo*(7*{Ka+Jnh70Cp?=WRKFOrXIjCyNUk9Ggu1ezuTxw z`5l7Ge#kT9Eh0cDaV8~2PKPZLoUiB;W*jytk6hY&=aMkV+P8D^1mid!GQT^n-h1>@ zdfDS>XV?(vn#^fTzU88O7Y@%HwJ|2|p?25+Q6KhEiM2U$3*+selVfeseOTJ^d4<1t zM8U%`u+v=bQ@+m1rnxL7Q}x*yedpxnH>280a}<4eH&?~33DP8wM5?=U-F-KZ7uWI= zJLC0!Sf64QYn=CkS-1g0)6#cP7~mN?z6T(ik^og_uG$l(>`*-xQCU{opZHimaL6fcd54-TaED{u762y`%-E5qeGP-DT?d9iZHJzfoR*Rp`1cIm0gFd z{(~QvqOW5BTa-_e*OXpb~NKg&o*B%Ok%OY`j)M(J?j6A>4oYQw6|5NFEdN#BY=!=1f~v zv=)+&cUX*PM#ZR*5=Ab59lvD8fFh7uQ(m!&BS`RkiIEUX_+}-I(<~o6bn-={ofz54 zHH-J-lRr`_KyU_i88Bgfq$C?dZ4wa7p;Q|;EdsxG~io#8C zgLXZk7)G{V>Y24?e2`~tD-288`M`S}q_^3}L~5Kw^TF*A5d!;`o1p#ChN`LlvjEwB_gFdzBV z8_Q619;(tp>&>Hd(Bz)QGWKqVH+`T}f(?GngtmX~u-*SBah2sh!u_V2mIHnisM3tj zTRxpW;29kmXYQ9b9cQQ(9h)@R*5(*&RBTvW%3%y=2@hG}F<{^aLscjK;4EwIBha`| zJ$B9CzP~EO%lwK5dkJir+8`A7cqTsZjyGGq`4v|BC5J2w0s@`v!G>={>D1MP97~(u z%fg;7SZw0eix4c>dJuvZyL=JEo+4*uqH;PO7Cki-4+vSbov)!S2X%Z#?<5^bOq{S6 z53X`Vw0uqgkpK2aZI64QeJKyf9;|$SUYJ`B)Nx*|l71s`^9N^4r`I9q80d#Zpi28` zvlPjUw}OY6ElGaryemaN95YFY!1*usCMO#kAM0W%7v4c<%=54Uu<*gLv=^>VFF#lm z^EOSmCcFDm_Ou-OYCjW?42xHPXHyb;Pr1aBTj;Fn;`t3!n%jK-(eZCswu#gxzFBeI zWyM~>Cb&qq`S}osVYk?@H(Sx?N<$@7)5W2?^xby@f5RETr1pyqq@-NP-i2}v%?t4G zJ)D%ycp@MJ6B`aJjRynTn>ug#BdOpYkN<3o3goKWU1U^qXZot|?&wQV6qV$(unyh$a@4;k0h)v4?Y%4l<3P=#`n*kEdD@hpSr?RhK6ZF7VHc z{~GTKUf3jlS;-FpokSJ4LuNU+9e5UN3AMX36;hfcthQYVHM!4luf9QRimb9h{#msS-A-BH|UO*xU z=*y3x_jhqyLa$M1-})Hc$0J9QZ~xv=_sISBI%p)IFDAqKgUQvz#N7{+iqcZ9y%7B> zwmM(h4;;)qJl4a^k({`QWdlVp^jl`Vz-M-HhQ(L1fYq@!$8#JGYJ|V1L+zh}{8k5Y~yL9|kiIE(aDx)s=W$MGi1>R!Co`cCM@cmu4}Y8)pY8QaZ% z#%%hGqii5zaFC>5^)s;CBWWCdZ}ZZZlUUM>tD=fgK5#_A7M4#iNQGST;&AtD@6|bCT;cYe-hwimhgdB-+;s5Pv7)+yCG`jC*Oi8h`;Y zbxvrov}gr@E;p^2?ogLriut!|5G?rPZ{YUug6|j7j;p|c&4IXdI(e`IMKw0jnM6`8 zf#L&7o+Ro5i5RCl%8}X2BhiM!gI5)MXswpwENm2o^^F!r4PFGO*~P;U<5*<3X(Q?1 z@n;+XNuFXz!2_q>>5;;Y^DimuGmXoBs+CojQ>LB<(i%N^{O`a27#%TTv&Vl1}axqR}Xn|+? zC+h#sz!&|=_NW}E4hETld@XYVp>MZK>oc0B)xsY84ZT}UK=0*{ zUpleFp?R1zN>Sc13PtuzpbAjN+}z;7`y}Wz?1Iu-e|TMd~F_ctO2o z8xNw>uOWD9qDVbc~XpwY4) z*Dar6A~>zHZZNMHMN>i3hE(ls?{fBtk$~SNlS1JjscB95k%5<&!~=7Kr}^mOs0V=O z{(YclQk6RX(MBr0sS=>hQ>}CYgI3r4^gOYJKj2nXUY9ErVoZ+Z!%9)3UDQ*+e$BHp zz-s5m!;+3i$`^^Ibc3?M6U;A(KA|cv|K0v*(Kb6{VLb7Jbh!_eqM=QQb;+YoLqBsOw-ru$otr!k1PGwr1?hZtgdeH^OOmlA59U?5-_Wc`*R79i%#5!_#iz9xMM{mdZFlt;wfU46b5-N{o+!E?W2EiS0<&KNryieu#f@L%THZ z{wV(9Mcx)N2MM^^SPT`FyqdSa2nwL3MULt3f#(c>%AG`zVD?9>O|Nfvdzj@fJ*EG# z5DeNu_^GL>``F=bpEIp=iKQ7S@ac8XpKhBGX1GfTjxQG4F^bn5Efz`y_?9`AvV0Y) z%SoDe%1EJW*;(MTx7OA5J2)MZj`dxc-d3S6SDTl+F4be`$OA^HXt_V^Wq`$LoPxq>&ghejcsj-Nv=| z@ro77q3C-(+Pc*DrYs$2>4iX`JVj1m7Il2?Of^+64=JS}i6}Jdl1NcEamIqo^ljb8 z3q0ck8rkKD*Xck@FYv~(EbF9kLG1P#HJZ5AEy_VLyR+r$!7PTLm% z`bgn}B))zG_7SZ0D8lFJ>CBp!LkAr`aTf=IyDQ62Dn2<8{RcoN0-S`-*@5RjJgJUt z;|uBtKZLR3L$*qems<)iKJv^W(|tU_u^(T*F0i)P7zs!(1h~y)r|9(QKuoTF`WQiP zha6;_nJ(xFRj@nBFPS)auq^2_LTAGE%6@B^uS){*H zbO}}C#uByLS^xU2wOCG((&K5iFC!e z^GjOlMgOI$*VP<*`j=NZX$pJO&h{CA7B}^|coNq45Gc-{e=a+oG6VD_lBwbdyLu8S^iAq*7XwhCp4CoMddEDh{UieA_4 zvYT}-!)*+%My9V%#Jum5R&cgU30pE+b&KZSWRdaH)eae4{CHkHG)jk<=+@4%()#tQ z&0TktarSi-B%z!|)BFc5fu6mCr8x8R=i1-DJDl?p@2crD<}^I;hd1UFNLihrJeD@# zOG|c`<*<<8oSF3ezqAycdUhY9nmJ$bVbHcI)(2N<4)OPC= zVo10vwL)|r>2#*}x7sXWdE#`$3a+R2717%Xd-XnNVuh$RCdl1)B^(fPihE7LZRPQ5 zOFo??rgu+X9(>Y$je`g)@{n^gD)WZpMpptki*Zlo?wDGGk%Vh1! z199_xov7Cq4+O{%%Y|^gseLtkg2yTttqZsQCg7o)#o0hCa~$$@I|~_ z%sM26NGAjDq_qw}!sx|X7|iQ-;Vl+|R_*=&<};sU6BORM=|1NAOaQ)6yPQZZX(Sd0 zx~W|5BB3~8V5ZzT-RMA^QQ`|JAPr+y=uGwFfu8;Xw^dEj z2>6$`Yl-tnQjOxoYxEE>huALVuDGt0Wxq0}Jmp&U)pa)6q?~>mcWJxWa9GO1{ca0p znb~CM9lEy(Yt}14sp?{s+D4J!`aSuneg?PLc?ZZ$jUe4r)4#ZrsBfs!15&HfRwxy)CxqjsLLKc$AicsM>O%(HOrUb2JaOTf&Sz# zdmq&1i*1X7%FawRYx|W}F2&9ESqujTeWX@Svqj7NQ!oB_7`;Hb#wV5CogZY17u+ba zGvO*`;-yxSm#`kT-Q=`9xN}(tu9?pwE;OySq{AHl>}#~?%2loV>w|4(A5R-)ZaNHS zaQlDpwqaw+$=#^Js7nX&D1_i;G)ZOLU;C7=$3F5i=^+nIix)O?AVHes{h^aM9#I94 zu5BfS;va?GJeOOEyaUZu!oq|Sx6$4o37NoWB*S_JK%{6hucjynz?~x{DIB>Y```*p ztiAc0(&x}(4TVx%%0)$HND)Plips3~{0Exx%6qcIt){nYRn{YPRicWLOiwBTIEjde zPJotlf2Xjhp}`#=;RzhAWg|*{=b!FA#C^|RWyYNbertNEMMpGqu`f=~`9~2jWX+ft ztd4^E4$#Hjze|OKm%MXzrVtu*2&?h&s32S&&iTgkLFv2ZN7_4he41Og7ue#q4Lh>Z z+AZU4b$>G_8`CWjr_uD`;;j7nHDq7;jJo$?^BvBQsiZtKf~1iRM5QYTLY~Y=yrH#& z*qat=h?Lc`Kz0IF;<3J?OvP6cLtdhSP~-M@{d&B$)5E(Vq!31w{hE=?WD}%j2|kwu z`YOMn)RC03xDNB|EX#!sK)%iOk}kC;%n7ba-{(wH9dWh%aeFsFn@M4GGN~Ub|K!&{ zO1RO>*S=$^aITHr2!c@I5zk@LDhT1HA1_bBz23T|N>^ICqOvAL^BjdxI6C`t zY&M0m6cNWOs|zGhW9GCpYY&icz2mK(qN)EbZGDU4EG@)1?TWzKWBmkqp_Nx2(mfZ6 zgO^+AfTJq1zWbXxUUX^5_0@Vb!#q&&9-)uRWkPDcJNPu@q(u+;ii9d7`kM)$>w8riH=40c`nM5*9^1sB}=r zRri)4Ch#s#LKF?d36P3Z88?_GRC=8Uo^@Cnw1X)~dk(Yp}{WMsNFkqW|1Dn@<|dtj=5jqAC~uc6OHtXI|V5-=UZsTe)<-~8e>0WAl*m= zGgVL7Nla@GEO5JTIHSJd(zb*tH=FA(NpFV> z9U#lnI5<~i0qU>gz*N|fjD9M|WE!HTVYla*P2}Ch5#pb8a1-PsP+I(pjos>3#^3rI zm0KgZ$9pkZJqJj{b9D@ebYe>G0R=ZLw^GrNE{->noWd+Hw#+d~m@ zPvxLRGMd2*oL6(fr<=$L>^Lb)lb?qE1&VzZL_Xh}Qas*jRpFC@^x{nA#rd;l{I@bh zR9xq}iAjH{VA2aZZ;GlDi=O0^UQn5{9vP=o z83#4xvrg;5=5pUPH~G{MlF|?Ysqs3NpdbHKjx^i9970VqYdMUkPB_1QoDlQe`ZEO4 zfr19?5~>x8%VCh&zTShsodR0sbnD!vrl!sTC{nuE5niL=Jk{gGqQVVS|9-?2#GIz~ z4T=JmQ77{)ZcIS$-K|9VYm&Nlu^O=gitaE25YGvvj)+w@i3_^8kgeQ?jGzc<8+>KL zwSoUI^CSi@Bh;h5$|>nWhlyku)N`7D1Fvm{(5wES?@d?WDlGjPf}ZiivT4!nTajUd zBFMV<=(%mICJ3^GOTfuzP)2ICDa!-iE&sTJ+S#JnKz~TVuC&cbe&+4Z(P%Siw^Z)I?7Odk{-)p#q!q+If3O`fuI}7*&#%l=;_nlgl?pRP)Y$& zTjA0ZlI^~d)^PmW(1ESJn^<$SOFl!aXQ zT@g#DRpywi3S{0%z)Z8!Z<)Y|?g^k7&uNtwp&F7SrueN?d?Y6dvG2|+T&+T?_r|Uv z-6VCN^KY4o{btH%tSKA=-cwNhJ>LV|YMb%i1m$IOt!<$=U51W811P5hj((qaKZpLo^LR;X=`v3)R3V;Sh!nzsEjiY66-S8ofg{)~~At;MA z7I8i;!t&Hr{HR~Ma4CdJNkb$rVl&_O-;;p3g9ynFl}1H^m?%yF$!58q9^+ot#SBE* zk7747zJ^NF+tzZpgQPtiQl5DcNyzNvbh{!vs3%@h<;~_ z4ynCS{&FTbWto+6P$Dw}xDMm8XIKQkhS#vBn40Am9M3+VM7_}inbzi}A93`*;zVH| zd}^=?WR6Bfgxe3Le*ULi)H{yTT-)C9nw5b>n&9f?^WUW7B2ATy)kp-REt2xvhn5-b z&rLk1jN$d+aL8Q?M-z3O>td=&H4LYE7Wjt_DS(j2ViF7Bi?mj_(C+tsg$8#zrlE>L|n>)f@kxRA5 zx%8~NG%`2Le>BUdm1fdYoK&-4C^+5Zn8DM6NfBf6kmxozUuf4F5MT;0CE-7d@|>e$ zO1hsNth1DE*w}?o#viQPFuL8S5amydU1aeeQnGfG)EZ8X0}tc+E9&9`aw&5S20+3^ zAt<3O?u+Man59T6KQqk(hFIIK56=v{PKsWvzqo{#dcQQDI|yJ5s#`se=p-0sc4@K$4gKm@nEF=@H-mXz4$b~D z*i_QZ@Yl$oiD4>;rdskF0N0kPv&)J3>&5FH%WNdhP%3Hd$w^rVG!N7qIK=CvSLO&z zVvmJF#*5X+fivV~MW|vQXL{eOKUY64=}ZhcAQZx^km=s75Zck!>fn2xH-D@C3S|a< z$Nl1eqvC{VP*DrB*NmWkeJH?us*-)xiZXV6A(kU9W5AFLG8*bP7rr8V!{Ctmq6WUb zAt$@9H_q3J3`;u}L|71<7(U8pP#SlxS|>fTR$vd8v|nDR-ysoO&PwjJoFDw>5n}Qr zwVF}q_)&}S@lInY;|tdopyK$BdsK-}mx zxX{6m73Fh-{mc&`WJbdQiz-nhsi?G?OX2Es+pj|l*9b6{%?+{CiqC>zE3mi_q2f`7 zKr-Z%5a1%_E~^cgW5ZCKD6~DsAo~8j!lP`C?qCE0uGj>ZZp1+BUNj>flU63G-7Hq9 zVmb0p>0_@6JlJIeN60KAXSJcxoYIvQMfcUAG$g|MUgOK7bXG37N!BgmpZPPF_o4Km zbjvCIvBJfaM5zTNLj@`C0OmlEyCb^pNuk0-r5{YQ-TXEkarOI|KcD@6?C%G|=MW(>L{!vDq8)_9 zlZZpBHwxn7_+&Vpd#H4~U~-t^KzUoDHpG(2=8V@Gw}Sv^O;|Zh3!|bAXQ?dEKt5Ta zIx|1bL?>=2M8F}HHRc!BFRs-JE!;*10CN*2vi%*lV&z=>rHO>?DX4;r(EALB)M4Kl zQ4xa*DD{K6x8psD9e-FF1Yh^Mu-`RuHLVz}Csdozt=q<1k9^c7RoMU28%o)yK&xxy ztJ>rHzOqeB7HB+G`DAq%$@?8c(dK{hJn5IJ{*~PE+>ssc)rAAB7KHx<{t7G8?~u98 zHEagdTlPa3TMLrw3J|rB;rV1v=IBvV{n0o-RL;IKja(hn4Xi4CWQs!R=g8!L4vUvd z1c;>SM&bx=+oSc!JNjvVfoa@zzgRpH-2R2dx~sv92zrR)TIaW!-{b`&(-mNiPVpRa zeD-ZKmwac~Zpzwdge1RXd*n=&4}#U&aE>qK*I+fM)#$w9ThXX^&2h0WZ?~5dnMEJE zmVZNs2RWme-*)EhuL5fF0xbjVCUoUiBXCDzKc%%P&EL2Dok1}TsAe)V#5$8KqhEH5 zgXLH@NMiwoIGLI|!O_`ZgMBwI`v!#5FnQkpZ)7Ag03%}z*tYe7mP*NL8u`Ep@cODS z_o&}1+)iE}jlA4LG0f+xRv59Dt(`OjYypG5#nyLWB8JhGmXbosLPi(y8ef5|c$!Jw zLbK#q+>kddq$b+9-MdWWYvkFpeV*l7Ya!TUuv-XG-IK;_$`>>yLnX@Wm>ayCV8aN;+MBJJ+0=i{te?@iNFj& zss?#rXc7>>`Y)A{jwVt$h&ht-=sJ&`l{bIAX)+>nr_L+9+57fCNX?W`2#sc&HaBWn zl}uyykKgjX^F=T6IcU5BVA0=zr=NtMa?{Fz9#0Mo3K7Jze&f&Hg~F1GN`8EPD`$So zH9;$j#-#2xu*~?TK20w-a`xbOncPjY>F=6V2c*W$ro|O4GHOuhQj88-mrYW^0V_q< zuX^={2l@4rn#S4$5WG6P6`XLY5%^z5Klp7Ar)NKWfBhacv>#})Y&W=tk$5sGCUfZ_ z&8HpjNc_fSr6o_ee_WhSd!!#~Vr0yS!Yi@DaofIf~RJ5EKZzZ98t#404*k zS;B(m`CU~N54?Dy+tRPVCXqXDk)+0xvPu7I$&!5KL@YfP+#Ys+A{gEr<*j)?THd~! z+cD>_vh~qq{uxM*Bqe|@wSR%hD9cz{S9e63Sit|;Azb$ntL+WY&AU1Jr%vqp_234D zEpI1fvjz9cX>ZT01e4r1_jaJ$#*{ME`d-*qs6B_pi2J%vwJ`WH_2B`vxSI`N(9V4o zuN?xgC3vS5jv}0vv@{hmT4Z^uK30EFGa`X?-ews;k%9w>SVf}hN=`CPzb03x z5`?v*wxYfzaXt+DEnC9B;lk)^!_^>@TbzCTWZP@8v2eXVfBv`&tq=6V;$bbo(h#lV z@YUB?vsV8v#8EJ_+dIx#sH4tL*CU8_wSg<0nVg^nC>D#k61WhS^k3DT!OYe=`?p7K zPD1ihjz3T)WvMIB>@(^QjbRznv*g|#<1OYS<$UqiU%#|$peLx56Fip4L8O*4G8m+O z=TPTj3`ff$7CLK7@$k)jAkC%hF_`?*VB)0@pt8~8bC@00`8MQP;2x10mKjV5PAq+5 z6ZfEpk73K5lsuan0t29b(!QkswKC=}xD*+nF#s@A1nkU}z~inF6SaImPcN4A0;#yw zdSubSK`edj1ZS9NOMC&`rgQx`Tiy*FTY(FW4rwhpIPEbuCubCf)ldX8kS*G($lflo zOxt`bfAred^4c9=okl^BD!rhENE6KR+l-23*R`m=IPf=<%uyB#N3E@KX;}D-iBlwr z3@-qoSN5Th&>)7%&`b%@9{Oj&n>jCnJ9#)jJm2^xewALIMM8W1=v&aldixRI**Xz0 zHoa21%=Z)hG8@1yA0NbA220OfQ`YI8$?K8+=VNd5zlkz9b}si=I_na0&xg{;v!2ti z2cY0m$@%epTU-gm%%@)zOSM#a(J%grs8!)&t;0{Se)KfZpt^jzSES2%C43%BRrhaJ z*_a_GgeLI`Zm`-wE*i~!zQvPuh=MDg*xDybS;SzJ3wU^J;u2p)?Ry+HPkM+yIm|hW zP}Efa!akUvx4tF7cKs(*SWi1@;Pt=e%GTstZILtB0l>{CZ8NX9|N8dO%ldrAEL$`X1qH$^pqh>_rt(D@ZXb#DK;`^%+W@JZEJwe0yZ1b>>)lvjlKK8klE# z5ndRE{##h2Mf?2C)&9&2nXlz|h=<%tL^^~Ewbx|CQ1(jP-$O2zN}N>-&kip;_C9~u zsiy-@aQl=o#SYaUx2@UgfBJQSV1C;$Cx_ z3Xmwb7ccOjT+cG53H4u#=VhJ4v zfZ4U+p&A9G?BJk;N=Fz1itJukM9JU;bjnwWcqD~N@vC_fy`_LUt&-LY{hSc@tPDk- zl|z2t8RA^?m&2%mPYaz-GT`bRu4?Lk-CRbYK|=fmUO`7S^Ptd zHG|(Vy%3Cn%#03RjUsx>&gy#Vg?`kT}nh#4QK*KPU zRgUE2o>is7^z#INr)wnh%dRcrB4tBn;yg-u2&FF-ojw7mZ_=RvIk1lMp_oFV2#mpb z%r9{7fjW8Tl;%Q@Ccxz2;e=?>ZBW#_V!i&G%o)=2nqCn%UT5XE7^w=()@N$~AI<9_ z{-^wT|7yqI^Tpa%o{yvJyC7_f^{Wd}gwE>8u6fYj7l%1L94avyB7$bj&%HnCRRfor zwD`S#BPG)bMSW+8@q2=Un0gu+BnQWiJ6B?q@5CdKA=xDPF7Dj~hMV$abrBY!b$`=H zO34K^P+wS-Y%K5asMyx#dqGzFs89N0EGR+u=%;R#<#$ETwcZRxlSDj^U+LV1Gr;df zN-k=eg_h3d)9Yd8yJr)_M@?*OlRzRJeRFdIj`uFmZX6%`Rw-d_tk0pA0EikRC#Zxm z>>PW8ZlK?uZRhNMV5VH%eNkfT`)$V0>fTJfjqt&!q3~f2+(s}>8jcq8m5gtaCoXut80WI_kGJa=uK3b z6&zQy+Y{gqlvpgJu>O+5NdXo0*f&p#s!f$!Fz5oh+Om&=@v|(6wPtlz88d|P_c1^y ztNT(`s}CPuMj9Ncq9UiO_i}tn2R+6YNk525fcg|UFXt-hS+B>EYERjB-ceN3*RoGJ zM0=Kdu+7~*qt3DyL^9)s3e%J;NM_Z@snSj~qV?TLv7VOBp6}*#{1#nm_np1p;=jY3 zD$$ybBAl4T`=A7bUL1`Z4kYwD&nbKj1}N2H&pOnOxt>$VfES+efd-1hCi9Rbe5Sgs zvEBBWoe)=uPK%u`6FiPT&j|?Ls1Rs&5q+>=gu+L^X?~RbspN1u3dyyl7INt>O$*4O z+O-2FLlviMBlxpo;LZ`&>pw4$_#T9b2Q=$5GBBzxP-ZONLr$TV^ciQP7C=%)2)2kA z&pbm`<9`fa9o_}6!?AV7tsz4w2jq5sj3 zNiE##l2h-P5gh}8l;QErP_V9n(4xKduNXU$W7`;@-T)*d$(QIuDqDQesa&NHOGX;0 z&3gshDMjky7h5WeYHMf+(ywFD6r4hV1*kQKmb)M3fl?}*42vV?`zdnir5oaLmK~?C zhn^y#(p2eeGENZ@IUJh9e?UM#Q@=icEdytW#B89!&F`N=tUbaHl^Nyrl>GyoipX;! zZ5iJw1GBBxZqr-ae>y57WUl3F<71G-XVw7e6j3TFNDG`9wP|`((s29 z(>2<&DAx|x0=wtaI9qpv8>nF{vu{OR5X$F|OQLw*!sT_3)JniNvFaCcj`_rnR)v`} zPxI-^JNk7jB)SOZh^OT8KGN%9`pB4aMl?b0ljM70=iuLN1$K^ECuL?DTU*B0lff+v_WK zO-@6WwA3?*O^wif@$I4xP< zw-V?6Ik?nRMq3URN*VvB7n{Xl%R7}317QlPgHK3oI=Cirry->xTDtRJU@}15U=y$E zb22&GYbx>hUn^4>O%djy_SosrJTr1aztmU$Bq0=hi&l*l)%Az_znDx%UI}NruN^yk z94vvlwct4BKp{uzWOClfWA3auzt1`y`4|?20P{ZGBt6MsGf@Qhu;uqCLhWK?#jW_G zC!~-;$30UuvP0$?>KaM&lZ-}96L}g>$eq{Uew%?0D%+MX)}4Ct9o_{UGctEdH@vkPXqGAIvY@J++UpLoRoAQ z(um#!Hux8Q*Y=mL(;+WX*`gam$1|qhuckl;(D@m8f~;%Mm39V`MyRn)fKsj43c7== zXw_w1Z}`PIM47=gszjQ&gm{~VZl*v!de4qAZ((5XKYl8kzK{1IN=YWI>$oKEJvDPQ zt4%30D)#h@>3$iuldx#`p3$Q4naGwYkRtre-~4eAL4>G&iGU+LVEBJ+o)w!>ed(QxVL0yPa zrpN-&gU;l}K;rmDgNj2BMYLRS+P)`lLxbCT1s8P+zXwy1;(^n0{CW5TF1d^Q9}vMv znC+kb7d0FCeD(GGLa{20glp!zE zrPF2D2k9Y}8ah(bN;c_t*CW)yA3cs&zdw;R%Qt%kjV-qrNRYNuliK3@Puv1XBm^2o zH_6PdBul#9uLW>Ju-ygP%^@zRuNrKX7h9Cv9h!siN#TQX0_(@0;<=P||8}!fVdZr4 zI~tfJwYx0-363|-yvq4bX4d2=U-?N9jy|i{=IJE&q|stz17}FjGRy(+#Ja0I#8snO*f&&csZU=9G#=4fv~~DAQ!&r z1nQp*Jv+5@ExNw;S1o^v-E2J2#r)3iANxPjpKX(;lE&;r2mEWw*?;1^GD$A zD&KztA0qDjt~y_m=#b2~6I}*_#~y`=kWw=GqX-Jc?`jjx(p9tA2JyPB@n4;CSZ{5` zb9(>1bW-1&4|Y&1p6eB|Xix@eGq5vrn(T}-M0{WsoMUpD|m+JTb(6AV>1$IPQ?*S5^j>}}hrD-bnJK_#wL&9!yr02S$w?vIAtPI?L3v_cm$6H`(t;k)h2?wU(0g;W7rzIg#&D zsPo4Pqq;j~a3FKywl5zI$T@D155-Oo`lz~pd(^VUNOI%@&!g(!Vsc^x$gPTB>t#7-c=Gyd?J01N#CyGKw9ty6zv+>F?GKXC){UOqnE4|dy1{)_2HlCGd)@<>Y3}88oi>wYKl`rsqPT`}982v-)PvZ9N`{Wdz>iJe%caE!7?BeM)=gKt## zH9WZ?-ZURK&dQzxI~FrH)VA^9^0}vFlTqiA)o4yNkU=N7oz0#xDHSDLExSKl4tOl! zJxl)VdkoZQTWe3m!@wvtg(oX0d z**GwrG~~GCX8%J~b zok7Fy1$ygf_m?WF)m(F6Hpl_FsFq|cJj1{Q$`0tDum)e+mrWD-HcbE1K&2EH6SR z`^5FIZ={6B^VCoq(jK1nLF_L}_3A4w#%oI=VAA9^?cJV-J-IyIF%~a^_#`7CA~-MF zd#}?$1zgm`al;K2RA~E%sCb|o592XPCpuQdTP^MlAr6*3Ve?dV|~LB`ttfiphX4A}aovzLnf*Yh30Rmo)6Y9X7mNT%Uc)GFsxhKM1f9IjABQ0+?S`10!iEpUge1OE?r-M_GI+z=1RX^ScdhPV|v1|>j zJI;t++bfw#4$z|^RIj7yP)8eCz5FQJZStU+#X2@ef<33w^n8wgpl$nufiOV!2gBU~oRX3p3G!og8lp5yC`8Gn^x#4_EZOLo^f+`XHyq`b zM(}>)2r2EM?}c>MiZO-i6cAdecFUj@x^o;knKc-T=gkR2k!wlC7TQkM7KI~rkOa^w z6SE<0aX>e!JxVLF&o&fyZr01Tq}>}`)*1Mbd?$8RFWmd<;YMPeuDtxVSV|pb#tFJV z?_^CnfHN|^w5NxhZt6EM*OQB5+@vTK4Gw;OI06_n!Sc>q=0x_mCbXTd-Zb%{>8lgw ze#+0$aW^^lHf}psl|LXsIL0%14JLP4oR7~wvf85K)7Zat70kEjq4kX$e_WK}%t388 zYPeU!;_MPYcXv5(8z>Y8zgP;#@E8|K(~kp@rzo1N46>c3-#zJVJJE(A-*HuA((bMw zY2@Ib|JX28pf_BK;0k|BJ!I)V!W$`Z=x;Mc1Ff#}~x5Wprg zUgm$Gd6TXvoirih9{nIE@#m8n!|%*q3#}3gQ+Obp{+XIDab&Aj-7HtN$g1eV4YQpa*Yu3B56)k6rZ z5R`?ExVI#0`P_tc=jR8eOL*Or>ueeO(6*sMso+x+ML%i8`McCQR2m`4iX5YtIBm6$ zZy#4YDnn(}t}si@H1%fL+~|%G`26e>J83s-HrB&8a}hK*z977>^x0V~1bpHdTbBbu zPd0*Sy@}W!i~s5U)CX2#>R*-9KEjn&QE;s@hc-wEXB2UXP!koU{e1V30k-Z$5EKid$ zpBEYZ)_0kWBObdP$Vx2o7x>7)U~Zm4L~gOa;A@HhDuuC8VIHqdyWxG)q?KT1yC37@ zsar}MNck9uvn1)o{Bly^xb_G$w_i4O$2GU%ZvBYkWq}!B!+PO$1oOdPwe8~(d(U8m zcD-wM+iSuv*4>Ej-mX`$dtq4sHl&Qca=2x0e0B-`D7$Tw)x@kJL^ zv#?9FSs062>&=R~9cVo4y~AqrWgChWL*as&g?AJP;yi}r4uDlU*u)~yr}Q}Cn!rc| zta%qyCRmR4{g3A>wB>ZCA4@aKiNPAuJr1qZZs&Vnws zupc%(1|H^ySWjlp z9n$Cyb4dot8FB{{r-Q~??Q-nis{8jakYgDsJk^W@$X4s$HOl--5qs_S8Hd3q3Zq5Z z8Vc=x#VAB!*3<%w(8hf@2!i*uoNLT?w(Qm2+rZy)ot5E2(6dSA(M?K_emB0m^EPbz}TAHEPPp?%mZa46%6*Z{8w=+RH6}cF7vbTm=?qoKYdo|uD18Q&pzJ2 z`|~+=fcz~LQm-|8(@=2D=l00(y~%Dlr8$@1Co`ed(&qNkhRphtaoUgu{1opRGL_rE zAY8GFp4ru%+W9zQi*xm4CFz^&jXCPAeIj4pNG=Ea^ywN8ZwI4_IWSjOA^IEsL4#-AY=BsAtj=dBY^#m zmM1?iV)B9us?my_FPKs)W`A&7&%j_TNXa6SH~dMYYTj=<-#q*DbTaO}Xu+1@?t#&b z1!L<>UiP`qq4nKSnq8 z_=DBv*4DtY_UAF_R(W5K36{NIU^VcNYdagO_QcK)Cuf*4*YLBsXQ?BcRxs;sPIYCn zDeskt&Ys&B$S`re?p@Pl+UKQ2_3?96g?UXYyNKP0rYYBgA-eBx$PmpK1o+)+x$if_ zzk0WL4UAt+5{F>9{-74)C)4Mf47-ieVQnNh-j7~BZUN85(YWqj-mpb07Y8ZwZ({Q!BNC6y~DgQO>`}JjUB0_g--?r}m&w z8=rV**J#v!og`7O`1K@X_}`WHJHW4EOO;>A=e#NEs-35?ag;Br#^Crw%H;og*8{X_ zBIRT67|^u7IVFV@X39!$dvh!`0lkQ9Zyk$5H){Qta{8w`hPQ{Wj#^P$VrNdKcG*K# z@@!|P$K$dUSot5OY)2=8)`Gt3d*8mL6%L7Qd{fd-?cel(PvqjPMNV#0{N6p!SesxT zNDqDDkDHbJ@1#b6Pb0)!Oy-~?II)zf)#U!taqUYjh~<2VmLlRBa!dD&*6TPl57w=p zR)*a@Fv=E?Zl+Mw@kBe_j|J<4F&PErMG@0|_=*{10d3@@U8-|R5e%-g9T@{Q7N@Y0 zlSB70(idV_)mNT|536W=b7C7Ud=*utov_x`?O0Fmm=L}dh!FWNnI zSTdtSf{*FPeilj!=FWDl5TEOV+_iwPepKN>L&BPGZ+|^|55)a7&P(;f~SnU(J+ zgXG%)$@GKxBoU9)Z|`L+OcwiB`XK~<)GHjujJ=`&UuxHF{0!HzBMH$93GgpWDFX3z z%(mt!R9T%(!lW$#1sgm&xh2Cq@=9K6v0*wu>+&h@+p(77=@pvt9agnW>Fn<#E^B&o z-Ike>frqj9q7flpMvb262Ns~{zY-hJ1a#I6B~`#sVj<*`-CBpN|5mES$#li+1r@*lS6w1z45??<05RIyg*Zz(D8cw6Og`y4%LrSN&kGRvET<>Zd^f z%6$S@*u~hQwe8)JSZOyi+0$WpmF*yrA6;blJb*#q#i}`X(Ha|L*a3?^1W__RHn-2oG@)3SV#8d`sM7(BU z7-4%2k)#O;V6_U0G`?A6^NXPx7S)}I*IQmZoEqvm6EPB}t;S%5sdz>Yhjt9jIu81q z?@XW2i?FTKMBGuJBdbOp>ek?N;X(8O7=+H!!2eAwFU@V06GEQ|Vx0@WJUVM(QFqDx zdZc~){53?KYw|%(f}yuEzaX>gM=Tt15WM(6qCt@d!{k#jlMf8>JmvZKs5C~*KxUN` z`dpH$>W30MF2eU4`m8>L%!QMES08~0&Qc>^3dtZH~5s;*K(3WON!lo$&3*U?pq+@LHlS(swHNB(Avb| zK_2uzU*4OzEU44B3I!W>}R?>x*E7*m|_lg;qxuGkuYcN507!YV`#m8zv{X>?{`i6J;_WA#9%~e z)Tm30)7*VaXL|iJ{xP6UA2S+jYCZ{c^jG+xxBxPhRA}O!&bq5I;)U|hy#L+53e;{R zp3p~Y=#-46Kw3_8G@3SNB~VfOdNhI()fogMSYZrxwZdWjjec|nY+x!R)<&St2F57MzwVO9rU$ zB+h0n3x4bFq`~wBkII`@a8d7{aV$QzU5Oq_pclzRbsS1OeBbnL`J>=#&&v&1rPai4 z>yGsECou`PptuPla%}Z!sXSBpnGjeI>kA^8eCNc%4%B&GuT0gJL!V=ccgo?=m%sk0 zc{Jnb6GyB{;-6*|9XNQ_fj`e_Z1jt_^(NcIQ#%4&sT|x2r!?(ylh@WnSQ6BY3#Sb_hD%sKRseDUwUexc=d(X(@iFX-DjT`ip){`se`R}}a_oSxBTkMJ* zle`;0KHT*E-N%P~7##|bqz$c=1A?gl%sjO$9oA4IEF0+{gXrg&Byq(3s@{k5Ks)CU zONkx3UBmHitnb9fl7h;&Qh)GRG&s%}1j-l{VE~lqV zHRx@ph$AbFd*b)MM@QlnZi1UDPg=$PvLN7H=Cc~Bs&em}Zzf~>Zig>0Nk}m0;~zY- z-IKd7E}0QZocoNw5`0y|_z)3^m$tt@ZaaqrD1KsR;-2Py`}z@vb~Mj|ijc(+BwrUV zT|!iR>o8D!Xj#dt5g=U$%}27OAK)6|jwZvPpR6KtlhMb+EB9R`RZRIs^0}8Ol_W8jazdR z7*f^=K%^HznydN#!fmQXGN~}RyRRM0&-}WaSMvC7P3-R1GY|D;%Y}v`3i~V`2nCmX zeR6ju5It4rYZe!5OqT1Lb#15g0*S08YW2q&if`s2RrKq`61LV$$6{J^xLV~VpI*bv zwez2ejFuVSXsD+tRl5F=ud1syoqEKMcX`iDfeRAMcqR@kj6CsbSVVEGV_kKy>Q%xU zyP3M4>%)YoXEVX+YtKpcpE*;i_x_d5ijaJHULXBS5J8}WybR^!`upuk%aonF*+b+Z^~Ri7K3W?pjaABn=5OQkUID#@DV(>j|=E`0-+fE z(C?&$XWL{4XbvMzn`3IET)uv+y)f5g?1-}%2D$#Rd|vDKZ8q`wZ%v(DUhgQJ1=NO( zxH_9uvT@Yl0$C&ej8W8OTZqyp<4!7h1}BOu3&mPgH4V^Dk)+RTK3|$gO%tHNwuRuH z{e-}BUGdV1r*zN^pyX3cmz$LD+CQ-4X`yO>(2A>7kNE}#awsFYP9t6G21V1_lfCO7 zK?cne-*vT8)JpvDP>1q0Jd#l7+zrovE%=9!f5IeUdAK2muJbJ&;?n{PNBYT|ec$sigU40LZ(K-h{$&A(Ng^Le?iqe95XaMTY={3={ZG(j* z3-~aW3vvglp-5DT1x}g0pw#J%QDyMW;B!tS4viXrQ%7)AB3MGgb}KzE4%?aJ zV#tS>Nu6qKxuAxj^ZY{?3NRj5R*}cll3{yKlv;t^tLRyJF&n7y(aBqwGEqKI3K+c5 z>I+7MFMD_o%b*6Tg)IrgD_WA&6KL(28mckd0Cw=MT)p&+Z$TCAUyLITPtW=GECee2 z5_pZbCayjXJQOO|)$$VTfH_r139^OSpQ|K6_Clnds(tNCu5AUGKkZePqa*G?FT(kV zTeaev6L7pkHj^48NLPn_Q8AS6Zkv>U6}UvKpl=oV6;f_K;Sn#FG2PmhP}WYl52=}c6xYaU1&#xY*5kM>IrX(T}f zpk-x8emwb@uaR3rJ|lAvMjn4O@Wj}*Zczu5G$4x8-NtjK+EosDoLARI$b@@v32NQ9 zT??NWSkT4x8kgx)<l&dO zYk?L+0@H5bF4_i7ePi05Rj@g|tTE5(>{nOMpMG>UT%E_|KfIX~{4=-T{qS5^5%WC% zr0JzL@bs{Indn%SthwbPaZ-FxfYysoa|Sl-&%$r$a}wxGw$mBsJ8CfGfr8Ttut(PB zU22;5h9(C}EqEbS0v606+`+qN>%+fzbk%w&)VU7cln_2V{NgevP}+3rlgo|i?j7(r zm$%%Y?8Lv66CFVe?d{~3diwW+_HLLnDeZcNpTbDYKc%2~lz*lcNaW0tkcXPbXL-LY zOJ6A<2cgJ19t=d;d6!&ufW-N`(-QzJ|9N3&CdO+ZK{`(C{DI0XOJ4(Z#c>EHDEYJ< zx$>a?Zt#LKjqVoojkr`rPFP~}U^a-)S8kUrKCq9_{V4rsyJl@5NU$^+XkR`XcRV5< zE=$13^*0s-OpELYugi@eOmk;sh7X9A)6xaxIR(uQtEZZM&9g`_E$`P0?A9&InRkXP znvjva#!RnlCc@bwJ=!u9qZ>)Krh0-AuH=e8?j}I&o?>hh02RMGyPCgi16~o`7f=G{ z+EOQHG6jgQO{&|fgH`7*!{gcwBiWH(K3*mC3uMp`pUC^a`T;-CtfcAmZNqyN>495@ z(&n~26P&Q=cLz&M>=!?=vqQXcal|{(8B15o%Iq@$R5MvCM{AVk$$LE_s=pLvz6nDy z$wbn~&G-rUmP;4}vo>OK7&v#@pWYms(w_#$48Xom#q5H(ityDqRneDAX>`QXuUL)x z(GxF+Wr)OynBSFwiMZ}e7LT#1xOJ#X*_%OZ#+Iu_wL?^mJT)Y=@ zPM!g=^|hdfBGirc=0s#lQ!$kwHfB-?Hzj~JW&GZ@JL{pMi-7@&)XadJJwS04K>TUg z$l2-x$85>|s)XFKc78^jcw=u=l|}ng*IpM~E9q(m9(dQn1JaYJLRrM+fozDmvl28b z*hui1J)Q}&y@#Af7}`q+7@GmmDre%RB^{B5B4rFZZ0PJ&zWUUR$KIECXx&`cK)Bh9 zMt=}&hb^U=-2!IOR@p6leRMQQ>gYz`v&P2w)KZdfDT>L~6U+&e0QphcNlVMb@OtvY}oww`v!Yl&+V4kHHi)fN($ zdFwMxg|!H?xdr#X5>Qn~c^k-x&|A^8{?Md;zsW1y`(wNhb@{RRR zJ2X+E?T>J)P<4`6{;pB?FxKoKj#wPa_Eb;8G&Z2hCy)yE%TYX4h6b)|txra^M#Fo&;6AyeVP1Rci z!ia$rc4)|emaA=O2Mo}+1`AMxCr6$7BSvUPjQKl3TGXT#)N99wrTA`Nog1mn+%EL>jNx@*HQ{dg{CHgNF?!iq`| z13mtYL&^xGv=7ePbN=kmUpmWmuGbFi?>+H|Yrnx%T*BtuHg1A_mdAs2h8GaQeHf21 z7a3aJ&Aj6tY~5b%)x2%EjPL2{+S_&}@K%iVko02{v%K98Uh5aTJ&2#&$?JV2q~!li zmjam{-Ph`_l?XhARp`!&4+wW`BT+df;~r}_V6hKRdnUG~1cB2s0kPb#D=}#ZWRd;6 zerP#5H|FuZ=*~d0MP9hn2qQqLU;}xDh8@h+De~iG~&J=`w0*pgE<}gY(#jo_B=L> zl?_dOrbR0)C&k9AAYGOlw5mvdky1M6+#9Fa4P9Y$+pTZW=5S=d5zjec7}$ARpoW5zKyn?MJj_R zfD7h&9~vQcTNZ;)+5b&0l?#1{5oB$pFWU5 zT^#;=+z$KrNMHDmk~rv%Y$7D@dl!QNdwGd^WVR}(xJkrbUFORWh7h4_hk|#i!i}?} zX4gTiGWV9hy~_O10P}#Qa}QW~wX?2eEGz!J#m0eTAVYDNWqMbB!lT8-mEO(65%>e=lACe@ zLk4T-n=_sAyHD+KMvu+2iWrPp%kg`icg3}H+^SCxFr>olL@qEQ-`SsVzk}8KX+l%0LPbflK4$yD?MWG*x30tli@6{|wbH zRZu6*3P#m#Sg48X_}fMxIqmjXE=MgZD09n&{ME!Kne}$q0q5H}&m(=s>om0`+Klp^ zQnp^mO0W{CPnr?Rcp^wG!6t@4`=vX?-@ubrtc!qhj~mRU1pu#)Pyr(}@FSh`^O3VM zu-qK5YgYtBuDT?-E%@HW4~?fp$BCQ-&z`n#z8OUH(wf}O-x;Xh1}@(FG@ld&-ql_U z={)KO7Ci70z4{T575?&AI>}QQsxw9*O$!`B)t%2-=rBhWXYxie)y9mISdAO^a=i){)nqgs>X z=#R83W}NoswE2sasxiAmnMyPPAbNXB-ylFTXv#sw_*gfbo&bhI;LWf7RDL9ybDck550ie10p;3qB8%e33kmByJT;E2d8SMZ(@8)npQuxv znfwHax${+gyB_RcQ87;A;--Pnunz=Fsx(NjGc2GDZ#R$AU4shYZxgTSVruX}$;#%S7O&_lhcWL8cP|-c6+a!SsHd9FA1CR+s_CZZxtS^~0g2LE8S2@{I zr^9;7uRa zT$M!zHUl;YuiN38iJI!QS1@L`mrc&Cs*m6-2-kT*?i>FwIT@hwbVFM;HwqIXc{I3( z-Zx0K(p~CBzUlF~nRpETw6)Tt&}%&g;yJ*ZZ1LiVB*`%DaFdoOcP{=LMu43yy#mV9CyT_UYMa)!oSkYn+1= zvcA8^XCS3M{#?vf|IGyJLDX-A$95DCga#V^`;Rr&k0@G!LD4G#wVfGF{KPw;fj2;LNy?zMyTA6PjZkAeZu! zI5Vie)q4%$vHYkpq{&Pa>H@a|xrR?L;jwI#W;{CqmzP?-0Yn&&ek1jfQDYVk>FAe{X7C-hpgR-YnT0-OG}rNzTv1 zO;PokIH2XrlBM(A%p|Mx>>^e%D8yha(!q#;QuCXyiK8@o>l1Zf?aI|eKw6zVs~^{T zH?gt0!R&rNOy09w=#1-~)!fq<5u)TMP!)vGu}aVPKOLCLNI6-(79|$V+5_@}EH^MR z>0A!x-)ig+h&h^*UCpOO7q@{lno?gqp;|gy@Q->{@nz3*&7GU(o#gg+3J58Yi7^)U zoG}t?XN(xIZ9h(860@jqgUIo2S;`ro`{=VTRHb4@@P$Gh!RAjHc+nYe9=|ODJ5>2Z z3mhGg;K32Ion7T%=}CS>xFiOMp@n-s&tiQysFatH(&xhp+z=r$qkyYC<)cuD?Q;;C zo%#2ByE^d1$9gvFty!Qx5|u1h<_A|1=-b6^Is+w}jX|LEd&e&}KpJKamLPiHpcIc| z5ofsgO2`l3EPNF@0*3zB#?zAtMw-i;-(L~5cQ$jaW`|sX5Ee4VIgE)d1AT(G2j~5R zXJx{|- zuj!eetD45yHgF;_|?G(b1n9hj6hp z&FfU=sMM#)l!=xF_$3RuBwQXfT%Ox5SX5M1ZWhQnZ; zm&xsxJpw7{t@p7saUWVdlL!!Cmmigdx0q6@73;jzs5HkNZSkRX1Y+$T`4Bk}pMzao zV3a>e$SU=Gf88Oh*r2H(Wg9ddOsA`@9u_13<%Z63AN{+6z=OW02hFuBnrK;0s}lj; z^NGV*^8z5M=BPa&_2Q)%8wwgjI*xeT`Fd$R!9vx^>F+yk;1}is;WG`+<(m&=9jCmp zP;&y>wCT~fSzfjOjJ4mW_!;yWP?VmTx8LJv=Hq$}EWWjmg0^f(+Gm&ECSk7@ohQU` zNVv&2`fIhkEL;sr!8LO#0_shzUEWeEy|@IyK&gPy$68#90SC8RhK4mgo^vAoAvuL2 zmot4*;x8dd0_HK6+{tHW%`R~iAnFP%yDOGk7u|eZlDH0IXeu?2Rm!aAfTkf99>DR+ zn~mKcWYLAwoGr9T5x(_0RHmbq0`o;BPzEc5txXZN3`6U=@@^fOJ_$$P*(KWa+TOyi<7s3FuaEq`Ei~s z#GPmPvU6%=f`hM#gZDFd*2jy(jnFk9Ie+t?fQ*n3RcZzwu-2?Q`0Nr=(DF#(nj|t2 zG43DPU-qVM{tR=6&x#N-fk}#~*GDVeCBuG1ubfZR`e#u=mg|%P5{xXfxei2QPZdj^ zM_Z!X;djccK9IBYe!5pI@3J~|TM}HGsmF5}$ETHp%wY$=i)+2-aj1nmZFgl}QYEpB zy!qfyAXZdWEPUnE)&W7$u73NP1J905LKhL;$%OFa=!3o|H*;W=C*;hAH_GotctHz- zKnt;31lHV!AxbAHwis%Ol<<|$a$(^j3dofXC6ZkD`H6!e{w!qUuV4m<)~ty)Uc@Y@ zfdcMh<4WfQr$J*t@v`Sen$$M&%|&kw=Vi>f3ceCdR4~Y!L$ipd?u_uOdN_Ub1C(9} zV|e-;Cmg7lyZ$!17~jp|`v|J5cn!y)r_i;kQSpbY^u!td)^NZpS$H9(nDB_f*G5oT z3deQv^8J$Z5(%z^i1&4(2zBh(YKS~$G)eSfqJwgZRzb`)WDS!__sLiQsl|8hySV%tg@p`6fn= zhAivrUu~1+@0t^~tg1$tDdQ?SZ)f(!$lr;~5!Yeb{sT>#6L&S$hdvsPN}V>*43o64 zA@qW*4%S54$E$;e7A4K@iIJ3CdJkr!sGmFvGEJH?WNJ?mvfb7B@{H}CtoP)g=jjw9 z;aQx~Gw@hofmWJ4TJI%}1nA&2tpf2zgFLG2hsHJ#Hu5SWrOf`+;3LH2$AijUc_u;? z44w}YXtLgaH@HAgm7;Z(oAvOrZ(ACi!#34f)v5c%;iuK+D;j>Vq2&L&NAC3a;rg(l zxOY^TF_)IcCDY?u)*CPRhr9}n7)4ir-_IoLoh94!kehnoZR~9p+*KN}cz=6`c@U?S zg4WCqf28o9Zwef7czrw?YQEZ6zk2wSlY2ssSFfV64aP`&B(fIAv*&n9xHv|%;M5O4 zRe_87eiqzmIh_{kJN#2P@{7vLwvS-H3qmm2p?GjazlgLC^jLuk@~_nBQy#4>U+BTbB6il&1_|25fZ6`{@V1fXl2GRG);$_V14T6x z7mow#RXQ+53Dhp-#PJ1|)z5EI-Nqa5Zpj^Io#*-eG0tjZ_Ig8qBZhaEbeCpo&p&+r zooc**!CP;?XmaFpcNYKnIN5^XF^!`~gF!C5PPUZ{RXpesXJGKTTXe#k_3Ei`NcjM* z_c`|wh_chIZmm^H6BS{)eH!FvK>pGW&Q5q5lwhDw4$3RMXCdbxoR9kNHk|R5l=0cd zUKmKWz8?CM3D$_y%a4jpiCTJ8jBzYX>wR+kdLxZ>J$jNKHHo==zlj zmnMX=l(rYVG}J+DIqQ?7jr{cBgPL_=4VwX+>#Q+8)9m1#;*gMU+iP%u6G0m%VUQ!6 z?-5hxr`z>89%qk$cAVn@!;tbwPdhw&R90M$7DQK*}i>a*#krR=sa8lz6=^tw-w}<$l8T%-v7E zwdQXmHzbC`yUgfQHG)7|AXZj$p0LR2xKBwAVvh&_^0&j*7P?i8UYc>3=f zZtL)_S~9_OBaXO9mv*}|n={_;(FOyFZzbVErN)gL5yS~ZhK$& zYTwL%`f^u43$Gylvf>^|>oc6$)v57%AO&sQ;14P<5a3kW&EKxDk2l`-frT!h~97Mlp*g-zg(;LWAT*^m0{fEQ$K8h2ciRsYwIe z-8FB>*wm%*6fy9qxZ+q;D_L&wi(kBr@1J&I{GC2@D~ki|yfb(K*?tKc1iUgo6FBW= zi+gpjU!KhrPE0}xJDw#IGr)%GZ#Psq6V4xPjZEKPyjJ zM{y6LSFYROD>+>v#)0BXXAcy63jBP=?PfsJNX%>TTrAlsFY2!z?n~vm%`XoGSDC4% z+Q8^-iGD*7MlaRDJg};y3fV6h^|=?$h7zL;IjolIq9eDqCQ9wX-e~}OxZdnK1ijX` zTY>_DLFg4lj{>{5Bm=7fg7~R*Khz>J35=innlTqD7Mk~#WgS-3f9C|)wuE%9|MfCc zhPk?zm^wNh`oP_u{d*hTWyBSb$#o{~ zfjvd2t>IbqgU{C7>+G36oOCxr#{_p6zgoB;At-{wmHW|f5=~blV0C&tBx5YTu@@r+ zXdKQ#6Cm@@CK4@K+k8QiQZKLJuY&MFCNWDhsZ09{f(%w);cNo86eqvWkLk*{_@#_~1fb_vnbfdC*V<*zc(u?sR(0c!s`+jN6G zxHK*_qM`ZRq`c96F8%1XnRI z_5p^?rbx2#A=%iJ*GWw>&yKU6I8F1U!54adc)qb?FDi3sd$0f%Cqze%KBZ~{yA)~Z z)f}QaJOd2;0%vPu*Nwd-e~1AL@Fk)rK=hXFgpAiHsC2pMJI&MGPYTAdbEJE}q9*wp zz14MJ{Wys3s-AgJtX(+}cDD9AB*xK(zxk8-#G}Zbv}v{B+^gck*e6#= zjUneCFpf95ev_g01fJ0E{|Rc3n`Dq^45xbXUI`ADHXF;URR(+J;#qNx(R>Yz(Ha}8 zpHySSQukF-K72!>J5WUpdY9ei!p)#4c}v_VP7MRkr@&_X#f$S+$Inp214Io3AkSk) zy^JRh#2F#>hyXqC=`kZlk;m94UXvLBJOD+lfp0;s#8tMl?SK1ICN^NIqtJ+rFylhS z3__m8Wwif!F9okAq+xdn@sbSoX>K}tI`ztaai-BN9MsIeG-c{%QjcFH;!^U5HpK>S zo~X+p;^Fpi7shB?;cj6okYBW+sLBgDM>8Q`;AiBb#Nmg8&~nj?f#cvs0XpPQajdy& zCV1lA1uycg-%gY;DM?hCNB*tpt%r*5=a_Vb#iMe*CVQ0eL>ccpB15#BZ)xZCj3bdd z*OJdAwiH@9XiEl%?9-jY93)c)L+|#DmFt$#-Zb&w!pppkATzsURHA}awU(o+e1f?Y z5O%Bi=BAVIFRqtA+C}a0laqi+k?w2D8%mT1{2>&j7e_78yUYFt`RX!O@>=~ti$$im z4x74c68b+G$4Jbf%PC+P#**73kR^4Id~5%`rgg_p6=N08*JV!Oi^rT>$~tibUI0x) z#%#lxmUHj>;d$Z&sN|pm$idOqEcIcfR-SmnjzSR#Y4d5^yu-Ydo1x83F^8OIf*9&d z%GNjWXnI=44DX}~Hu1E`buK+>7+Sx*OGI&+P?3n$EH`nJdbZNWk<^SOggnLca=bT9_rTYPn+`& zJ`~v+2Q$r|ey<}xaT=;H!sQ8xO0kHtAU)6nY%X{^sjo*$fWA!l60s^5)q^!+dKa1n zg5wl?M{>+LV)O_@#T0zKFH-}-f63KN#VPPWa!3O%@+^ICaNW@p z%}dNx#@UVG`~4uF$hHnk>mY&V9iM2u0o90 zw#Jus?x{DjMBmKfw**+d0d)Hn7b~9e-kLBH-xUJn)Y|if_h4Zt)hzz(Y-sB-?muV( zIsfB_{fKL?@#w>glnJ)QqJ|wYK-z?ypm#?`+?K+#@t~O`-Y36|m4`kFcb_PPiBA(O z9Phon#y?tT(ce!!hxj70NlpYuJhb?Ch)o#@)ggzzlREy5hr{Oew@MWCuPwV z;-L#%gFUq0QX5h_Aodb1-Z#=w>!;9l+;VwomJFGH{nLN_hMyc#ivOD@XRyt-D}Xzj zHHE+ZGMfIB0U@9Bq$AHvhm&Ni>`QCp2^@+AzI4ACL?AQGrUv~>^Vp+l+!Q%d3gx={bR zUNC=%rDv1b?ehMI@LM2rJF*dP#(_Y+gYc-?Z*OcPq4g!wm=&47`p$eW{r~rCb#lm2 zvDEjrmfVkty9>eAnx8^D&Xe{dCu%D-kmbaU`5JtMkfV+mJ^asI0Y9RtP_*_vgl?V$ zAArurG1dHC%0sqjXlr^DRwD_Gk`x#e?fl_y(){1mdPgT7;{9UFcITk(%cz*~%@Oad zBaIpYwN(a`d1Qosz;lQDwe5m}Mi76(=6o?3JEFAj>T3%TWjqwu$2ZzuJv-NGp-}I|G$Fs&R#@-+*Z0LE= zMMMrS_xru1g?MX^s14b0{^z{JQJ_TI4k4#RCs9v?obYKyXFgG$dkyZ1^l;=k zfQirZk}aQ7xPSTQvJ zIj0Fp-v4`q4&@;9bLkSOszE+u-#1!-wFQ_kMK4~ZZjnvkK+bUQ^U0f_x|X9Yh&qwW zL-|x878Yv%ycdVEkU}3$OrN|!fBsEBO1nlizq3F3NA>gWCXoHTy8)MG)83D4%RTIb zp-@5;g=a_?Bw+(lD@m3bw!^jvcrHt~J$1eGtTeT%Wj96AU9W+5RyXw9%Lh|}Xdj6yj!ljjcO$|`x_UtOCGkEl(VNNd`(duNBN;Up{ zk|De(DGNg69$#}oLK|NOfxEB1j>!A~f-s&9Kse7ftN(lF zQwSEscjWf&z%V{McoyI1Txz~*OaK+hO^YAoc#wtFT3iO;Om9iZbCZg^{&SS}kr0W~ z=n#*{(;ukM^G!d_gB<9I!Jl3kcIe2kkVBWB4{JkOGG7(Sl(2|GYGB^NX$=gQE&n_L z(QNRAnqWq%7N640yT85{@0-hu4?|llBEFlj?qWh&+9w`8WEw!EU(4-);oF6<#2+6`%38?3D-PK6K6hKGf3hzjqQb z05?LNXmoQNJaB4-nftmv1^R|4raFTPK_>9>GkuAn?IiXd03KhMWU~vgRc}+K;n(MmP}MM&Ju)GLC))PPo4ZmQDmO z@El@gcQK)q?TST#!0hBg6l# z0^KiQ1ZTC@QzMKFr$0=0fx@@G)WQdCkyJ2{X#-ymO9=Iv2g-#4lSMAGX%=Zo>`cu6 z^AYitrx(Aa3=zwHFJ|Inz4@G4Tmy_?XOrf(= zvjlo*i5Rq=L)&vry7V*`ZlrnyM!+^d2RgJ<39Ma%jwdw#yv3~#QREBhuIs*E5ndco z$@2sqPZ)R<(@o;XpmeZj@Iv@JN3s=GLI02&d<9NsDH0*QKtc}?E>{1VsruYx{qt$g zri8Loi3nerg@_Ry0XApylf6dX4SAnH<5(heq^wek;P5rbX;lfsOs9E@3W^M~@e+NG zKFGY~f8X#CUr5j&ckoWj)0~!$fMAB9jk%CZ%KC1 zRSbHoF;KH8kD+)=5(MKGjhFuWSP1N31SiC;A38<7Rc{b0AvZ!7^A10#V;K(NP>buH_U~0k+>-#gKs<(l4r#eHbGpG} zpDg~Br`7?yg|j(6ceeu*Y(AwkmF`$0p1Ez=XCQX?4A32t&A<|Ke$s!6q( zsr|b8*DKwP2TP}eWfX$tiNM?Qz#nlfkDD%IHrTY5>Y)VYBcoUr(-aVQ{Ib8hM4&xd z>jTwYEI*zTfinkAY- zaXbbH+57zk2?y!`I&t>sB8lK59IsdZ`Ob)^`x-Tp3dxp|OyT4wxsr_*)4^Rj-9O!4V`olS!S%p2{82L#L7^5%rMWeUE2@1%aC8CKD-TYSi|9NC? z3GG8fm%Jjb?EiH5<495;(-Cm^Y&NfFhBIc-p2E~PV$2{^(O{LLdk@vfQ6e0807sR` zwOyMvhLL-sN12#nt78UnsVHLxlrib##O`=s4sAQyM-hI6Q!vH%y>Ogw=G| z%VECQ9@st{n8YE(^4#-1bLP>2i_(MXE|@(zG9~R3A%yAQ=iw^VDug`1lL$UP+HE|a zuMhf6O~2RZ;qkepaOz6vBo6EU*n6+AsIun^^mNlelcVI&Bne6oM35k@fMNhqP$Wo@ zs0fH;5xQFx6$6L@Dp5f}B}tAF1O!1qP;!ziNhC|^TkV9OI``#1-IxE&H=oQ5efpez zYFE`>wQ7}bN2bl_#H)%TsVQevHSsgZNz02ju78|-`$?Hswrty zp6x-7pI927{~14SFrW)>UYa2r^&XA;RZa=8MPe7q`C&&{J8lYR> zX@!v^?9gXoEAzi5DU80G5^uc+X7Pm$YeL?BxOjNCX_9RPqKXTnX5C7}?c-spOtX=C zI_OI)^3SI(+#=fc9Oyv|)+vefSHNv*I0BQ&ug@Sc2B6Se503m84+)whZmwB!PGMq} z{cAiU>I zF`_ZzfG`~Y$B04@&x-GTqJc=~s$N%Xv5#9F3#-Z1Gary)fgETj#BwOuRAPdR5ouJl zyL6pj+CS&J8!Bkl0E1PqXF3=atIakK=Y5m)AIF5>mD({;4KRuamAdw-DS3<@QF7_p zS*KCpF=p@xt3YLtKsR>jpU1AfjKQr;kz*F5V;1;#I^j;Pk1w-*pFh1TBGneT$SlW! zUfwH?P7<{&@qO*f7AA1x<{(_5{ASGZgyUbqlr%0{E;LipG3nkbW_=X8&Vl_aSp6{6 zNGo!q(=6*Ts#bHnYVF;f3jnh^z$|wM&{jC30_15Sx{Z4AN`)#L?8;Qh#r9|9ZGRnh zuiirZ73Hy|^Nk#@=LdIMrI{&H-ILUD123u%vh-)tT9-re6cK7%Fq_B(DhX4t1)%Gb zJiv{s;4D`Nqb+}Q*?2?&Q{w!0fYlR1U2itxh?f?qYHsz&lDB$O0u50iFJJ<)2#Q6g z%(C>-b7-`ki|$DvH137Ei3d)4g<-4kRbgdR@YO1zQ&7xMpfS&PRfeK?vvDQ0c1oEH zsN|XSEC`4;-PGb|T(+PZ8>}R=d`jeGejw{lLg251 zx&}Jw`>d~=wN+3?5W4q!k001a{_U@CWFnbr{>|B4p=wmS97^Y|kf|z<%gWtTq>__0 zEF+T9Z+A&Y$kIsfEF;e!mki!UD=8)_E-oqtKT=B`{az&_byb={RPxB>gsiM50s4}n zSEZ??q|1m~w_cINh>JPAT@E5+maCk^#jZ$FZUD&_Xr}wyfWqFQ~_aOp{;60 zVgiC(W^;>~VUdsa@`&DSsIGeVN|Z~rchQndP(WPGNQ94{H+Latba`&=IyYyjKeN`1wTDjBq?#)W ztn}{FqrE#hxw+Z%2@{iP9NeriOXpd*aB4<^oJ=>DycpTk`iCa$n6_{+UY;EOEW^R* zws41uQ%KE7fP?YjoFxMXSJCiTG!rK$^S*(BQ4>bSQ{SSQI0V&<_}J(pzFE+*(~(98 z-U+d?v+`C9jNGNCKRNw~fsJ3yh?j--$~QL}R=WLt-Mu<=tgKkV?ZLiMZtAns!L%%V zYDU~l)Tbw-Xqg%LD!aa1!Z0(_9PR7qP($6DI77|Et8T=_K)b!IPLPg~I;^)VjftL- zR-n4Q&zY^NU4fQ?Tis|2mX7wx$XSFQeZ22e^J`_rEWt`&5AyaB&N#jHZ2zlJbqM%1)-wzYM( zzl+kkhEOq{uGw-Op%p!Pfk<*U6uXX4aa1hmKSD4E=g8I4Q3z^l1?-KgBU>2nu~0E` z9;|@9u~6Z%D^#6I#kqW*_9@6|L<~V$hXQOu;ALyXO9FAe7mD@KEMTt3kySn;MFfyL}8ud@i zx%btKo}DhRzq3M+5_5_>`$FjHjYlK?i8+De=V@8#blN|)Y130;&h_s0d@dS?(JOz) z92)bXaELj)#jTxQ)DUy@+dh6&Mcp2>`a9-ew>4D?K+L)Ispa|4nDeITvm;w+ll;Fi zC%V@jV$RXe_3vINZI6qtsBem}?uz|i%-P#gE&(y;Ty6cQi)^Jwn`+v+O}P$T=Y>z-0yudlACsBXUbYs}$D`V=PuF^7AbBsGt$#KG(j&wr0O0#94=%sD7A z$2g-gN$A&@!$x@5m`OOLqhk`7@u7wUzw}ScQM*&p@S(o`Lw(6zjsL|Q+~IS6e&>#e z{C|%*XKi;VAgDJ_ZWl z!G9zheNj`fv@s_>7v@_%^9kOy(5FM4bm~||U%H?&pZ>=xr#BA+J=`uDz4_MHQ&q*O z|FniD@wKk)nf4SZUHs#PKHe@wo0lgzy?;~QLbV(*YLs(~ciaecH0XAWaGdV+FC{Og z`v@+(huy}Iuyg>lbwiK6jf8|mp4a3_Xy&``0$7{oMO2|ss#XoMsv(>` z`+{g=+Pk`;=m1 zDdp6hK;obkXUqZ2bZ&m0MdPuG+svE$4-@*saA610SmaUCqeQ>D8|{pNHa1TH;e1a| zPv4V$JMhE!VQknvKy>%ab8EXbOm>zv9=p7gA0GGbC&wMYuDs5S+i+GsJ@f?-MK$>= zz^#9zLj+{b95^CrP%EA>6(woV4=dxZ;7`!u5aS4oyPF2VCAR~3SAj_C6$%0r1)Twf z)Ac8;g5QLz%&?lz>%LC#b$+y@| zVPK4Cu|(0KuazQEfgfg@Vs^K}7!J@|k@aw1F0E*q3iRCAd+?WKU`>%h-I*SFGD(f% zC7a1L=et>{{}{d>tC}E))hj%Vpm4NnmTara^;$kN7SU>6FF~!g8~WvjMCT@-k}BeZ zB&qd-|7Ws#f~cFQz{?pfwCbMY164#670sy(%hH~UsL1cn)4M~va2R6Z;K8OS#Z~)Z zs7LjJknhxOg3G&P)4+|^wF8&TjvW5i{C{5dD1IN2;3SEn=6qmM4yDRok5?ztPK{35 zJX_&K;i_Y|RUh3?2FZc*l~?I#ZwKB)DXy5liyv%EKKtuas?wvr2^xYFcK%30BwdVq z4KHmKF>(DtM%F_Fjpy>|yZ6%4*m$^Y9!RWhpo8+IXTyGBsEZ(e<}&lIJE2Enpc!cfAzN=;HWX;7%5Q3uY}1E#>KOh z483hv%oOp5_P~=3FW=k3#nrX;t;~%A^+x}QqD4>}SqVcaqQBnJ@k8bQ)H`7e^t8Z* z@73JhUz6iq{rvo-UEI|kUqiwM0xe#jxps$VM}b30+Lf~5@WWFrhvjo&DwnaeeBc9KimUx{>Y2sKbc?Ty1nOm@1T99g zjLqK2n5bq@s99SJYQCoNq4A6XuS_>p`Y*Ry`cEsH=bnpr zMY>J+-S;4*9QN76RGiyqA3LYNws{+D3Sp?Vt4Ak~0fnn{m6n!vcin_eA1#-UPjyGh zDA823?ys)_?%+lQaVi0dU#7rUa)Fruu50B$pr|rSwvZM;;jYB>75iiW(XK8I`hF;W zX1X)`dI})jzuP+=SBS~^;0Mb*otoX4#qU~BVW9R^{LGVUJ*TmV2vu8TAapaufUx&_ zh^-FF=RRQzt2`YtPP4y%uH7HCaku@~Rk?Ac{7JnpOMtMhU{)#PoyiflW+=>Zi_dmB zXGj=Y--v}t((FAj?y*>TQ~u4$)3npYkqF_yKq|qP+$^%0OIV7J2-vfb+jsw0b77Sr zY2OJTznRLmj0#8z+zfc&^&vQG=cdU-SMIA`-=RC+P%%;eMm(S5zw0v_3iq~D!iS=# z-eq)D(;o0a#ll^lPJ!buPGr!R*qS%Ip*qR{N}gy#7J{jQJr*_th2jt{-Z@9Q66GY2~9)>`iN#RMR7kqYO6FLV|OE;FfpA!k2@df*|4C?mw zCA4d$t1N6N(F7h+d*tvl>-cLI+dUwvtS)ZnuVkRw>?Fj0+{Y33XzTIZ?E`>wd#lp` zSx)1Na^u|z5yUPb9>_WN&={Dmxv|ii2R%_#GL|%12A8=MCrV#`i!a57t-Lqd`>Wp) ze@J8`XFbK6y5lN7H=g=zXOgl(&%9FD6wqj(KE3k{aH!?bcxBZ4fv93~?L9FU@Jlb^ z7nfF(F>LH5P)y6(=4`+)pv{4tVT z8wW${UN?b2h4<(q$LE1_%GsFZv0%iI`X%(k=Sar+R?;Et=!K@p=Sc)Px2Z|fZyQ<` zkGl?<|7sm!ThV0AJ))!O0Zj_fzL#@Q+FQjI;Oe11lz9eWu_o)HuRQV>#VFz>q2IWf1yWYEWwG z09q-GtS8&w3Djz}+AAPH^Gi4bzr;q`8G?G8^MJPegG&FDH$w_EEOIUCX`FybI><8U z|8@(LR}N4%q{p=;2NgcvjX=$^_~{G;%}n~+-T&pHj_)hKg;Iv`jH@FJS#F>1{tO^g zAOR&UQOaqc#ERUl^nw&n;hJTH+E4 zlO1tmDi)?~bw`!0+w&{h-7rs&O>?2k?!zJn5{iL3?zjTo7tW@|rSb98{oq0vCX$!M ze=`jPeVtt|$b_gvgdunZ14Lj==hHCp33N5Y5JC~dMrj2wW&ha<+Ud6}TAuw5(e-7F z0Uzf4- z5D%5dhS0+$NG2%s`wx(4&qLfdO?3Dw-o{LAHr8~Tz;$}Z3z=VU*LC3xP!M{Bl2`Ox83@d>gFEm(W*)&j#Ls)W6%~T_QIcjxiBi z?!U-N28*`tU#U?^1@_V-s(ZS-N0Ja6wcgAo^nr(^!MMclurS8F8;=(qYqV}{7N`7n zKNpF_tH@2HlT{3#cLItJu~-G)*#Uv^*r4c#9%$n2ac)cBip{yGvQsTV8+9Ga!EmiV z!(I}5vn$FHt9MTDgnZ{oGnxcJbk261`I&P2D`ySp5Eu%)v@#UhJ>R6|2DC8)&jbE% zttQuhbJP&Yv#t>}P~sRpR=soK*yaLv+kEYEP#TXOFLDUCX~ZJc)zwS0Z&|v5&NX^v zOyb1Iy$X4GYC7akL+;eWFGcDgn&IdC>xPOT^Y;egP=}+|n&~Tu?7N<(8^?O=L!@M6 zE>>((%7*#Ddu~c580_KQOr6j8^rODw*WgD<38ePm;Rty8GEw}bk@rIluJp3aOT`c& zjLP(_C#WgnWL;pF+xWEtFQ18!?Af^QE1h8Q`$e8(YilD}aeHxR4~h~eyeMwB*Qb9v zl14o3?xUASUs4mVTtOE@a5&PvA@NtJ_#bK5iKOAm}Fk>Rl0%7--Sq z=}-q5*`-9Rp>VyF<`z&7`Umb;x$M5Zd%=m=t&^u5wX&f_U~A*ba`xuqACiCU%vxRa zW0#}n!tnCw%WK{Jd7~eqlVAEDmqf%nR~*~4PLS1i()<=GQnr$kYH0LcVZ&rlxEElP zLWOIGk!I&VtOEp*8JklbCPTG&?z5Gr&2s^G%KKf%p5cCK~7&f5;IY`g? za2FfS&tKsIK~7+?6{etc%LjhHRh9OmU5yM=qWNmb$BIMc0SNll?G*^Ngga;*yv?J0 z?t$iPIn_xEiI&dcsVk(1adAb{qDA5E)dHoo64qN9VqpUjR5 z=iGT(wEPB}*c3>R~3A|3S_vX`wjD<%)fNwJwYVT(FXcqg2vpUkhSr~#* zJX|jSqD|bHJ$&Ljq`sXTM>XzAv0Qtf`NH&3(EW?4J(J{xWUWr{SrIo7w&N+D7iC(s zcQxoNmRw?eT>>>IX5+R~JACn!7cec>b`dBvP_I zSHA?*liee_vqMKVb&eb-hN$`(?)vRs5E6`{Iy-p6JLL*$&qA~IF4vv{g~$A6ai$W8 z(;9W*rgLmSPfb~kls^vpj z!x>#}ydTp1Gb)>OOHogPo#l*83LWl5>R-Gf8SJ3a@&Ps(6eyHinhQD7$e~LVKOTGsGWKegj z8R9zK9S(z=dngl%T?O$6LF^Pa+Rh-JeEp za90(GZcgfif5}h_`(x$sxJQ^0EbAE8be8#+oXrYa;T(y>k>c#eqCdSIt$w#m)!Fs> z*>@{Phy4ye`{UCBI^{J6r;Iq2kxx2I~srR;LVNip@bZe+^ET$v65u2 zA+#8rn4tQ7FAa=i!bQ)6d5MK`b&$Seu9lHhpo_@b z_rJEiz5#@iJiqx-`{&1aYbAD|%V9Xw&#VIE!Qp=6i3DEN#9H(py9AB(Wy7WSqBvQs zfgR|2)BpG&N*rR8tZeQ9;eb!Nhd7f|*_FudhRH{MbzgADU)aJZOH9gglZ4cvNn`#< zYTQiR1&y{m`HlNy!0wi4^CE|^AIu?Zt6j_g!(|-Dm;0lqy7T2D+fpu9Q}HM{9N9h+ zC7a{Wsp4J&irht%lZu?$83FUnx|Jx=BmM6m`r3J1fsDA{`}u*&ZAHKrjJuuv{*k$;};`^>OSazkp(tvwYYdH2toOx_;Fw zjn2~Lkl9b)l#IKu%gMsimIJZ#bgl=v`{%mp<=8yVgaS3K^v35Ff2>F0k@9Ym`w?_K zhjrBrw%4vqJkf{(;7<9>qRg}IGFpY}P5Zn5hyqpmIJu)2=#V))Vd6#dd0^eVw!Wc@ zbQHKJZh#cZ>%qan+S6nSRqQ1T6zmWUr zX`1a|3XAt5amz~Re?nK`VVqpIVH^T%ln{Gq{4ly4=t9##?z$^6YiS{KbY}DL$BTbF zUY$5=luv;beM0XnFxS|mWAvyg!t-h-*C9~p&a4WztA*-;K-(Xi37=4j84O{Obo1pU zQ*0zjXyP6{0@I{k)=}Xm0=PfFIKOQ2g^Xgp`umfGDPcH;<(U0Hexp|ah`#pJfvw1P zz;%J)OwbX!EH9UTxAs!lmH&L38HQ8R;BZai5q9Lz;luieQ6V8AK=NQm;c+8fnQ#uS zr6N~v=@1F;OJx1y30Z5gsLs1)YC`rc$BD3!I-wte^WbZk^$z|!8@BGdtQoCyTO`9R z|6e{Th4Ksi)>GJ;b5-Mg(nTx3kuA~cKCa`{!E%VyKtF|!2;`d^#)x$+| zS1g*Lzro zDj21l`~lu-it-XQX5-b4u>2<|;S;hjPw&#=st}0ZPdCO zR;8D`l1OA@xyj9KJ!Apw?~V=5X{@uBBbV;V)G=5$#_V3oAZ%>Q5!L2EK(gs9B#^F2 zlbZpOx`H_(Iej9we=hEgYs9TlZ!PZ=oZcEgFBvyUGxHiZ;q|jp5-Su4zlkda6tdCl z2?YAbJC~gM| zpG}56cPK;b4j$tkB5 zo{n3;{{!?yNW<)5%Y}KRpA|-b>pEy~H1E7a#cPk5mr9JQZclRVgY#>^z#HRWiEt zQ}Yl*GqZD`#eONtmG$w4*D{m+P*Y zSB!5;9e*&xbu8z);-4SFtzmU`?~wI3cV7mH=i{=5pR>9?4^Z&eVQSn7(J65gh6vuz zeYb4#udn~%&ha>Py4r20m(Fq?T=G74>>f}6)j!Lmo1uwq8}(GU_nl-AVoBlJ5y<$b zX|iImddQ7)=6IAaSPEzd6}_K&wH+@2K#!!_FVt4yw$M^Ga#n1P*m z>--V{&f%#W8HZTr`TyJk*gNT0$l(;aY)29tFtKl`x^gmrH;o1pXym2y!mwde+(3o=xcc6 zyjLZ&&^j{cjY&DLxlI9w3%wnGAS#5bhfM%yREA*Ns zkgY=guD=6$dTK30FXY(Fi&G+J1pX6OS*5NMOOU6THp(JpkgB)Nt-nt?M2B2df)-TW zxrv)`e*zD%Ba%2~z0Y$l_LTbP-x1R6TCn(NERC{;dD$==GE46Y`JnR6bMn<8m4mAP zS&{OO*jfg>IaP4%F!g}TLP+EZ8x)=?Y>f=99@`%KQZYryWu5!4Gk!=sKlZ-i zy-DsRv&f8*9~@hcq+H$d;> zvJ)b#BY8_*%!b8Fg4-w`=^}$3)&8u__)fB|7jM7zUy$g(AH=uN>5TNI+asW=kq=Q( zkfUw~HKHpY0|yP=9ovLItKGCc{ssBZ3&1PWIbBf?kwt-YjSc9@t2wxYN+YS3rCp;< z@xqVR2p!JicMo2r`uB_e&2e~fxFM~OQh`%rajKv9U#p1T?DHAVb_;f$*mE{EUT8HH3L)*F3J@?8@71u` z&UJ%o&vh}aS3H7av@$Q7`btBr&%16PxP}P`9Cwa?) zN21M^J6l?gQ;=vTA&r$AdZ981rFp54ycWI0=22J9&39fv&8Zfj2er1JpS%%6oN?TY zJGo@KY@}K8T5hNWdT36<03fPR7}jbILSf(sS4XYdGx~A9r{z~trQISGQ6um8vDxI2N`3|eoCqPpQ63>neEzT_i+Pv{6@y^ z*pR2{mqsF&rzTJJ`9y3?ouVmt({B~0!_uU?ycjUkmp-QH@2&nCUsI-YB|MxRw5wma z^cE?fdUZ-bV^=qbllBx<4-U#!jCJ=6=qmBD^N8TZOK(!0NnUr`^A zCeF;;^kv)S%bVvl$Wp6*xyte0rScdx;zCZ2cI`NH&F7|6e(>eL^S~Pqi7GSod@)5Y z-Yc}9FLfA=-g)*6oEfuWfC6o3WE*6k1BJS?rg0-9Bc|XuW8E``N{?k|lzcJUi`>42M25ef9EypWc%YsTF|m= z45=SV>XI0zntuKwdnU8o>aodq`{7ZuIhW<81I}d~w~|Xl#Z!ETdZ8IF=yA*}nuOkx zVO3T^&%n@Y3rHQwFmn$p-LBn7sczEoIj+n_L=gnixuL zgS^2}uMK?MDFs#DTv%p`-OXy9NU|t!XZ^=F`$F~%x`#fILh;b3)zEdi>+d z6Wb>HO8pg1-s{LJ6W&?S(t`Rx)We+0Pj7~8C#Z3<^Re=)pykQf1Effim1op4Z*QS( zLj9RtSib#b)IL~Ww_Js9%J0q9QMJwn_qne>7o$hO6}!Lpdl+G4k0Fs-pY&Gx|J>V9 zJn1zm`JUqc(BXmoxQbg}5AQNSSvgQQF@;elY0$gZc*jbqQEf(V|C_n{-9pm5n9JnV zv8>$iQ)1tks!xx9deKVC_(U+1!M!EG`K!@9A;H={)8OJY^KFIRE>G4h;;8L4Y1Xsu zQ1O2M+fN$mqK|E-Y7RJDKf)z%Y2l8-@Bfi%njK>!RJ}@;nHzHa%Vvf!cE}|*Qx%Q0 z{PtCU-X+D&BJb%lfYdTGlD%C6-drVS1-}WjS|Z(xzsvD{`RkLkW3v*mVrG^D?fWOz zk6qYT&2Knh>qjp|Y-xG679<6y;KrQo#|KGsC-!lwKG^=(dL@WrJcaM22`(ujz3p~Q zAa>b#{lqH^7DRdoCN))TkQFR0|Apqdvo-6_Cx+S)1Ev@iTu4z-?F}!w0mWdlsPI1( zB^OI&vbp4X(n@Y{ljc=_WPtVfyU(}2L)leBO_eK%nl~NVHu~DuDy=~`5#^uRJ4u|Qc|&5V7x}bV=rLB5P!!^9yencGvfb*9 zM))*&Co-?3V!y50`ETEDY7$6&y*1Jg`Zbvx7K_cG>ASbft#H0R*Q(~t@~OAYM>h)- z_Z&JtH+G~`?QEpgSWF~`RAV`yExoCCB3YeI+rtq})}lblce|$$)f{KD&*RQxm4_3y z>s$WxLqy2|7i%ro@9~avrpvhAWKXwnNxM$tbc9m$8)Hru4u0f$|40`O*RT|>@-zQ^ zaI~D?`cm7GmpwVT&n%(rjJ7R{TAbYA>%A zYL%@^s;QJpH76cmQ75m@pT_M4QrR-f58Du}(ti^T84q8ulI$A;3rK$r^da{IW|-RNl~01YhvkDSY|rv+HR=ybD^-eh$^WI+Y?jH>n!gH zSS?uW1ie&mQKCh=r`Wc&=$$-k=UAlIuq*S;?G{w3eov&*@j$bPbxe$8mWnHHc~&38 z@O z^hYuWAaK;E*f3N;f`vkjn0{@wK@*?$%M9pusE8;(Mm_pQL!rH5lR}9q1D_RpeL1c5 zU0dn1wkDPV_s@0I_}GsK&k~^`Eg&L(z^oTebtia~XkTZpSh_Db4REI>4htFjCqNax ziYgx#Fl2T3&>Rq?)m+Wrc1m-M;=a#z&C&fd!K(+$kNU1W+;8*Xng2Ub zAh1v<2T%C&Mi0g_n@&lJWTH{&B<1%Ls>OR^JT;eTLfR%KX*>pRN_K%lNKba&uyaS| z6D8~Q*F5VV7g;1EqVT4B@j{A$b>$LK7VJEm=0T|LkaBhFNc~u~&uL$b#T^UXWxfc8 z^#ne1pg1v^L--+)J9jDgh2! z7vVo+oytea*K=Z?LwMWENLtz}J~rQzg;X@ejhcVtim8J zVaNUSMVqd^IXM{Eb?x9z*OsK4&ZBSxXyja}=|fknOayY&J?6{|iIG zOV0b}7Fv%opjQ?tG%bwtk4c4ktKCTgOr(o+wF6pab7zQm&-N6!54b3mYr29g0r$XJ z7y_}&eXz>AyH(Z(9tvGy#ZP>UuXW-4%e&d$os136BOi!m==|W36M?&-TR=5*kOg;7U zMXINU0@&>rSLQ8g=TC&T4&39v zeB$f(pDXrAWPs^X;j**a+}QdITw&im_1C_-qvB}7{ z^p#jGMmk|*5|Nu~XNNOzNpB6g1IdxL9^KXj2Q-(4hs(W+q{Yd0VeGzZJciWG>(IPO z#4BUR<1%(cl@wGSF+)@EDG+g40mP08k3!xWFv`5$dSsJgw|gYO5O|m2$dUy~UH_D~;{sfPy?@Re*j?GxPj*oEuiapxlaoC#4Hig`0B%m}D2%bbxW&2z(c3 zATei6cjt4+4_vr>nS!n2hA=z1T79LC$fr^Y0c?078`{gDYnM>@)&f018^fEtKlo?H zb)h^Ziq!w?FgFl{^c1=YcPZhTZ`b*8Y=mUhnX4Xic$^BI?~Vj$WObD`gupnh6#wHk zgOKXEPkI37KD{NNUqbBB&~j3S*T;l9ed{eJBEH?}sa1td?z3zT#5hF*sT9Io@)F~F z@Zjg2sbGD-pPXD7>7s}q6l+#4T0V94+fQ%}dlCWfNf+z1o;-QuH~<|n7kt}H9gAK+ ziNuyH_xbOFBe+qC)H4OiTNbEwin)YoV|E&}iZwXDO8=g<91(+LI=9Pli|jhzxH-!1 zy?dB~9pRvCZQcH1pOQ}()n#z(-i^D*3LX*skn!TIpw*oDgaG(b2kgpZu)}T6?Z(Ad zEdqbo=UG>v`9T1OK`6x~G51*l%`2OtREOuf!#^LO6SMwMuEFdj<1~`!D2X`5wnLoR zS9Zf0f9NN0OM1!C>}5>lO1+>yozps1$snI;WU#w>Zf?%v`Kr+C+=&3+V!sL(++E5A8Hi3`S=oWaN2QB&N;&C}O`}DCnV2O7e9@C6N!7Rl4ydHRt`4Gz zs4YCWBU@DH?Zsk~snXzUFUAtf0)gb;4s6{o7VBF+9i3dK$U`}-ZQwQAO+N-=hiOQm zVlEB}HlFxX=Rd!EcG_>GZ1?wLmMAC>#6gv=YkG%%Dp9Za8kC7hoNQQs0ZDZ$j!nRk z)~Ja9(R9K2fE79J$^MZOmUWG8C7R^0Ov}V)7iDqrHNXlTlqLqg zcm*&y@3Z`IuF{Hw+(?y>l2V&CLVrIdzJflR@vlUR23Zq2L z&=J)41(s)!5Oiwe+utitBln=bs;s{bK5AzI<2*WT{j=0x`99`d#{y zUGfa}{1~>TN!zn`Ly>y-&<|^(qrllp#RhLGjf1Ki$_DC@7KDvOSLs%e{FnkdRyItM z=z<(M21{fOOR`LqgX_g$*1eQc)uhPajLr26(NaA44(TbHYo`fw6!Kgc9lD6?+|n!1 zla~uwg4~L)A>1n;SkAlOTAh*p>V#3K4Baou)NgY&`3bj{OVBdC6YO7@a_8rIvmDqO zXpwt*6GIRoNdW)I$p1h(V-?y_*Xg#%@~F7_T-`%#&;V_Ngb+Iz8t$)}7P;9hqhTL6MMoenWS zNEPMw5KXiCt$s~5{^#!BM#`i{^jGTV$pxTdb9tS@%c@3U>lyHOKE4U6i~e$G@~d_+ zJiIEMZ-%7qLi?UhNKc-{!oju;X2{X=Y`C{+sMObBn?3|4f36c5aNK}m0{;}>tZSym zz=>!r0;zj9u7P-}P6V${S*M5doG!4#oZ^1)#RI#h1dbpZ_*E|To@ph&z5yj8ae<$qnlg{ZK38FI6!)9!di<$2e!R}Dzx`xT72$dxxxPN1t1d5gA;DYws;J z5#I1%Q68-5i)#xBi>!K@C^x0+XQL+7CNV^484IR2zVnm2pvO|E#;y5gvm$P6#daTY@_)Qu*9ebtrPin}wixCy8!VgY#{17(g`I$>y)x!suJzWLK zo{kSkqzkN0IIL@@ikec+U$v|xJKqa|>#y?;}m z&ayvTJe_Agy*}T>;LYgUYY9Oa8dWabH?z6xe81{S5#;q>MOkkmnczJY8fak#v-Z>rkW2CLf%USZ_nH5Jw!4#ix9HUUa?bHDb>NbEi&>z0zH?RU+1jp89L)??9qL;>$!z+uwJ_3cKI#D4ED9P z{&*jE^e(r)sgi zuVMyGYrW;i;ynb_`840uZ$~jsH_RQebnQInBpEtfp****{HEm6>o`qCgOMQReDsk$>~LA$g8I;HrKw1B*F11} zW&oDP;s$6Rd~CbXGAK!86Cwy-HK)2gd*MgTqvG^##U&U3;z{Xsn+Acle2Shv^nF#d zk=(r*?ZFV=iNmX5ji4p+1+9JFu@(bG7U<}73QIvKO+E~aWb+=OR%qGrtk_Uk7n`~v z#&wM?P-my~544zugy5rQ2VeJP7+~&hp8w{@X!O$hJV9leCt-*5P|He1J^|kcGB_;NKYC* z_TWLCo|M2qz@_pQcwPQhP zv3}|@oEGba)Z4@_Ag}3edV?iw_->*r_=;Rge<;5QFPO)xLbdH~xEOfz?V^UZvjY`c(%ZK)Dzm@L<7-$%`EbQ@7;izF9>vdWLNkQt z%qID}aDO(%EkEAnVz$?ZJR2Q~653;Xh=^Z-?BK1VfD0Wxjk;YeE1>7a6sT8IoITSV zpR<8K%UWxXHj8^H@ycc^96*u|_rcR7VJ?Ry%bhOjfKf1Qa>Y}MV>21b@z-4^ud@lA zZ8zk>>=Ol3XYYtR8Ekg-y3y5DXfai7!s3k_~+Td0J9|^mb!(pealvmz`=-Ai7sbgrDLj%wK1KakZwX2Vg!X+`7Q)pI{T?o9&O0j`nC7{4^u7O-A2WGXnGbe7o znU_3!4&TUnBO0Oc?a{>l_$tv{F^-E z1TITY1NC8*qR$x2Qq~7UkoVU&xh)>_B!{wF`N%xHgHg!Ie%+p;1gT8Q)SP^;BLEd{ zlw6Vt#CEalD@moBtNfHS{R$?Mx*pJHoHI5PgL5tl2?2ACBeKl-z&Wk3_5;$er8x=Y`G zfHZO@Fq-S)0+x8!-f3-~h5pSN{)9QzAJpK!IpC5H%Z;{UaE80le7CM73 z^(z(aH|2tQVi(_GGkSQdKC2tr4Ssakx$7v)7Y4)DSn)70 z0kpcr&0jG4@ae%LIP#ZvQXWxKmS{#QG54Z`z8$2~j)<`}qFO zItGX}QYfuSLIw30ZT6;B)}8=^3rgFC19If0#tH`58q4Jp@8FcMI{1NR(|6kaCdu^I z_#DECDjh5Vkyp=`z@z-sBqrKq%Gd82Dza|z$#>(em84n(BAnh~Gs$)%RzwHY6{ z`n4_BIUl#HJbMXP^u4=WM0eqz+00Q~wI})>9kMydbLo~8B>_5zRH>OVGp_;E+$Jr(gPhvFxv@0; zjP@>AU-C3C2JL#bg>LNMe1mJ!lp{lJm)3qth5kg_$IZ}lpH%lKoVjcAImSKXA;01| zamL!&jML`my|Uc55}~`~`2okPSz-GHaxl0 z@X_Ft>x5}byAk2rv#C>gjK-JBRPY{a6DHrh&1v5AQ`r4-j`Csl2|QWg7LFfEmKb62bHEiD^08?Z1jY~LK0HWJFU z))ebcraq02*!b|#a?L+qYqWXUUTYaDuI%3z!d_b?8wi3TA=U{F3z;4V?ho8^{P1|h z6oxWf8FQHN0^UUs`Nw{eZ*%#^b5J!Wu^xNN?SaRs9@k_RsBK9&dTdtaB3eyrk#J$D zQyW^9W|pcr2)WQEsJl#-&ETaRyh#OgI-*Ebuikq&HHPb#vpj?@#~ka?VL5))3km#q zL6C=Z2xADN#LG-?f4C!O^?!0(G$rUblh|MSotM#JqTq{v4UE1 z3bUae`$!s8!xFeq6deX|zI-_G(%fq^HxCSah39GTi60qJW~#!_VRC!FX#nY)FtK+) zYm65SRm5BoI9b2>@xiS_I(Z@gy3lH(?5WK1jd+tW!nX7!LpjL6g`gYx>(|3(Xi};; zPE1c}L#0Qd=fk>la|Y=?%|g;FmbhGdURiaSMev!2JIL3Q)B|61fAfcoD$n7=IrGv> z`m?75dDgBq&bBj_Ijnk3y$LYOn)|wYYJQ%Ipjn@`=R;lo3773_tIfM6$*1F3F6rT= z*UG-_uDgYgL$TAE<%B7OxxpdU$9-Q{p36|xmFM~r%%6NJiSjHq1BP8Cq2a_Cw^gk& zNZNy9(U)ktsrQ_7EBWq_+2a^x?!aMxS_)hiktj(-1`f^wi%#cIIZFco-qTMdSt#Wj zsXCt8YgbWmw=E-|j0?ZK=a6n+Rj+FmyzxF_n@e@bmXn5=El@h2Wc&mHtq@gmcd>5l z3)9iGWXTgT#Ie!%`px6*^nM1l(RiUP4BT=TttL;&Dj9A^-@E}zhM5qc%wRL8K{HoH z+mq>D##B(J;P$Pkw(?&%as|%`ZS~_Z>lG|-+@RYX`12dtfbpuSN){UERKx?(kQ4;D zjzB|@&(Bc2lc(i&A7B#at$w_Zb+t}vs^kk(Y79*@V4no4k~Exnyr4S$#8DKjD1;k1WsLSrYrm zogD$In?z@Jnqel~P?>&o+YDbT(Ct7zt&6%1Bl-q8g)w}$d+v1w`rtGL6+k5_K z#)ib1+=d9Blw%nldn*&UsnvXD*bD5JGohuFx(_XAaU11FKVG0sh%Pj{$AM`8OTAT| z9Ct~rLvhwk4x3VTvVs;oGPGC<89d}<{W}RON1^*N`80ep1E!tHlGqT>khMla-~isz zI0!#?d-mMU=Pg<%@Vm?I#OgRwkAP7}c<{ypJfXCrd^j~_%`9lLNt3B$$HKNd>du6O z!1nX0;j5O}SzR~rqC^Zz_Am_jG0%)ixMOaX5X<RCxg?6|Ggh&+0QVEsp*<(_&hEgH2i|k}K%tP_x)+_kVOWif+iY zE%}9}&i;DW=aFoW;r>23F(o?vd9k|N+SB(Up8_9x00?=b$vKb04 z55amw#Q)vMsI=s-v1^+?^`RdvZy9teqGYjq+S5;baXk+sSFy@}avy{%36lbsJthEA(f74V?T z(g-wuvJC*yc-MhFak%E+SX!EI=0nF`@d+S3>gUW^H>bW9bMI5s(ed1JPh1+q*TqiO zYmAUQ1PhOznnEC@X=Vl(P(UKPR~WX3tpI+pD)z8 zmE50z(Q{)JkR^Drst-)gI0HT(c@AnC*Cq=1AE||>vZ*22gzowHw(wTbvQ}k;mPrSS zHs8N-7vqFIMsm3*CavkdAiZ*z5EgG{05%R(km>JRzh6X?i$nZnSXI35x|Jf1|b0dwRC7LZ^p7!1IlhK*wlEXX6*IVOeA*bKXpKb#bEq}Y^IAJQhP5BoxoBV zmcn?BdKmeRs<_~wRDVv_P3glrrV4l4*idY$KJM{+YM=Q0NBs=BPN7?qyPiMB7SKQ~ z?o6D>hv@2!mXEoJE0icEDRIslx!5aYoC(L9qlVM^9`C_r8C?5AF{Fv@+Iob+$q>x< z%Rc&N(!g}|Hq-p1z>8{Rex~o^^@X4^1}V|vu(FmHPk&Vi$f3P|U8!)o|0|Tdhg$=%dTnz)N-dF`ia6&{UyalT+oohEgq)9J;lxYys)%5+1433Dz> zRk+{T4fdZoyJMRhXNWtOu2sIelg@vg6=}uqINveT*M)*@X&11^))`Ia36zABc`^oR z{w?#q>BYVls0RkK$t1H96XM95ii#9O*!`q@EJnu&&Lb?Kf}3I*PM|6KDYBa}LW;_~ z)ivP0af!$b7jm?3y)lX5R7f8)Y89irJ1?+!0*abu#?g~JdeXwr&ss?fvs(o% zy#4W1TW@#0IF$E_%{032;zOz<&!Y>d(Ph#9%T+(E+gMs@UGl6FzO_pU7VDzG@*cpky9<^GH!nXs_0XoyH;O!2ZN*;Hv#f|DuTN=J1m#^9S z%@2)hG%Taz`d5T!l6@bhF&`QK!4TJbKB*C z<{Ak<&@ z6$$Q;3k}9kT5&k9kaUBE$F6TJ&qeSa`PmOr`8nGTVsi{)#hdnp7C;O*gu}VZ=g-3h z!bdQ0`#rMqO%L9i+eGhPuJU0WdR|N?tb3Z+y+ILn{q8h(0N;PcYrA^1a^wz(p0Zlr zj8UN}@3hRZTPgSfGHh19ME#!X@=sXU+ClBWtLU0$DcA#v05{YR3Pi}3y9{gph0th{?%Lyx3B1r;4m2wh(~0=sNfRz4oc z#O6p+zrBL*2xRZQlVXTsLmC(ugpU$JOQCN237R}}wxq72yo+MO1Vqx;doRqf`m$hc z2AQu6R;*hpdOK}U@1BtzJo5cB&pzzwsDiB+KO6Vgr764*AZMK{jUL!PvVPDf3>ZUT znFRJ}?}t}-!(Dc23>jy>zwQgOx`Oq4{YkN{@$0(Pvqb>i9YVK0L>!ftD*AJJQD&AV z%m}LR+wI}NAp$_mfwT#6K6oi;q#Vc?V^W8Ze+%N4BZyGfXd+5k<`a~LDW@tU{O%u> zp;ONnS=%G<^62{A#`-#TdE-(muqttaL9b!+BJzuIXVwL8$w$?4&fi4AQo*Lq&0=p| zBgjJYoIp|#`WY61L8S#vb4W%J?a(B~9?I-QaL=Vq7d2^kDL9MPn9(cF8-vo1?1QGnRpoDf!+#!?#1TfNpn%hw)=AIXP0k^)myi*j; z1m#N7$?g}`&Km3{lDDj}e;Rq^$f<&9^AX>xYWJngN_w(Fc5ZMUo_$j_>=pLx+h*sE zNFBL=7Y#)63AMeEB>-j?_;MD|B4^WVft6kk^y(Ty*v>FdQo2+&>6vg2=(^OYXr7Gk z)B%&Xz<%G!Qtt+Z&e#0w6*N=@Oxg$xhjh9&ZU8NRyyw*KAC?*}n?rx!+CIdwOlS;P zT^jq@whXK6{Yn}_qtte0vKJ`2ZU|H^6jWiEGjGOn%)ko=L7oSUTV~WLyF4IPCD-ZB z_>^A@kir&TrNyB?{y4=gVHRBQg8zMPw%bs38!(0EcT#uppRf;D(XzU56YNFXFo2+^ z0Of>7!FdvXp$v^TC$8rBiN5e0x&F9{zh|kxREVq7sFKoRtF5*Qxl1&D{mR?95j~hW z$sATFJj(PQ+C{XCX5K)WjJ+$U^SgL*Kv(j~@VNQlTUzm=-Gs$$HY6w$+$f%c!*&fz zEQebe;3i#yI;wO$-xHJjh!77az_P!panB7fmz^iPLykWYiF5H^5QcL5KNoL+#%p7D zg#6x(;_7ousRxYFin5+w!8mXttm zPq_G7#4m#!`GZY_&*y$Hu2=${^u9ANV*M=fc3Ul9BvRj0wz*DomcuZ0=ufcbdQ}FDIzfRlZuo_6}h`E9C z{5=51E6QQ!OGGdWsqQztJjd+T3LNEDFv@Jz;vszB--H$ElnW7u8XZx0m{gv-c)NG%6u79GI7GC@S!38(x0&np}NzVqU4SW@H~W z+|)VAR7wICBX^wy;qsHBvVW~MuTRtwPEqg^XMCM zHUTJD>T_pT-(gY=15P6FSfV0f%1<_13rCNVWroq$8gYw!ABa*66B_h0+)~;uBn5{2 zgg|jhOlT_vU(cSx9azR{sE8F8!30FUo;yT$bh6+_3yPjyM{5zh_5iLN?Z_ zcV2N!u%g~=f|>etSO^0sxVQynW!SopN@~LoXOkSWbVShE(t6A zjqjty_o09Yma|abK^(gP*L9pW^!fR=`w&6{Tgrl ztPs<6c#>(-R;F;RwcCT^80-9XO7#MXxHmM_p>+(~iiV!xtnm>R%p?x^tJM*2ee3i0 z4e-Ed0ovoem2BbZFq&_HKz<)`n-cLO!_Spb>3ks@=DDZ!E}R4LE;97j1zZgz09qM? zPj#)Uk1zfh{+m0G%PEaeRi=`XLbyF(>=%0$~hED;Rd0P~{ce!Ir4=3sE;D$bzk;o6`2G7cnm z+#huP`|6NAnrWuvE2f5eVcThQu-F)HL8%TXFi@fk;W`v@SdG>7 zHSW#4Cu+h@{yK1g1ySc@u&use*VZ_3Z}1!o9dai=a7ZB*tYj96VM69wrtyZ{939mh%p}+uYgo)s7xIr^8QQ@W7EXECh-G+`>_zF-k^Gi4kOC!r{m4OQtO&4i*hWVnhxOF!IK0-T`n(u3yGrVTlX z8vCL%*|nOsM3bQl3bBa05|J3=fQKX$iUW?5F{{C0s<6XHM-#|UQgpo8?K>5bPIkz` z9@=v5o@W&rejBv3cjw*CtP91$_{X+Lc^z~tI4m2I`(7YBhnsgtHe_NCickx>N#(GV zAhUqciS-$i?ACJQ4kYk%Z-9p96k4=6V~Y{Rje`S>jB!AMp`@q_DRiT}s=AfT_$0Y+ zFyx^FsI)f*M~0UwH!N$sgrh=NYwsR{ugJ>Q^Y*-4TStVX(MV{hE7sLL!e_q!J&+Lv z3s0SjyW58-c9Q&{Zpar!A$_~8#9*+BZmZoF;l0-U{9C~TL6fi&%7~x3EqQI2OM2HQ z?OE*5UfkWg^H9f@?k?`WrO@C(0y-T}F7p`$7APX@ity;W=O7W_I2QYuJ!U`w1umkf z*^u!sM<;fxN5fdNB5$EtadhwSy#Ie@4f>z~S5ov1*UPNeMK%5+i`mzApi}c%-2eI8 zC>$Rks3V$+GokPm%(d`+r_=yQV?V}_HY(UPJzZ5sB9V;HCh_F6zK%K3mrwkudh0w! zXY5EIVJV*<;mL@<%@lS;$k=K^<1*uNbr%DZts@DPb)*L~`z*AJZzP6@2R~>OSwo7( z<1B%C@zN0q!HUV3f$RE<7lUukJnH%Zn$2&`RWI+s$KU^W;NOq057#W=ok0c;KECWQ zOjTUyw^|;P#kCk$uFI0noH_H$6-H_y#e=iXaMP-Hqg$U8!qm{4ZMhGYM(0zj&~%mn zd6Z_5#<$hKFvC@;X<{*08kLfgk~Ti%^d!8W*RR+(q>_?@O8l0rrRWDjbT5$mF*A9u zcorTtct-~>-l}rEjb*p|aKX5QMkNq5Gu>EiYccOtB-|rg!ZAi7>56b_SU}mZ3}wE_~7-|el-~qpF+RA^Yjn! zS{SMIc@5l}k?=Qri?_-zoHU^NpYL6y9=^|9QtW%Ogc~wDS=0>AhyOv_>(KRobT^% zaz0tnpwz47?}t}6Sng*4}UX)a&WXC63)HPF+#W`ag^*v_larOUCNvX<~v@jQzqYu$DBJ?S|nE2 zq=NR=`xZjUo70Mp?a}UoVl*=5-(rDsf^7vZsuYZcTc9Dc2frhh#catp+R+m7Ij(;v9DmO?0DJ&Du+h9y1pe3NFB@#o{C~JAGn7PS^ zo}?nR<_KcoQQ50=JD zWxb9|^z%oLQ4rq*F;E8r%1M7Je$wOlYXv z*IA6plI)rI6l}jJnrQ6(A^&W>|9t2Wghs}QqyLCI1Xz0t8!NN3PWn8)p>ooVGc7xt z{{ld4$56HTl{b1n)q4MN;1AidD~an;)FIewPV7E83VEmZm(j$nJB0YqQ^y~!gnm6O zLi35-aOFnev!7x@q0mxpfX;CMTxTpf-&%)s<8E{X%Xp z?69h2P(51X$()4gDYe&wCGT%74KG_*w7*k0{B80vic*cv=sZRMsGhVkl6TH!cZ`MH zVW{Tg$tXwGdu_GT!0-td{lfMu`2jvOE*wMNtkH*8eS9vIOI#fuh)cSNo@6qrgkN=jLa2{lSX0vd;gPXRg5nq;?kBAj z2gh+NgaXs}M7eIMs!;tb`|Hg!pV<}$Hz}4OnCVsNO&wv4Ak%k4+mvL7#C$x5@4My3 zseHZb3TJ!~v}Kx4GAmDnW+qNp7dF6R!sx#rd)S!P88!#2$=aeCH{h@){}B2{3yTJD z!u4=wHr9(r>@QobdvLeEy*lvmW^b|C=VxjLawO=TH)fuPR*sPOSwPox&z$U3NBBy4 zrj8a~H;)f?7`$asPa9?I;Zv)%1kRp4$H=XfkisC3Y;}>eaCtBS>IhI5MCq)0;04UJ zAcmefdDmGhM;3sUx$ND0D(+R5?lmWlpL9V}CACiyOTP=}AX}U6Ff2@KH1=5s{&7^o zzMpEWHQlD5U=)9tWCF{?ZM&sC4nh?oH=6u0R#D0EeIq{%HURrz@x! zHg!jc2`Kz{T@1Ze&MG?Bd&KjD=WhGu#rWWjr>k$f7j0tRq0>Oy(Y-$%8Z!G#XHM=K ze_5T)%YU8+qNVi^P@Di+Qk5clWH8JZT_2(qH&}LRxoE1t-A~!=5WJh^Kut{z#>ui< z{7e!fQ|xd*p3J%*%3wfOEK)q}Dm_AXEq?&wU}nW~E6i^|(K#v?3j+!JmmD7tzD`kNK_7zI`u+d@!_E9kl6w37ojtu>}RJd>$ry7xWiGz`pr*l`0kM z(P8zcYYte*&G?)|hbN}r1fegzsZ7y)&}wWMxeSyEe+rGX0;`7=pl69wd_3npLW8=X ztP5M=7YO}lT}6@(%7QIt!A8XJ$;?BVC!Sg!RgO9Fy|40TNtLrNu1!ccQyLt1zbN=G z^%<0+mj?)ba=W;Na>#VeNAE|UO?&wX6l0+mz~bZ8TmsFYY=}O#^&&LLeHI@m_QO9w z+f%t4H=~15Rr~c89OzjV8p*qjK-_GpO-^@=DWDRi7;@s9=w?D-ab6Y<_p6i05&Sal z0xVj-rLz;WbB@L+Bt&Aa=V?UT=Mp!|(*=sBbv{4i!kDJxozVo)W(y)rx8+Bp2ModZ z;0Ndaa@Fc>KVDAt}7t(y`5kMuQy z5J><8QPHkR2lO{Ze}_yy240XyS13GJ-J~$RjxxVHe&F1n6jYc47LU-^)i7qjn8rfr zqh2+xAcKsg_yXT)rHa@61;(LyI76@vE`u`v1-N8sgr`TU6^b#YDyRP=hh;%>XM*z8 z{iuc3Dv)#A@K*?H%XHkAcNW6dFagrzD5s;g( zF;ee%H$mqigmQ>HmhQj+m(cZ(*eqR`EV^Q%`Lsx;gT~GmmI%C-8)oNn)HyC`1$`4@ z&h_lzF0gJMNuy!-)8BD|(PEOKg#$wxft;XDnD-1NQ@r-gAf1|Dy2MSmsC-Ef51Ehx znke4*>M&UABO!?AP+om7e*{ug=AQ1GTWN%A`y6UpdKx>$c(gL>gvR78O)2srE-=z$ z8G!Lzn%hW2W|(%dnB&J=DrHd*JBz`X&CAq2!fD5T{iQhkh&ga!)BDTxfh8ut^H_ z4T1c1y`t0TJrew4<{s`UxDSc02;zMbmKk$idmXGDk>Dddv^!>2lC*L$_4MioR{?ls zl4_^aZe6Cjk0Zc~tP0+2^gj5%$epJ6Q%02)9+ID5&y0Z?US|xH-TNs?Q z#jRzdL1^csXiFp@|3NO>dC<9EFx`wb?Nj5lR(IFPz1jQ4gM#{35&tuj$0{IyJ)o>+ z5d7{p_v1VDUUMr;KIg}aSqH~ep>Kd;4TB(Fxu8t}Nx3{dbf!p@o={FY8M@KBoKPTG zGG3uF-wc(vzPRV!I5=eu3{mR}XjxO^))H52Et!aZiQmjypHu3O<~L+Q{dim6 zrTH!Jf-?FDYI1tIF7_cvn`-?TL$2bUu(ffZRv;$CM60~e%F#l5%U$ScgvrRDFT6YK z%E&9unQ8qVpNPw&FGRAoT(ApdP0m>E39^30)?s*W;FWg7leE%8>98`=FMZsB31Nx6 zWG-`0^=nWrN-=)`V~u+?^Mbj~Ns?%-|9flem{tr0H;Tv!RciJum+tOYmvS-=$i7^h zd2`<){)|E8pf-z|D_|9oKw6Ze`Eipi$Mv5>p??xd_x)$oB^5N7NcK^`F|oD1@SD!* zN)H&~RWbB5-9b}#s@i$QXFyn>P>Qk2C}hn(NDj6siiN!gZUoOg4b&^~(83H7^r?WW zZodH%l61*u8#qJS|2zYeLD;-Ru@=fmR|oz`Avl)Uab`T&mJqdt?KR=QxUQMojh!4v zPEML94eRC`Ao}6qfkAg4LaOE_gAAvPyi>ennW6Y5f=BIBXr2**n_T-6jQ<)-fP~De z)OeQasK&ofx6xan;G;0*XPVT~87G!-qIXpad zLxcR+1f0w6Kef)#Do}^AiE`qZ4V2LA2X?(&K+NjMGu{ z+!JLw>GzC(h3mF3tyDNyBtflWe9p$8OBPi-@C_`S8kX;L4UL&dYqe*(3M3gq z$@*vhvIPeUNr#~5FJUzX6IY(Va_Wjk=sx-Sk{a>#$)dMor|u^iQ|mmy77(yG^q)T6 z^c!by@pWfwd09njd#6;hu)9cB8{yyV@ADo1c<;dA&H0aphg)tQ&aW9fJ;akIbR_VA zKA=_S3Iyr4rZYbv=}Zj=95;rSRG&mY58lW!|64R52eGg0wEMetMe~N+)OUn=j$cdF zAz-jb!BIw(F)D`y)6`VRWm!NDel%x+?$Q2nca2JzVH`>Ns}ZVw@K{>0q_m(@cojB` zD1_%E5LrMoO@{&|-=pN@051^TwS9~=m!FD5SBNfZR!lyA{MKuC!B4cjZJj;&8S!2f z8&VLDjv>@w@T|s`mJ*=(yUX2KXt_f;AcZhRd~6m}Ka56~RD(INrrW-UX`G+fIkM!nlZN~EyEjAkrws>y zU4PssxfxM@lJY0y=VjnVt+y`$Uy+LEWNwcQk*1xd(1-W z$HDQtubMJKpO9b(rtP+Wg4di;=Cu&&pWh<_i!rZ%^AMI@m;lyj=%>UGKqZ0rNChhb z8l+NFQ)h`$Vvi*wA6?vLL>dgK+l$uO^P>7m-C0&g$QvFbDc`=C8f||xkJch7*qvY8 z)l4`ZeWiX&d*N^5XN^d~ga5u094bT)F=uu0`yp|ptF8Q`Vl|319lBo!bzH1ehN5k! z_+A*o9)MrcDmC+k{sE^2H#g5}`rf>yuN!Fy8f`{%AQ z-w^os8w|1%*bbW`fzc)iMb*i!q#G`87jXUG5T2cFZ9k;$c*>~y&T~mByEQ%j{xbp| z(;p$>B8>VVNH{l3QF+x&CdWPhNwOb1@i*dASN-qFkq^E<-t!P7ZsV3}=YqbnDty;4t-s?6ZuXA%($?z9TCw(e`Rixfw)I!GSk#=5_H?UK?>_Iv9Y(P;kf_lCted^aP{Z;}c$=6)syE^Snl& zpk+!18>u;`mcgn%+3)<()NrIJ$1n_uC9v5JlufmtTXY8wAWJHO%9g!DxK#jP8Jh>x z48L7CyUk`t<#dQ17-Qt9(!DYb83Bak3LtlrHstke;S(p)^RCJ~?I%BlO*QS1lzfO9 zr0xgg{jFu`{|;d=EAiy^SoE36#kx=&^m-Gu1DsE|8Yeujg9k}F$Ef8_DCx5nI{Y5I z7)^fUeBJZ5o+r8VHzcwcU zntnh<%Y1>(7TIy19(xdBv-|V1SfUw0iI#bEB-2;|?&OE2eEf*+ZBxinciqp($e7w4X}p{vGw$(!H&zE1h^F-V0W(jdUp#;Q;Snvn zGU4Vd)m+!wn*7x4wsdl4<~f({nP{QxWmZ%LVwqKLAaw>yeD>h}eStn3GWoQ*xfo}} z%&5woyHl^Fg+;}{I4tA%^nhHB9jE^9C%G?#IN=B+WdrpS0;#DSvS3rt%71U!u!1!u zyut9X{OLL*ZuDx>4j((;?$c~|3Ki!7?Q_T+@s_gJoE36LtYjdw?4+o?x#;3&tVC?x@ZO$!v@+V_=fSAL zE{3Y;PGA=so|Zskv3d3NXJ15Zzmhz(u1_rULMFZ_(szoV(Z{#1U%Te4QPTE6WUiNNrr5KhF2@yYUB}#5dp&FWo$B_a$J!rs zB**{%S%`;ppDeau;FAlmlx3%GrzC1KoV7MMcl$_cZP>HvSto5UeH4Nc49#|K*$^ zzmY^ous1hFeWa6QzDc2()U16yBg*h8R+xX24`qt*PrRP1%#6maH&#^Wyddq~b;%>& zO!D~l9IHDVN)8Ty6*nNa78)Y)H1o3k?@fhhBjy&NT$`lTsJ^V|+OIE+Kg<|~!cl@B zj9fcl?cfYOmiFQ8Z~uM#aV}P(Ln8a}<=w|8edtAYxo&>6$7(@9c5j}VAGrQI} zF*)(ygpXj2VMi7=0@tk9fB<9dn|lfD)yTyN39treUVU2}e2R@NjXn?&@?VF9Ei93z=n)GxER*wF6V1M8UKK?K({mF}P zM{Z>Z`=X0#ejlnC4No*yjOW!-|09D|w)c;>JEEkB&Y6-F7r$UJTztNRhbMAJ48#5T z^X^o%M!gY0yRW_ec+tmF8LAuncZ80jOZXc1HdI!7v(=yVF3Tc3&K{pFr03>_(gYFq z)K0Qp3r)I*c;VN0D{Zg$L4cQ6+3meKa_)tOo^FXB0?4sv@7`JQ1Gie;FK#$?lr*6}>s|A7jmG36gj6Cz(L)1HOn;(`r6(&gZGYk$e~tBnOiwzie!53)bmVuj@&+0}*ltc*;YoRmk6#U8#-HovNk&}s%q;$rR?|BW$JX!(Da2?q(BupTgXsjyGu zcOb8T3wCL#(`aUNrbG7DH361;f*vs=k@C|A*2b~Nx&u-a!=SDF1oT=k+)^rt&9)Tno(w z^=x<6t_)PV)wzyX2ho3M&HkOKgY!kR9XQ}KRMzjpwST(h7$QpKjvp53f+cJmR3}r` zyHMv{(Ui7xyP8X;lD6IIlxuthL0x8Mxs?2GvcsccUvk+7Y4);FE3hD5Di#a9O|Q#8 zIVxM4^yq?#i4Wor?8!OlB{=8xXy7&xn}fpHGHwR>Jspc`J9xGV%7AO}09DWL2a<8i5@O|L1=)?8Y<80va4^zJ2==kDi z@;5_?KV13~LOM8i_~~PKv@Y4Qp;{98A23MQ{r!;-jls87Nv`0Z{=@I|l4052xhM#1A?!DJ3RebW5(#8)|lOw(DtHm7QB@DRIbnR--M_zMTlfaG6S1L4nB z%0u6r5px9yHYaD8&Q_TT>LGr+!Dg`37QWp(cX9QR8RXWd6Pf>g_;}sX7mutTYV5BV z4G}*Ph0PvX`wj?ueL2PXl^#b!+KNf1{L!N}`WI6$_%m@&-hkNhP5dDR@%H=x6|DdM zHs{p9^=u)o|NdSK!3awT=U=(a7fQNZ23yfsB)0BU{iG4b=a zSFO?Sco^^Vt+9?i*cUl+q@aLolMD&f+3ESD*jY3Tk^~afcM1&m^fca!A}1GgY+=O2 zt5Q@XyYvImvwVTTO1zfh691P9QO&RFZww*?k>m$*=jiEI?Nd_2PvqaBLRkl|UtERl zzbCu#mHOyox)4d?kd8iGLWI}=Flt_nHM$-CctCV48dn*#$tTG!uekVpT_()(l5bW**MYKiXHPBxd3kn=pud;DO%H-e0q;0nFxzq-4Th z6ZYE#pFf-p%K-u8Jr;+-$^}!0Q(?Rw3M^0{bWYHT-9;hOt~om7y7U%yb}~L)3Y{Ds z)5&@MT$`G9r-rTF`M=hX<`m-A1^Wr>_qW$xNL%Hf(;IvC@t-*K7Btw zzn(GA7i41;ffh)HDWThsJd9ZnP;(wn{LsD@XH#zoSXFe2(sSsYA5$91-z$;qr@|1C z3yHz=_B#~`8Dvp{O28aorBCWt+h2dtXcd7Bz=aJBKr2)EGkIsz1)&36%qmcC@`d;FeS1q9B6| z%iD?y(aQuq@HZC=eQrnnX&Lx|lknHtUwZd`QSA80e*H{LFhB4NkyynRhG8WnC9n*?g$c$P z$kXknvL?EThNHJR1fuFchhjhpV?q5~-q@yz_{NBFl}w^emw z!~G0saRUP;sT^lA@4ZA+(H|7F6-Ft4J8NK2R@PoV`wTUDOyB=K1BS@SkQYgwCX*V) zY4B0e)Xvw1X0LvUUYd$v**D!qdX23%If*1#>mruSEG;uCQN)*)H&M#Upi5R{+- zqi*h+?cbJ;Ab(g3WnT8jc=bKq(mEU8)YPQhw}^Z0WHrLdG71#Yua^uA&8V&(Sp3m) z@|#Yx_X#Os;lGpZ5;ww}g^LP1PN2EaJtqnWl22i5-@hs>G`&GCGG?H=<)%k3p(vpL zeyvp~@zsGgUw6D9wG?7gQ%1)H8^q14E?`mS@N#~jTyK9uW8aGU_+EiJ(+dT{yZ&N=^QQmR zFs-V>rHemz!T@P3VBd07LWv;N9%W~HWrzP3+XXrG{jCG5Fa#ohSwV!0OPu3qQUdAkE6fh|C{WYhg#s6qsZlVl=hhR7WZAX@IltBrq}J&25>-m36X9 zzw9<~su!#Gy;>UPXP6IDz}Q}qmmk&HX>{l-`lZ@jLi0nw1^N5Un>VZd_Kl_n1dHur z@(xQ|cXwQXAy1ji{%0f2ssFqH^wEsk2p$pi$Ih;lOnS=ZO;o;>uQ~vi3t9{;E$)DyAsd(J~c`cT99HyIVm+2j>w`tojw1&c+?tB z{r93po4tWQpK4ksJ|Qb4I|ohdnU>kYE3a6TV2N6C;O#-Fi1Rt6O89O+S-ji05$CiP~kf zzK#uff9S5i)YO!tDFPifrv68W1EFr0J5m=$rzqhPewP&b#<51`=}I0OaY-n8FOD(n zfQA2xufgZmK<9BQY&|$diL$6JAJ~4+vY7fVi05Uz%>DDZZL;W+yhkaW-yb5jx~|fo z(&C0gcHIKNjp8$|mCDqXBzTl#(&S^oRRNSW9&)6#XRN%l$p9G9Y0)tXKXf*GipGec zFtV%|Db#WMCo=Z6lT)8#n&#!I{Rh6>>eQyR&|nzc!K5^lOXYj-aNBkpbD??uE{6Dw z1$K4FDfneC;*5KK{SpEJiHF0&Pap9!bX9csmk&>Hm%ao5#r9?Vq0@u)(U_0nh>>|S z;|aNYf{2CL6tM?BwH#?o7B)>%$RB;3hhhnwSfa|qhY$1KAnNhOHa8e0j$D58>Z<_( zbVuhUs0(t}Bw8YVNk4hmv>r1tSa=g5fyfmPUi)IxI`%=YH5=1^z*-yRz6|2J7w;N{ zi|Y2=93D5PbV$eM!nKu;{xAxjeIOg;d;d-TgLnq)6Pf0$tOP`)I%Wbem+RzKiF<+s zlPZ`t(T5wTZH*dD^*q(~rt?gpa_yu;m#h>Qi1uj)mv)t>MIgL}q^(I$N!#7sUUEDF zmbTQMkc(fPy*NE6{#sX`*+U4WEr_QkCM_@PRk0EyPr6y$yZu|PPgxKVZ8SQxs5B^o zG894}>#O}MD=XgzwTw%lj9a~ZecL@k{-z8C&bgHZ{5`1m8}AqN`A0+~;&*ye6DnO@ z?NP>AD((^J>cDSgDYv#ZstCclOzYxg&~BE=ic?d|v_FaJ^pE?e59ix(JpmAhK;vq= zL~4@pOpb19NEK^IRMi$J`X|iI&1J6}%20D^`|~I!glzs|nH(HxI8QMgRA9-stEC~LWuaP z>L}uWEsN7!T!IOs6PtMqULIq+`mv1&z##bo>!p?xQn<)F9*>Xeeoz>mb3`X4yO8&( zRsrR!A7UE$a?h5~%0{ur=C!3?G}iysTGQrN$b2Q*)_Hh%SPG~Yd@bc+L(yzGPoHXv zIm?>Hr+HC{$tUK1{HPN~jrg<%E5zJ>*`)UcU2vdAacTQrPsyPsa6GxnTZtnuZ9xzd z-rf!v0UyRkWks;LaZe)7AFV7jQBBC%%en><&El>a!You(K2B z_)w)?RqHe>YYYn6d4f_oLJw10*`=rHj`LuuL%|A;EO&j@c!U`;<M!wVFF+jR4;Cm z&J58bL=;CO-j1;++er`@q;fb2CTZoFGuY94M^D!KS5bj_kX&5 zSySU)B;wOnICW~}ZuT~voYD?TT|r5yBQeCgkpw)GWN78xbB1iugB56G+S&a4{4exG zHu;5@xpj4xu;a0+3~Y-eX_+e9_!eu`zNKH5V7JWPJ^TiWZbmT_;1r&RxuOw@{n=X| zTi%MvrXce;dh~<|H?9b%T@oPpxx-c@S)W;Up+b+s*+=|yVQdJkZ5Y~JulbW_=?Edq z6t#8#i~7=m-%sl6x!V`I-}!5(NB1W$+BLj<{rUm;jEd1z#UZ|wa3JiFI*5ZFHxEO> ze>nOH6Em3Mh27}&s@QI9%Wws_a9l9-uPjRWU0eI(9I9JmDscmt$mwSQ3Kvu(l-giC z|4NQJO*Qm!g6PXzhmf6eF^{d!pa1RM>nd%4K3-hEzcVM^w6|-js7v52MC>mvsed5W zvf4IV8VZem+*yc0;ar)$Yh6gXXc|34K3SP5W^;`{h*L#Fumnw%kiWXzwah6vs)RNcrsOTLh;>tDZq)k6N_DHKYjN5+qA|CLA|{<{2ykZ{`4 zvQBm7V==YZ$M;GP-?L&Yk;g2&-G^isM&B%RY6y&e%L@1gq%x0|Gp_lSPob$v(v7@> zVZ4J-B!RXpmAt=17Cvy)A>b1ycIn;l}FL&$Z$o7YQNSvSCua0vCPNT;0qa~XNVr}C)0?;vl=(~lh{Q4DOSAo^*h&o<<{eDHy~T^yy1 zNRz+utTR(3nqI;r{adtDrwo|c+_D(JoRNWdL+p?8>1et$?QdMkTW>>Z9;85%Zu{F& zfya&>5V_WN@p7<525-iI%$7FG`uQ#dd)a3P-j{djk@boCv}WhecggT_c>Nw?>>YwK zs}0nCMV&s`>}KO%uJj$=Pf9LlotX>t-YBlW%7%hF24Z!C8g#|YOs?<~W=7CxPs(N2 z=vtyi0sJ3TgwtRB8ChA(N{Ctmv%QBfjbVB&!0SxqzUgsaCeDD+>N#$8O^9$a-h`p< zb5E!4`@wH^A6Rb9M)L)lHEh1)*VopjN<|D`ds+OlI89PT}#9LN}l?jk-olh4CCATn>4ols^?ip znXK=(he1X20GGj3Oeh;6rQWoOk>0rUR zb7WeE~I-n55)=pvJG{_B$D#GCZtE zva+&TtsB|1cCVRIgn75nosHJ9iA+Tye4kJTvhrzQtJ3ay0sxEzt)5S_xDR&ynIJzrQ-^7|$aqc{?EvQ=%!O%&nf<%Rw|j_ouj zh2hQcHue6o_ZXI|8sFt@^E50bT9mY7*!O}s8;rg0}M|AbA+3%gAuC9RV?*kTf zv$J=eq39<3b69?S5i`ka<+yx1UP>y0>8Q0Ini5wjzicGpXw&%kfERd+(_XuUMDj2Q zQ!j++qKngE1xH#+N~yS)OfT~BCmxBJurtK2Odqcn-jP(W1G-aDS$V;~3T5_#l@sNu zW>I3R{yBF->&rm2KyO7=m4pOgqzxnx2}w6;eiBrC+>Wn(QKx4|7^4qs@a+VZ>;`0F z`YD4}UxALm=u-%uF&Nt%Fc{QO_k0|3?4c=&QANi;0*SGG(v@xVp43dU+{DawoJA;c z6G&cXUSO)vh@pL9oJiYKD0dZIa#~zW?92YwF(#9bEPr+Tu#_E@h2|!buqd+6WHrHr zXEE&OO{N&TL!ZVjhlFGq1P9+w4!&Wo#CU}?a1>H_37y#`6?s$|BHyU{;uo?s_l_<< z*+7_(m_GNsXw2#k1K!Zkca+^n{pv)L)%z=v4+?;5qusE#km0a0&a%E|dV!gaC$xeN z>H7QlNY8rfQG#3HK`}jw8zRi*i%bj<|O1CmR$< z00E`8x6``|W}=6~E05cJ`>~h=7BcF`i!w`zK$?|FSMj??v3C0?b`Y_Bb_nC?mx5^isUTGWeH^pRRWS$*Z)mK~$30Ew{vt?H&^#l?(kIIB?7VyORo6kTiS+dJpNAfDad#XYVN-M-Yr0eQ;bV8#Dc+Zw z1=MQ`Tz6mD_v@RQrgZc*?k8FxvEM%+Yq*yL&wAdR1*)UNJO9;h3b;I~z41$p^a z*mmt^CCwOxOUkuV*9s)U?wUXHHs#m5FYOPs4W&?f$tw@uaJ_3`IXL-lbGWM0qGx~) zUH7)D%aB)(f?BZW`Z}E8L3S1!d%q*^Up;YqYk5Fv*#O5%sCTS#iLpP37$Ty*7_-0M zI5xAq*WKEhg9tcy@+pHE_Wv<;-tkob;otw9V;v(idmWTLv-ir#*A5Bk*ktdKag6L) zs-t8?Lb4SR2iYT|kkv7=vt@I?{qEoWc-(*cL*ZQS>v~w+s4XDW zML5Sn-g&i~m}JG44E#nGZu_7qy>wxeNZnsQIVkG^^^z&#Ek(blD%!Sq*(*@7c1@X% z;OzY0)Yi)M^j6bed3YlH5Q*EV{)j*cwwX}1LVSf2Bz42j<3>V%_JRa4jw`Y-fE3k4 zU2uQNWgbPC>Ae0@z@@NqWJi;jT*}a+V=SzoXO* zV!O^{j#xZY_kaI5(GqBxo>o55=g>b}cb~tQxKuVpe!yLi(fucruOTXsO8l&0NpG@J z*w%_8Z+f_3MnU5rlDV<*ajgayIj}4|wfY-b;(I-`IUf!@-4qvYTdEJmTYUpXCiNZ}_cwX~$ftZrRJ z4?`FL5czqT;K|}-Qtfe_Ix$*y{pY<)&hdv4&jc+$S$Ph6y?KH#e*$%ygZiwStE8SH zU4*P7xyQ2}Nvq=TpV9q$^04;q&@$a$srunT4zWlbT?w~Km(ec89Oms$33cFwe=MqOX$dxAm5Vn|DbeX* zW--ZLRKqV*f!1w8LcRcvu5=AN81TE& zYv_YcAp4hnax8qh?M9Buc5-m&0U>yy|I(V&@ZD6K1MTh8d*p_Z3J|H-CsI;U6H)B3 zf*>=*Ze4u^e3W%Ot9ug((Rth^Z#moT<0Z?9)I0jFZE)Fs7_BKa_x#9S6LM6Dqr3P@t*LY1dHz0p-fc0=h7R3bHUB1-6RmMt z!2b2jep^{R!7roC+~D&24aV1%|ErJp6r^1kTUc6_*eKK7xNv8LZ}(_;c;Fub_z{l- zEn5Ou2;d=j*ysLvh7ZtYK42@yYn?P%JPvvQm9;;W1iv+@&kYR?KC{83VJ#@Tk<@>910G>vU(fz}R^&!n zxkaeqJV5OQj#lr6h19LJdbq-ylxzPfNdl319hee^qe$-hUQ01ekLX~DtJ)p2^jtTI z)W(Z#yZaHDmN;>e8te%8)2AM7AOKjGe>58voUuRfmOfxfAnecMvFRHCzRFd`qfssU;>R)*i&uFS|YS zx<)Nz-h!gDymyZWj4?9HL6~D1j0$c(OPti<-v+1FqIyN^a{zlug52KbkQ8sa;q@3& z(CX5OCH>sjf}$QgdXyNtZh2W+`>)~0#9^EksR(%WGPf?(KRLH@cu7ldjkngL%E5v# z34X|^fXm(Cfc7psJ2kJ6VFT~^=v$NqcKT`xXz-e>l5ta>W&D1lgI1~h{Z!vpNWCZl zgdyG|dRbd&Y&K5<2fFg4#tO)>a>O>mWJDcx-}?Fabx-2eah=o z5+QS*3lb@$XgUL5)KCS1%s2O+n`(XG+c?$W?bf&}s;F4nb-M+8vdlOPt*tx#&}Na0 z=#V`8m8;#Nbx|LC(;8fAJp^Z!>*(K}l9Z<;4kgxxi zB4|RFl?{_~9&f6F7Oc0*RkbNKXxq@Z}*o|(42r;`i&+BHWBl(v83b< zSVW6_GT3k?ef1BtxvJpE2%Ur6{f~NHZ(gAYSCmAKL2T5*@$vDb$V+c1w9SO*OJoDl zdiyI$NuN=@&%HI@l|*J{6ho|i@`9!2Z19m<`k3^06m-0ILogYMe*JhdbK&3_JGfjk zFwOq$O!2!&UdAqL0HCruP^y1m5YFs_{?3#bJ>;fl9u)}@ioC^y16S~mOLpt?4y9lK{q8Edagb>${Bt1GvgN2bMqfdv1Mkh#NOkGJp`#3tZ)^ggEt!$zM0! zAkfAAAjbJThabfE?pO-~4AGCclE#ny$D4Ffi-rMKGowX%-?E^b-1uwN4}1ImtK-b7 zwvC7dPr5~#cYPBwjPpM)%Iy?V$of(9L@Q(ckX4(6jB@U9$oECL7;A(GfQS;6vXu7% zJ;k}cvtV5tp=oXqX;jVR#7PqjgBYX-XHzmE%bSs8)Q_CjKOZl zBFJJP`<{^zYDVc)su800Zh>*{HHtsKuQWDWdB4X`HtJVR)Xo1U#9p$<2Uw*X81wP+ zR?b*RZ0c9R`zXC0wQOMa(^4q@6Q$JF)*j;11^q?T@EmVQ!nH$y6gdE2LU}f};2drA9?G)hWZxi^0_+E|84;7CpkwbV17jxA_INmIL`#Gx%@a_<=de z!?a4KSvpF5{cIWG(V{=R0-w|dWtDKU_S8+MK$i$Gp-ZsG^B7mmsJ^zoeznf#VwY7O z(F$Uxif4dnSwWLiQ~tQz_>%dQ91S4$nP;~E+BpC6lF=$gC28OgsLAoSUe7MA*p9!< ztVE^w>3|!XFA`{KZisk(c}t`tcZ92=jO+$czM0wCg?9geIn2vlS4&f9>lP!d|G&yq zwNh+Oj`bThGGT80H?f%HYcA_)5N9DcP;qKP*>lmdDgxUn`|n*yQ_jD;i&1$wRbP4TOewNz! z{=CAv9je8+LGsTybE0HrsPINtjoetKYLK$-T@ALTc|FC+!3PB0sotd(}6Xe^3 zQ&iX^jBJge)}8loY(TxcC*%Xm6ag3A9*UbHE zPU$bZ+utgLo%rzy2)rvY79*z#Jc=gp062-9k680-lQy_?CJksnYru)l?1lzC+w{3w z+}H2lb2-31(r`=hfi{`bOQ-I3g@imbg8b-8=^-MOaNpb~!zgiGUta?|Y=2ReP^TRw zIBo$@SR8<2Op!&35&zUk8vJROvoLd`Jm@uDO#tU?=dJA@cD8bd)4s_%=R~ijPxe_U zCvJQNlxnr-!)H}V%rWbL|L(i$rNS>yl^;cjlT3#-bZ>zd`%pkp@qxevgSss;CP>H( zU-Xhi#>;TCHNE-t0n|wgjV2-&;kP7`2_WM zj^`Oj?ArDEOXhauv;j0!O8J1$T{3*iZ-2nGH5q{~UoDxvYF*V@iRGm(Je!YS5HFJ5 z!K0g7T5>!dPTPD5Vpt!(diAP-fg*t+0w2yG9gl~et^;GX4nAdeZDXSrxF-3+7+F$;rtSMP9~quh$~BWx)7R zHVq9b|JVGf!NqLL4$SS_V!m%SnDp!Sb{&f|Q5`-T6IJ2wH}ywsY%M2Pz|R=)ONv!l zgkQURPUDo%?s$AzHM`#!@P=naD&tT!pSb7}VJs1RYZONKWLf_o&FipGN|l37X3rN_ z^otWo9yRukFy<$neJYSk`Bf%$a}r;5yuy@3ZWujy@X4>d<4790o1YoXA7<&6Tyakt_$VNyXZz?_{I~?@`~F zl2B-KJ6G2%pK-qFnMZLh(2QEnU(F{f&bCYfSI7(yVpPcCu6hGOrMmj5FZvC##zV1LPEPI*Ku(@cd7QHUk#2f! zCtSI}nMqE`%+<{yII9J2lw^Pgl|4s6lrdjUVuG*n@RZ4&qLNSH@=U`IFP=_jRv8JO zl}Tku_p_EDfC)AQ2Or;hEVY-J(?e5uag{CM@3Ed=_sdDiW$^dJ_UF%m-5}M{wE>X6 z!ELVOu4Dy^G35A|j0{zqY0<%>l~*3TWr2_>*4Dnr{bdUe6kzI{HKP( z%h{0}yVIuM&L$c^8*O{kfGSk|53mQTghT9f7qnbJ!T*L}VV6&VxZn8>W{E6xI`~j2 zT}x7WURqIcZ*t>iZ|1;M1rxsOHe-~Ok8cYYf=b`>n6wPdwMy(avE6V(CV$6>J>bL3 z0EkYmbmYSXv?4-9miQ$NFL!Rx{P?60#2k9Pi&zI$OHW(}iJxyZmWRoFA@b(K zSt6a+Ukz*u0(NS#RTW`eti7U(ZWkUcM*Lvua4{kr;j9yLUOdM9!AtWf;W1 zI^;_|4_DajF6cE~J)LwQF8@!kRRsm^4Xl~e8dFlBJJ=Dp^O~vFzM?e0nLYP%hO`-QnBI8#4W4FpC2Nq$Z|1D#@>TFGD;cIoZ+PfAOgQTg^%56(B zgiH~H&2!jchvU}Ij-THOKah);8T_^6V@{P5quH9xEF0YH#Gaxc}2 zZ2XQy;O=1-2Z1B&Zjo-0D4WVfmKM!m<_q(YbRc17ybC(Pnr@|4GSf+f@a)BBTA#U?zC}pb2=q|5{PcT34N*opK$qOLeHqP{kEBS9#|G&LnIXi>I7%eoAp-u}byZa^dnlM{+wqqPYQ7^=tT| zJgmU7arF}Q>Pd%b0XgKmY|k}KnF+_>;H<*;@4K3e{L)RNKrfaq_8T0id)F;rmZO54 zUojZqa_hbO*+%(^8e?pX?;y&>PZYcMD8kZMvyZBN^3NZ=ak`fb zrvUGx1Mf^cfuvFa73fLR_XaJCb(^(5v=qz?ozmP!yu9MLfZn&3XL7Oe2m6(Tbj&au zX^I0j9sS71sN&EtS`ujSk1%idA;uxS;urPApsH*GXl)w6@g@Hv$e!%#4-1Kwg0rIHO*}iRS)@}XfqRRD%pM8df47Z{KUJ( zcT)bmJsUq_zKcCV4W#V~mwcRQmFEu1sKFi4IA{eXWa3kJuc(sBo?+bv+a zeE8>`$F*8VI#Nlgi!{Z5_0z%2fpu!g>W%FJut#R7Y4|NiWiB8|SdSJL#(YULu|JgxE;c>^A(_`RQTb_wawdvO(LZJX(lh z#3u4F=#O>@9FEitK*iF_lvBN>nG36o9hzk-70ENYPiznM-b#@l{+oKEBtK(u z@x~kXI92_x*pDmDF*l!iRztVP{b${8!H1+cvG+7I8fLo>6dy=uc{I92V~tGfG}qSF z_(Jz#%=JiKWE38!HZqF9SY{nBoub879oR|VmBlRW-=#dM{IKzqhzfg#=OAQTxBt)9 zw{_kh9~pA#*Hw=_#@45MW_WWILPvlNGbQz1jc;Zbi4JALO;{>}Mpn}qg z%~t4L?CtIO{dk}WcqQbQ#>RsLo)Lp8@FJ2P0d~{>74~XN`u8$37w|f#^jp(#N91juLmsZ=M$3 zd6pd5@mF1{g((Us9I>KpuJ*WnWdLy}ZQSWp#ke^;r<;B#b>39yM#U~Q|8J}K{Piha zNqPC$WN6)|nj=6;iz=nH)l&qo#|hiqt$G|7jnzo#)mHYpUKO4yAT92Q+#!-aJ%q3K z%y;|;hy}V{bDsQG*z{u0vqC`YR0mzowY6{p=njognHM^zLVJ?v|KpW-(@D^_2v#W$ zfTjBOM?%mk3{U?Etivs?lmh_nz04%`sQ40VITzQL_D~cpsss*lKmSmVeoy1{mADBk z{4sIqwpW3-Aqy)n4~4tWvD{*kk{XRXQ7wWOs?aWXroR_Y;YRSQiu5i40lu_c&6wYu zSDzg1jQKDom@~_J-7G;3jhbTb4RLNcdHFx*hxF?MZn=GDatPq~Y_+vb3%xu&>;+i_ zZ);ED8j2~Rp5GIRd*WS)S17t^Dtz+VBY7BvKxwCr3jdCG&UJA+u!G?@&F&29Ha#z;zeoc=|4aZX~JZPy8=$gM*n^J zBKOxoXTUGF^;Z8q6hTK1h`qb{c_tt((XsKl4%$X^Z}R)~&aa(SsNp18=hJ@{zrb0s zD|%9qoG&raF@#g2$^aQy#%ykIrEkE4*l?#I)c(nbfac-n9{0QXOg+|t1D*lAxhnQD zJLS(1$Ti;Db-T_9T7QX8di9cyX;oFSwY)<2R5$B+HIk5(>cIdV{d2PJ`Qr0zNLO6q zJ7=Sv@8^{IjstH+H`!B9KJZ{7P+>7B^jt&tLq#%cPcLmTa11$1+Ee)B^{~!x-i>sJ z1eYy+ak>Q{bm{5nER`ggK-rqHzhl+GZ(@#ee(OCY_I5Nj;@!KsgwE7~laeKc9RfAG z$B!FBkLIX7tQK(QM>jS>vHzh8UnovYV*(stGBHhO-*f}$sVFVAb=dLK?O4R_q<1_4 ztrHTj2XvGU;4sR?3gu&O5}5fl;rrDl+p{6>iX9C33N}uYS!8uOz80h8R4T|2>MMS_ zFqOYwI9Sr6$uRVKxj7${Wq#QazkC0yp+=ve=$G!^feaCTCM1*cc(Va?a!)eC7h*;+ zUG1w7Uovw$HT4J)tourf$pq-u;9Qv3&h9;4Hg8Un{~$1UQow^AQh7C$l*rnCV3n8t zo$D7EOW9y-y7FiV z?KOQL3|GX;e{mi6`17g8nh(;{^sAn&fU{pj;>qGK3k%wJva(l?e-f6pWv87zi@W^% zQMzox4Bsp2p0zc9JRa`moNZ<5g6Ut zUFK$<+2xYoAwysj0G3!!f$;Rh^8u-Sgy?cI;FG!pq8_t37(`sdYpMWk(Vx@FxQ{(O zJ(Fty^r!P}I&AGodids51=t0449~#_7}7!Zz+Fep8rg>IZ|o%K^ke0{5ewNHVq<0% zgp@_oF5nTH0M(tLr^j(f;PxL1ofsX>}V?_*f=*&)0y& zfsXzdr8SCk2PXcPHLwXPJb{05v^V&b?j`6%WZ4rfmW|rrXB)yn59`GsFXbc#7ZvOM z{(aQZU!P1jspoUa7pk=%&XNO{LYnj*kT&teP-UD4L}#~d=1)1n&)<7c!m>>9xgHxp zqz*0640pJ?y2>t-5kBoIS9grO;|Jb1en_FSajyGGG9--#M?#AaX+7ktJhSa00%Z8# zoS%OqPCg9Hl5nA(vCe)nTl3KfhPyu|YalbShAkVkhgq{q@t4bw#Ee%@HjsTk&k9zZlA|oh)>}5ng+L8Pu2bpL%8my;E+^ijAVK%{ z>xS_sx(Z&XLA&GNa>QzC$kUIAkvHIBTd?+OI(03Z`pL`>ZK#bo9i%dfYxef)#456S zKiX3?m)CD4zqSv^Ia3_(tIMg#U{B8qoq;+X;6&Rf&heK^hVZGEmI4_3r08kk3t0-eUNoM?4PJB;|Z5n``1#^tJwx&bJPOWDFDNc9b!8 ziRi5}`!&mPY#9jSaUy1&IINE)d?;8Hq}Su75#n=!|n+ zCPRfvrseReSiuD)xf=v7B>OnmO`#O%SsE7m%z>GlJhgQlJK`7 z+0xAMP|VX^pQR`Y|lUKSShIGLSCF_lVhqlC;pEUi46 z!(R&c46|thOMoK1zB!ld8C1;$1|*sr=~gnHQj&|^x^*jazDjo(L8?io%;9w=Lt%fi zOS8Z#)<}8uv^*dzWA0EKyyrOG+ee4@5&dA21?z$96STKvI7-1v^5CGc*jvf9&^!W0 z$}kW4`TO6$ni*Ll(>08W^i5;7z%X|kU#(ngUqC8Dx@suKpP5k`#WpZ6g70MrEF(x@O>sn!L;{^5vjw zRj4g3ef7#TPE#=S^7ZS(56Ps=#lryz-^SOG5cxaKx3todlA}*MJ+Sv9Oc2=td!-p* zO)+?2l<%}Ofbr$l&n}kMsGPOip@+wJ)rK4}H2}%ZuM62o*nlFBZgcI&+f&ri0YcH2b-t3BVAl@ow`~FHZK6sLE(^_a!jRo4%%Nmec>r4m8YapfA*=AISZ@ zF;ztcHcnSe?)@6aVXa}!?EC&32vjmBEAl~_ZP!pC4@VzL9A6p@k~gw$Gr3eDBCq0j z7Q3mE9_B5f~K0I0uUN z2d}w1*MMX+o;Y&WC6_AS%Be|XAD^c>MCPjDlZm$k(r974kEPwC)~W7(?M%#f`K}rZ zRBzMQZ~ZL%F`d~mtF5ObeW}3s{~?;mcLti@aQv2LS7rc$BmkRJY@fzZhnvwRP}vS* z^G>VUQG--Y)?$(EC6s5E-kbyTfd7eW43zE$NjMBdSlJ18cVY!05s~LVObF@gXr0a2 z#Nl2Bzbb9Go|}kqIgdq8T<4%gSzEJ7YdBVMdpxSlsV&~n1?1&De(e743qR}J z4`*wp_<6d~UbjHI4xmdKT6t@S!n9TL2DXn+L8=M~`{6B_2kl?pyn0nS3TyiseXFD; zC<4%MVBpAbtvF{Hr|Gb$|AK=kE`7@$yOzm!h*&^Du2wRde}S3O9$W*pu-p8Bsx9?3 z46-cu<01gG+~1$-` zMq|U`qWynnWB3-(krT#plBWF8i?-E@UVw1xyHF5KW}kletbRyHwc`3{OpN+`Nc7h~ z0XhJT-Y$H|54lDE8cy`)7jqh9xtV8gq~d)⪼Aqj&YitlCNE-G8p1i?LT{r)#{h_ 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 tv3K 0.2.1) +# 3) Else if a tag vX.Y.Z exists, use the latest tag (drop the leading v) +# 4) Fallback to 0.0.0-dev +detect_version() { + if [ -n "${VERSION:-}" ]; then + echo "$VERSION"; return + fi + branch=$(git -C "$SCRIPT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "") + if [[ "$branch" == release/* ]]; then + echo "${branch#release/}"; return + fi + tag=$(git -C "$SCRIPT_DIR" describe --tags --abbrev=0 2>/dev/null || echo "") + if [[ "$tag" == v* ]]; then + echo "${tag#v}"; return + fi + echo "0.0.0-dev" +} +VERSION="$(detect_version)" + +# Clean previous bundle +echo "🧹 Cleaning previous bundle..." +rm -rf "$APP_BUNDLE" + +# Build the executable with Swift Package Manager and discover the bin path +echo "🔧 Building executable..." +BIN_PATH=$(swift build --configuration release --show-bin-path) + +# Create app bundle structure +echo "📦 Creating app bundle structure..." +mkdir -p "$APP_BUNDLE/Contents/MacOS" +mkdir -p "$APP_BUNDLE/Contents/Resources" + +# Copy the executable +echo "📋 Copying executable..." +cp "$BIN_PATH/$EXECUTABLE_NAME" "$APP_BUNDLE/Contents/MacOS/$EXECUTABLE_NAME" + +# Copy Metal shaders +if [ -f "$METALLIB_PATH" ]; then + echo "🎨 Copying Metal shaders..." + cp "$METALLIB_PATH" "$APP_BUNDLE/Contents/Resources/" +else + echo "⚠️ Warning: Metal shader library not found at $METALLIB_PATH" +fi + +# Copy app icon if it exists +if [ -f "Resources/AppIcon.icns" ]; then + echo "🎨 Copying app icon..." + cp "Resources/AppIcon.icns" "$APP_BUNDLE/Contents/Resources/" +fi + +# Create Info.plist +echo "📝 Creating Info.plist..." + +# Check if icon exists to conditionally add it +ICON_ENTRY="" +if [ -f "Resources/AppIcon.icns" ]; then + ICON_ENTRY=" CFBundleIconFile\n AppIcon" +fi + +cat > "$APP_BUNDLE/Contents/Info.plist" << EOF + + + + + CFBundleExecutable + $EXECUTABLE_NAME + CFBundleIdentifier + $BUNDLE_ID + CFBundleName + $APP_NAME + CFBundlePackageType + APPL + CFBundleShortVersionString + $VERSION + CFBundleVersion + 1 + LSMinimumSystemVersion + 14.0 + NSHighResolutionCapable + + NSSupportsAutomaticGraphicsSwitching + +$(echo -e "$ICON_ENTRY") + + +EOF + +# Make executable +chmod +x "$APP_BUNDLE/Contents/MacOS/$EXECUTABLE_NAME" + +echo "✅ App bundle created successfully at: $APP_BUNDLE" +echo "" +echo "To test the app, run:" +echo " open $APP_BUNDLE" +echo "" +echo "Or double-click the app in Finder" diff --git a/create_dmg.sh b/create_dmg.sh new file mode 100755 index 0000000..6026349 --- /dev/null +++ b/create_dmg.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +set -e # Exit on error + +echo "📀 Creating DMG installer for Untold Engine Studio..." + +# Configuration +APP_NAME="Untold Engine Studio" +DMG_NAME="UntoldEngineStudio" +APP_BUNDLE="$APP_NAME.app" +DMG_TEMP_DIR="dmg_temp" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Determine version (env -> release/* branch -> latest tag vX.Y.Z -> fallback) +detect_version() { + if [ -n "${VERSION:-}" ]; then + echo "$VERSION"; return + fi + branch=$(git -C "$SCRIPT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "") + if [[ "$branch" == release/* ]]; then + echo "${branch#release/}"; return + fi + tag=$(git -C "$SCRIPT_DIR" describe --tags --abbrev=0 2>/dev/null || echo "") + if [[ "$tag" == v* ]]; then + echo "${tag#v}"; return + fi + echo "0.0.0-dev" +} +VERSION="$(detect_version)" + +DMG_FILE="$DMG_NAME-$VERSION.dmg" + +# Check if app bundle exists +if [ ! -d "$APP_BUNDLE" ]; then + echo "❌ Error: $APP_BUNDLE not found. Run ./create_app_bundle.sh first." + exit 1 +fi + +# Clean previous DMG files +echo "🧹 Cleaning previous DMG files..." +rm -f "$DMG_FILE" +rm -rf "$DMG_TEMP_DIR" + +# Create temporary directory for DMG contents +echo "📦 Creating DMG staging directory..." +mkdir -p "$DMG_TEMP_DIR" + +# Copy app bundle to staging +echo "📋 Copying app bundle..." +cp -R "$APP_BUNDLE" "$DMG_TEMP_DIR/" + +# Create symbolic link to Applications folder +echo "🔗 Creating Applications folder link..." +ln -s /Applications "$DMG_TEMP_DIR/Applications" + +# Create DMG +echo "💿 Creating DMG image..." +hdiutil create -volname "$APP_NAME" \ + -srcfolder "$DMG_TEMP_DIR" \ + -ov -format UDZO \ + "$DMG_FILE" + +# Clean up temp directory +echo "🧹 Cleaning up..." +rm -rf "$DMG_TEMP_DIR" + +echo "✅ DMG created successfully: $DMG_FILE" +echo "" +echo "To test the DMG:" +echo " open $DMG_FILE" +echo "" +echo "Users can drag '$APP_NAME.app' to the Applications folder to install." From 15b4555260694294aaf9da942b2489420773b87a Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Sun, 14 Dec 2025 22:55:15 -0700 Subject: [PATCH 25/41] Fixed create_app_bundle script --- create_app_bundle.sh | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/create_app_bundle.sh b/create_app_bundle.sh index 9c77d5d..66ee708 100755 --- a/create_app_bundle.sh +++ b/create_app_bundle.sh @@ -8,39 +8,20 @@ echo "🔨 Building UntoldEditor app bundle..." APP_NAME="Untold Engine Studio" EXECUTABLE_NAME="UntoldEditor" BUNDLE_ID="com.untoldengine.studio" +VERSION="0.2.0" +BUILD_DIR=".build/arm64-apple-macosx/release" APP_BUNDLE="$APP_NAME.app" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" UNTOLD_ENGINE_ROOT="$SCRIPT_DIR/../UntoldEngine" METALLIB_PATH="$UNTOLD_ENGINE_ROOT/Sources/UntoldEngine/UntoldEngineKernels/UntoldEngineKernels.metallib" -# Determine version: -# 1) If VERSION env is set, use it -# 2) Else if on a release/* branch, use the suffix (release/0.2.1 -> 0.2.1) -# 3) Else if a tag vX.Y.Z exists, use the latest tag (drop the leading v) -# 4) Fallback to 0.0.0-dev -detect_version() { - if [ -n "${VERSION:-}" ]; then - echo "$VERSION"; return - fi - branch=$(git -C "$SCRIPT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "") - if [[ "$branch" == release/* ]]; then - echo "${branch#release/}"; return - fi - tag=$(git -C "$SCRIPT_DIR" describe --tags --abbrev=0 2>/dev/null || echo "") - if [[ "$tag" == v* ]]; then - echo "${tag#v}"; return - fi - echo "0.0.0-dev" -} -VERSION="$(detect_version)" - # Clean previous bundle echo "🧹 Cleaning previous bundle..." rm -rf "$APP_BUNDLE" -# Build the executable with Swift Package Manager and discover the bin path +# Build the executable with Swift Package Manager echo "🔧 Building executable..." -BIN_PATH=$(swift build --configuration release --show-bin-path) +swift build --configuration release # Create app bundle structure echo "📦 Creating app bundle structure..." @@ -49,7 +30,7 @@ mkdir -p "$APP_BUNDLE/Contents/Resources" # Copy the executable echo "📋 Copying executable..." -cp "$BIN_PATH/$EXECUTABLE_NAME" "$APP_BUNDLE/Contents/MacOS/$EXECUTABLE_NAME" +cp "$BUILD_DIR/$EXECUTABLE_NAME" "$APP_BUNDLE/Contents/MacOS/$EXECUTABLE_NAME" # Copy Metal shaders if [ -f "$METALLIB_PATH" ]; then From d213f86fbe221f013718c6dfcd542d0e060067e5 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Sun, 14 Dec 2025 23:25:15 -0700 Subject: [PATCH 26/41] [Patch] Added guard if base path is missing --- .../Build/BuildSettingsView.swift | 15 +++++++++ .../Editor/AssetBrowserView.swift | 14 +++++++- .../Editor/ScriptComponentInspector.swift | 11 +++++++ Sources/UntoldEditor/Editor/ToolbarView.swift | 32 +++++++++++++++++-- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/Sources/UntoldEditor/Build/BuildSettingsView.swift b/Sources/UntoldEditor/Build/BuildSettingsView.swift index a61d24c..475e642 100644 --- a/Sources/UntoldEditor/Build/BuildSettingsView.swift +++ b/Sources/UntoldEditor/Build/BuildSettingsView.swift @@ -25,6 +25,7 @@ struct BuildSettingsView: View { @State private var showBuildResult: Bool = false @State private var buildResultMessage: String = "" @State private var buildSucceeded: Bool = false + @State private var showBasePathAlert: Bool = false @Environment(\.dismiss) private var dismiss @@ -122,6 +123,7 @@ struct BuildSettingsView: View { HStack { Spacer() Button("Build") { + guard ensureAssetBasePath() else { return } startBuild() } .buttonStyle(.borderedProminent) @@ -146,6 +148,11 @@ struct BuildSettingsView: View { } message: { Text(buildResultMessage) } + .alert("Set Asset Folder First", isPresented: $showBasePathAlert) { + Button("OK", role: .cancel) {} + } message: { + Text("Please set the Asset Folder in the Asset Browser before building.") + } .onAppear { loadDefaultSettings() } @@ -260,6 +267,14 @@ struct BuildSettingsView: View { NSWorkspace.shared.open(xcodeProjectPath) } + + private func ensureAssetBasePath() -> Bool { + guard EditorAssetBasePath.shared.basePath != nil else { + showBasePathAlert = true + return false + } + return true + } } #Preview { diff --git a/Sources/UntoldEditor/Editor/AssetBrowserView.swift b/Sources/UntoldEditor/Editor/AssetBrowserView.swift index 438bf5f..1b5d08d 100644 --- a/Sources/UntoldEditor/Editor/AssetBrowserView.swift +++ b/Sources/UntoldEditor/Editor/AssetBrowserView.swift @@ -50,6 +50,7 @@ struct AssetBrowserView: View { @State private var folderPathStack: [URL] = [] @State private var showSceneLoadConfirmation = false @State private var pendingSceneToLoad: URL? + @State private var showBasePathAlert = false var editor_addEntityWithAsset: () -> Void private var currentFolderPath: URL? { folderPathStack.last @@ -259,6 +260,11 @@ struct AssetBrowserView: View { } message: { Text("Loading a new scene will replace the current scene. Any unsaved changes will be lost.") } + .alert("Set Asset Folder First", isPresented: $showBasePathAlert) { + Button("OK", role: .cancel) {} + } message: { + Text("Please set the Asset Folder in the Asset Browser before importing assets.") + } } // MARK: - Select Resource Directory @@ -288,6 +294,11 @@ struct AssetBrowserView: View { } private func importAsset() { + guard editorBaseAssetPath.basePath != nil else { + showBasePathAlert = true + return + } + let openPanel = NSOpenPanel() openPanel.allowedContentTypes = [ UTType(filenameExtension: "usdz")!, @@ -307,7 +318,7 @@ struct AssetBrowserView: View { let fm = FileManager.default let categoryRoot = basePath.appendingPathComponent(selectedCategory!, isDirectory: true) // Ensure category folder exists (e.g., /Models) - try? fm.createDirectory(at: categoryRoot, withIntermediateDirectories: true) + try? fm.createDirectory(at: categoryRoot, withIntermediateDirectories: true) if openPanel.runModal() == .OK { for sourceURL in openPanel.urls { @@ -389,6 +400,7 @@ struct AssetBrowserView: View { // MARK: - Load Assets private func loadAssets() { + guard editorBaseAssetPath.basePath != nil else { return } guard let basePath = assetBasePath else { return } var groupedAssets: [String: [Asset]] = [:] diff --git a/Sources/UntoldEditor/Editor/ScriptComponentInspector.swift b/Sources/UntoldEditor/Editor/ScriptComponentInspector.swift index 1b14dd6..fb4cb32 100644 --- a/Sources/UntoldEditor/Editor/ScriptComponentInspector.swift +++ b/Sources/UntoldEditor/Editor/ScriptComponentInspector.swift @@ -21,6 +21,7 @@ struct ScriptComponentInspector: View { // We keep minimal UI state; details are read from the component each render. @State private var showError: Bool = false @State private var errorMessage: String = "" + @State private var showBasePathAlert: Bool = false var body: some View { VStack(alignment: .leading, spacing: 12) { @@ -88,6 +89,11 @@ struct ScriptComponentInspector: View { .onAppear { // Nothing to pre-load; we render directly from the component. } + .alert("Set Asset Folder First", isPresented: $showBasePathAlert) { + Button("OK", role: .cancel) {} + } message: { + Text("Please set the Asset Folder in the Asset Browser before loading or creating scripts.") + } } // MARK: - Script Row @@ -177,6 +183,11 @@ struct ScriptComponentInspector: View { // MARK: - Load Script File (Append) private func loadScriptFileAndAppend() { + guard EditorAssetBasePath.shared.basePath != nil else { + showBasePathAlert = true + return + } + // Prefer selected asset from Asset Browser if let selectedScript = asset, selectedScript.category == "Scripts", diff --git a/Sources/UntoldEditor/Editor/ToolbarView.swift b/Sources/UntoldEditor/Editor/ToolbarView.swift index 9425a5b..7d2ff24 100644 --- a/Sources/UntoldEditor/Editor/ToolbarView.swift +++ b/Sources/UntoldEditor/Editor/ToolbarView.swift @@ -34,6 +34,7 @@ @State private var showingNewScriptDialog = false @State private var newScriptName = "" @State private var scriptEditorWindow: NSWindow? + @State private var showBasePathAlert = false var body: some View { HStack { @@ -69,6 +70,11 @@ } ) } + .alert("Set Asset Folder First", isPresented: $showBasePathAlert) { + Button("OK", role: .cancel) {} + } message: { + Text("Please set the Asset Folder in the Asset Browser before creating or editing scripts.") + } } var rightSection: some View { @@ -185,7 +191,10 @@ .foregroundColor(.secondary) // New Script - Button(action: { showingNewScriptDialog = true }) { + Button(action: { + guard ensureAssetBasePath() else { return } + showingNewScriptDialog = true + }) { HStack(spacing: 4) { Image(systemName: "plus.circle.fill") Text("New") @@ -200,7 +209,10 @@ .buttonStyle(.plain) // Open in Xcode - Button(action: { openInXcode() }) { + Button(action: { + guard ensureAssetBasePath() else { return } + openInXcode() + }) { HStack(spacing: 4) { Image(systemName: "hammer.circle.fill") Text("Open in Xcode") @@ -215,7 +227,10 @@ .buttonStyle(.plain) // Open in-app script editor - Button(action: { toggleScriptEditorWindow() }) { + Button(action: { + guard ensureAssetBasePath() else { return } + toggleScriptEditorWindow() + }) { HStack(spacing: 4) { Image(systemName: "chevron.left.forwardslash.chevron.right") Text("Script Editor") @@ -234,6 +249,7 @@ // MARK: - Script Management Functions private func createNewScript() { + guard ensureAssetBasePath() else { return } let manager = ScriptProjectManager.shared // Initialize project if needed @@ -263,6 +279,7 @@ } private func openInXcode() { + guard ensureAssetBasePath() else { return } let manager = ScriptProjectManager.shared // Initialize project if needed @@ -290,6 +307,7 @@ } private func toggleScriptEditorWindow() { + guard ensureAssetBasePath() else { return } if let window = scriptEditorWindow { window.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) @@ -309,6 +327,14 @@ NSApp.activate(ignoringOtherApps: true) self.scriptEditorWindow = window } + + private func ensureAssetBasePath() -> Bool { + guard EditorAssetBasePath.shared.basePath != nil else { + showBasePathAlert = true + return false + } + return true + } } // MARK: - Toolbar Button Component From 83ae8f2a084cd3aaa0cdc5ccba1bd32b9bdc5654 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Mon, 15 Dec 2025 22:15:06 -0700 Subject: [PATCH 27/41] [Patch] additional ux improvements --- .../Editor/AssetBrowserView.swift | 138 ++++++++++++- .../Editor/EditorController.swift | 44 ++++ Sources/UntoldEditor/Editor/EditorView.swift | 193 ++++++++++++++++-- .../Editor/SceneHierarchyView.swift | 34 ++- Sources/UntoldEditor/Editor/ToolbarView.swift | 124 +++-------- 5 files changed, 408 insertions(+), 125 deletions(-) diff --git a/Sources/UntoldEditor/Editor/AssetBrowserView.swift b/Sources/UntoldEditor/Editor/AssetBrowserView.swift index 1b5d08d..f18f9e1 100644 --- a/Sources/UntoldEditor/Editor/AssetBrowserView.swift +++ b/Sources/UntoldEditor/Editor/AssetBrowserView.swift @@ -12,12 +12,12 @@ import UntoldEngine enum AssetCategory: String, CaseIterable { case models = "Models" - case materials = "Materials" - case hdr = "HDR" case animations = "Animations" - case gaussians = "Gaussians" - case scenes = "Scenes" case scripts = "Scripts" + case scenes = "Scenes" + case gaussians = "Gaussians" + case materials = "Materials" + case hdr = "HDR" var iconName: String { switch self { @@ -50,7 +50,12 @@ struct AssetBrowserView: View { @State private var folderPathStack: [URL] = [] @State private var showSceneLoadConfirmation = false @State private var pendingSceneToLoad: URL? + @State private var showDeleteConfirmation = false + @State private var pendingDeleteAsset: Asset? @State private var showBasePathAlert = false + @State private var searchQuery: String = "" + @State private var statusMessage: String? + @State private var statusIsError = false var editor_addEntityWithAsset: () -> Void private var currentFolderPath: URL? { folderPathStack.last @@ -84,6 +89,22 @@ struct AssetBrowserView: View { } .buttonStyle(PlainButtonStyle()) + Button(action: promptDeleteAsset) { + HStack(spacing: 6) { + Text("Delete") + Image(systemName: "trash") + .foregroundColor(.white) + } + .padding(.vertical, 6) + .padding(.horizontal, 12) + .background(selectedAsset == nil ? Color.gray.opacity(0.5) : Color.red) + .foregroundColor(.white) + .cornerRadius(8) + .shadow(color: Color.black.opacity(0.2), radius: 4, x: 0, y: 2) + } + .buttonStyle(PlainButtonStyle()) + .disabled(selectedAsset == nil) + Spacer() // Set Base Path Button @@ -124,6 +145,15 @@ struct AssetBrowserView: View { .padding(.bottom, 5) } + // MARK: - Search + HStack { + Image(systemName: "magnifyingglass") + TextField("Filter assets", text: $searchQuery) + .textFieldStyle(RoundedBorderTextFieldStyle()) + } + .padding(.horizontal, 10) + .padding(.bottom, 4) + // MARK: - Sidebar and Asset List Layout HStack(spacing: 8) { @@ -198,8 +228,8 @@ struct AssetBrowserView: View { if let currentFolderPath, !isScripts { folderContentsView(for: currentFolderPath, selectionManager: selectionManager) } else { - if let categoryAssets = assets[selectedCategory] { - ForEach(categoryAssets) { asset in + if let categoryAssets = assets[selectedCategory] { + ForEach(categoryAssets.filter { matchesSearch($0) }) { asset in // For Scripts, we never navigate into folders (we won't list folders anyway) assetRow(asset) .onTapGesture(count: 2) { @@ -265,6 +295,34 @@ struct AssetBrowserView: View { } message: { Text("Please set the Asset Folder in the Asset Browser before importing assets.") } + .alert("Delete Asset?", isPresented: $showDeleteConfirmation) { + Button("Cancel", role: .cancel) { + pendingDeleteAsset = nil + } + Button("Delete", role: .destructive) { + if let asset = pendingDeleteAsset { + deleteAsset(asset) + } + pendingDeleteAsset = nil + } + } message: { + if let asset = pendingDeleteAsset { + Text("This will remove \(asset.name) from disk under your Asset Folder.") + } + } + .overlay(alignment: .bottom) { + if let statusMessage { + Text(statusMessage) + .font(.system(size: 12, weight: .semibold)) + .foregroundColor(.white) + .padding(.vertical, 6) + .padding(.horizontal, 12) + .background(statusIsError ? Color.red.opacity(0.85) : Color.green.opacity(0.85)) + .cornerRadius(8) + .padding(.bottom, 8) + .transition(.move(edge: .bottom).combined(with: .opacity)) + } + } } // MARK: - Select Resource Directory @@ -394,9 +452,16 @@ struct AssetBrowserView: View { } loadAssets() + showStatus("Imported \(openPanel.urls.count) item(s)") } } + private func promptDeleteAsset() { + guard let asset = selectedAsset else { return } + pendingDeleteAsset = asset + showDeleteConfirmation = true + } + // MARK: - Load Assets private func loadAssets() { @@ -491,6 +556,23 @@ struct AssetBrowserView: View { assets = groupedAssets } + private func matchesSearch(_ asset: Asset) -> Bool { + let query = searchQuery.trimmingCharacters(in: .whitespacesAndNewlines) + guard query.isEmpty == false else { return true } + return asset.name.localizedCaseInsensitiveContains(query) + } + + private func showStatus(_ message: String, isError: Bool = false) { + statusMessage = message + statusIsError = isError + + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + if statusMessage == message { + statusMessage = nil + } + } + } + // Recursively find all files with a specific extension under a root directory private func findFilesRecursively(at root: URL, withExtension ext: String) -> [URL] { var results: [URL] = [] @@ -547,7 +629,7 @@ struct AssetBrowserView: View { } VStack(alignment: .leading, spacing: 8) { - ForEach(items) { asset in + ForEach(items.filter { matchesSearch($0) }) { asset in assetRow(asset) .onTapGesture(count: 2) { handle_add_model_double_click(asset: asset) @@ -578,6 +660,36 @@ struct AssetBrowserView: View { selectedAssetName = asset.name } + // MARK: - Delete Asset + + private func deleteAsset(_ asset: Asset) { + guard let basePath = assetBasePath else { + showBasePathAlert = true + return + } + + // Ensure the asset lives under the base path before deleting + guard asset.path.resolvingSymlinksInPath().path.hasPrefix(basePath.resolvingSymlinksInPath().path) else { + print("⚠️ Refusing to delete asset outside base path: \(asset.path.path)") + showStatus("Cannot delete outside Asset Folder", isError: true) + return + } + + do { + try FileManager.default.removeItem(at: asset.path) + print("✅ Deleted asset: \(asset.name)") + if selectedAsset?.id == asset.id { + selectedAsset = nil + selectedAssetName = nil + } + loadAssets() + showStatus("Deleted \(asset.name)") + } catch { + print("❌ Failed to delete asset \(asset.name): \(error)") + showStatus("Failed to delete \(asset.name)", isError: true) + } + } + // MARK: - Add Model with Double Click private func handle_add_model_double_click(asset: Asset) { @@ -594,8 +706,9 @@ struct AssetBrowserView: View { // Create entity let entityId = createEntity() - // Set entity name based on asset filename - setEntityName(entityId: entityId, name: filename) + // Use a generated name to avoid duplicate names when importing repeatedly + let uniqueName = generateEntityName() + setEntityName(entityId: entityId, name: uniqueName) // Add mesh to entity setEntityMesh(entityId: entityId, filename: filename, withExtension: withExtension) @@ -613,8 +726,9 @@ struct AssetBrowserView: View { // Create entity let entityId = createEntity() - // Set entity name based on asset filename - setEntityName(entityId: entityId, name: filename) + // Use a generated name to avoid duplicate names when importing repeatedly + let uniqueName = generateEntityName() + setEntityName(entityId: entityId, name: uniqueName) // Add Gaussian component to entity setEntityGaussian(entityId: entityId, filename: filename, withExtension: withExtension) @@ -705,6 +819,7 @@ struct AssetBrowserView: View { // Show confirmation dialog before loading scene pendingSceneToLoad = asset.path showSceneLoadConfirmation = true + editorController?.currentSceneURL = asset.path } // Handle HDR files (hdr) else if asset.category == AssetCategory.hdr.rawValue, @@ -747,6 +862,7 @@ struct AssetBrowserView: View { // Reset camera CameraSystem.shared.activeCamera = findSceneCamera() + editorController?.currentSceneURL = url print("✅ Scene loaded: \(url.lastPathComponent)") } } diff --git a/Sources/UntoldEditor/Editor/EditorController.swift b/Sources/UntoldEditor/Editor/EditorController.swift index 51ad421..f3f3c2b 100644 --- a/Sources/UntoldEditor/Editor/EditorController.swift +++ b/Sources/UntoldEditor/Editor/EditorController.swift @@ -65,6 +65,7 @@ class EditorController: SelectionDelegate, ObservableObject { var isEnabled: Bool = false @Published var activeMode: TransformManipulationMode = .none @Published var activeAxis: TransformAxis = .none + @Published var currentSceneURL: URL? init(selectionManager: SelectionManager) { self.selectionManager = selectionManager @@ -157,6 +158,49 @@ func saveScene(sceneData: SceneData) { } } +/// Save directly to a URL (used by the inline save flow). +func saveSceneDirect(sceneData: SceneData, to url: URL) { + do { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + + let jsonData = try encoder.encode(sceneData) + try jsonData.write(to: url) + Logger.log(message: "Scene saved to \(url.path)") + + guard let basePath = assetBasePath else { + Logger.log(message: "Warning: assetBasePath is not set; cannot copy scene into Scenes folder.") + NotificationCenter.default.post(name: .assetBrowserReload, object: nil) + return + } + + let fm = FileManager.default + let scenesRoot = basePath.appendingPathComponent("Scenes", isDirectory: true) + try? fm.createDirectory(at: scenesRoot, withIntermediateDirectories: true) + + let resolvedSaveURL = url.resolvingSymlinksInPath() + let resolvedScenesRoot = scenesRoot.resolvingSymlinksInPath() + let saveParent = resolvedSaveURL.deletingLastPathComponent() + + let isAlreadyInScenes = (saveParent == resolvedScenesRoot) + let destURL = resolvedScenesRoot.appendingPathComponent(resolvedSaveURL.lastPathComponent) + + if isAlreadyInScenes { + Logger.log(message: "Scene already in Scenes folder: \(destURL.path)") + } else { + if fm.fileExists(atPath: destURL.path) { + try fm.removeItem(at: destURL) + } + try fm.copyItem(at: resolvedSaveURL, to: destURL) + Logger.log(message: "Scene copied into Scenes folder: \(destURL.path)") + } + + NotificationCenter.default.post(name: .assetBrowserReload, object: nil) + } catch { + Logger.log(message: "Failed to save scene: \(error)") + } +} + func loadGameScene() -> SceneData? { let openPanel = NSOpenPanel() openPanel.title = "Open Scene" diff --git a/Sources/UntoldEditor/Editor/EditorView.swift b/Sources/UntoldEditor/Editor/EditorView.swift index 3ba8dd3..fe4e8f5 100644 --- a/Sources/UntoldEditor/Editor/EditorView.swift +++ b/Sources/UntoldEditor/Editor/EditorView.swift @@ -11,12 +11,25 @@ public struct Asset: Identifiable { } public struct EditorView: View { + enum InspectorTab: String, CaseIterable, Hashable { + case inspector = "Inspector" + case environment = "Environment" + case effects = "Effects" + } + @State private var editor_entities: [EntityID] = getAllGameEntities() @StateObject private var selectionManager = SelectionManager() @StateObject private var sceneGraphModel = SceneGraphModel() @State private var assets: [String: [Asset]] = [:] @State private var selectedAsset: Asset? = nil @State private var isPlaying = false + @State private var inspectorTab: InspectorTab = .inspector + @State private var showSaveNamePrompt = false + @State private var pendingSceneName: String = "untitled" + @State private var showOverwriteAlert = false + @State private var pendingTargetURL: URL? + @State private var isSaveAs = false + @State private var showSaveBasePathAlert = false var renderer: UntoldRenderer? @@ -40,8 +53,8 @@ public struct EditorView: View { public var body: some View { VStack { ToolbarView( - selectionManager: selectionManager, onSave: editor_handleSave, - onLoad: editor_handleLoad, onClear: editor_clearScene, + selectionManager: selectionManager, onSave: editor_handleSave, onSaveAs: editor_handleSaveAs, + onClear: editor_clearScene, onPlayToggled: { isPlaying in editor_handlePlayToggle(isPlaying) }, dirLightCreate: editor_createDirLight, pointLightCreate: editor_createPointLight, @@ -56,7 +69,20 @@ public struct EditorView: View { Divider() HStack { VStack { - SceneHierarchyView(selectionManager: selectionManager, sceneGraphModel: sceneGraphModel, entityList: editor_entities, onAddEntity_Editor: editor_addNewEntity, onRemoveEntity_Editor: editor_removeEntity) + SceneHierarchyView( + selectionManager: selectionManager, + sceneGraphModel: sceneGraphModel, + entityList: editor_entities, + onAddEntity_Editor: editor_addNewEntity, + onRemoveEntity_Editor: editor_removeEntity, + onAddCube: editor_createCube, + onAddSphere: editor_createSphere, + onAddPlane: editor_createPlane, + onAddDirLight: editor_createDirLight, + onAddPointLight: editor_createPointLight, + onAddSpotLight: editor_createSpotLight, + onAddAreaLight: editor_createAreaLight + ) } VStack(spacing: 0) { @@ -82,23 +108,39 @@ public struct EditorView: View { // .clipped() } - TabView { - EnvironmentView(selectedAsset: $selectedAsset) - .tabItem { - Label("Environment", systemImage: "sun.max") - } - - PostProcessingEditorView() - .tabItem { - Label("Effects", systemImage: "cube") + VStack(spacing: 8) { + Picker("", selection: $inspectorTab) { + ForEach(InspectorTab.allCases, id: \.self) { tab in + Text(tab.rawValue).tag(tab) } - - InspectorView(selectionManager: selectionManager, sceneGraphModel: sceneGraphModel, onAddName_Editor: editor_addName, selectedAsset: $selectedAsset) - .tabItem { - Label("Inspector", systemImage: "cube") + } + .pickerStyle(.segmented) + .padding(.horizontal, 4) + + Divider() + + Group { + switch inspectorTab { + case .inspector: + InspectorView( + selectionManager: selectionManager, + sceneGraphModel: sceneGraphModel, + onAddName_Editor: editor_addName, + selectedAsset: $selectedAsset + ) + case .environment: + ScrollView { EnvironmentView(selectedAsset: $selectedAsset) } + case .effects: + ScrollView { PostProcessingEditorView() } } + } + } + .onChange(of: selectionManager.selectedEntity) { _, newValue in + if let entity = newValue, entity != .invalid { + inspectorTab = .inspector + } } - .frame(minWidth: 200, maxWidth: 250) + .frame(minWidth: 240, maxWidth: 320) } } .background( @@ -106,11 +148,125 @@ public struct EditorView: View { .onAppear { sceneGraphModel.refreshHierarchy() } + .sheet(isPresented: $showSaveNamePrompt) { + VStack(spacing: 12) { + Text("Save Scene") + .font(.headline) + Text("Scenes are saved to the Scenes folder in your Asset Folder.") + .font(.caption) + .foregroundColor(.secondary) + + TextField("Scene name", text: $pendingSceneName) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .onSubmit { confirmSaveSceneName() } + + HStack { + Button("Cancel") { showSaveNamePrompt = false } + Spacer() + Button("Save") { confirmSaveSceneName() } + .keyboardShortcut(.defaultAction) + .disabled(pendingSceneName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + } + } + .padding() + .frame(width: 320) + } + .alert("Overwrite Scene?", isPresented: $showOverwriteAlert) { + Button("Cancel", role: .cancel) { + showSaveNamePrompt = false + } + Button("Overwrite", role: .destructive) { + finalizeSceneSave(targetURL: pendingTargetURL, overwrite: true) + } + } message: { + Text("A scene with that name already exists. Overwrite it?") + } + .alert("Set Asset Folder First", isPresented: $showSaveBasePathAlert) { + Button("OK", role: .cancel) {} + } message: { + Text("Please set the Asset Folder in the Asset Browser before saving scenes.") + } } private func editor_handleSave() { + guard assetBasePath != nil else { + showSaveBasePathAlert = true + return + } + // If we have a current scene path, save immediately + if let sceneURL = editorController?.currentSceneURL { + let sceneData: SceneData = serializeScene() + saveSceneDirect(sceneData: sceneData, to: sceneURL) + return + } + + // Otherwise prompt for a name + isSaveAs = false + pendingSceneName = "untitled" + showSaveNamePrompt = true + } + + private func editor_handleSaveAs() { + guard assetBasePath != nil else { + showSaveBasePathAlert = true + return + } + isSaveAs = true + if let current = editorController?.currentSceneURL { + pendingSceneName = current.deletingPathExtension().lastPathComponent + } else { + pendingSceneName = "untitled" + } + showSaveNamePrompt = true + } + + private func confirmSaveSceneName() { + let name = pendingSceneName.trimmingCharacters(in: .whitespacesAndNewlines) + guard name.isEmpty == false else { return } + + guard let basePath = assetBasePath else { + showSaveNamePrompt = false + print("❌ Cannot save scene: Asset Folder not set.") + return + } + + let scenesFolder = basePath.appendingPathComponent("Scenes", isDirectory: true) + try? FileManager.default.createDirectory(at: scenesFolder, withIntermediateDirectories: true) + + let targetURL = scenesFolder.appendingPathComponent(name).appendingPathExtension("json") + pendingTargetURL = targetURL + + if FileManager.default.fileExists(atPath: targetURL.path) { + showOverwriteAlert = true + return + } + + finalizeSceneSave(targetURL: targetURL, overwrite: false) + } + + private func finalizeSceneSave(targetURL: URL? = nil, overwrite: Bool = false) { let sceneData: SceneData = serializeScene() - saveScene(sceneData: sceneData) + + let destinationURL: URL + if let targetURL { + destinationURL = targetURL + } else if let existing = editorController?.currentSceneURL { + destinationURL = existing + } else { + showSaveNamePrompt = true + return + } + + if FileManager.default.fileExists(atPath: destinationURL.path) && !overwrite { + showOverwriteAlert = true + return + } + + saveSceneDirect(sceneData: sceneData, to: destinationURL) + editorController?.currentSceneURL = destinationURL + showSaveNamePrompt = false + showOverwriteAlert = false + isSaveAs = false } private func editor_handleLoad() { @@ -133,6 +289,7 @@ public struct EditorView: View { removeGizmo() EditorComponentsState.shared.clear() deserializeScene(sceneData: sceneData) + editorController?.currentSceneURL = nil editor_entities = getAllGameEntities() selectionManager.selectedEntity = nil activeEntity = .invalid diff --git a/Sources/UntoldEditor/Editor/SceneHierarchyView.swift b/Sources/UntoldEditor/Editor/SceneHierarchyView.swift index b3e3d19..4120b22 100644 --- a/Sources/UntoldEditor/Editor/SceneHierarchyView.swift +++ b/Sources/UntoldEditor/Editor/SceneHierarchyView.swift @@ -15,6 +15,13 @@ struct SceneHierarchyView: View { var entityList: [EntityID] var onAddEntity_Editor: () -> Void var onRemoveEntity_Editor: () -> Void + var onAddCube: () -> Void + var onAddSphere: () -> Void + var onAddPlane: () -> Void + var onAddDirLight: () -> Void + var onAddPointLight: () -> Void + var onAddSpotLight: () -> Void + var onAddAreaLight: () -> Void var body: some View { VStack(alignment: .leading, spacing: 8) { @@ -30,13 +37,28 @@ struct SceneHierarchyView: View { Spacer() - // Add Entity Button - Button(action: onAddEntity_Editor) { - Image(systemName: "plus.circle.fill") - .foregroundColor(.blue) - .font(.system(size: 18)) + // Add Entity Menu + Menu { + Button("Empty Entity", systemImage: "plus") { onAddEntity_Editor() } + Divider() + Button("Cube", systemImage: "cube.fill") { onAddCube() } + Button("Sphere", systemImage: "circle.fill") { onAddSphere() } + Button("Plane", systemImage: "rectangle.fill") { onAddPlane() } + Divider() + Button("Directional Light", systemImage: "sun.max") { onAddDirLight() } + Button("Point Light", systemImage: "lightbulb") { onAddPointLight() } + Button("Spot Light", systemImage: "flashlight.on.fill") { onAddSpotLight() } + Button("Area Light", systemImage: "square") { onAddAreaLight() } + } label: { + Image(systemName: "plus") + .foregroundColor(.white) + .font(.system(size: 14, weight: .bold)) + .padding(8) + .background(Color.blue) + .clipShape(Circle()) } - .buttonStyle(PlainButtonStyle()) + .menuStyle(.borderlessButton) + .menuIndicator(.hidden) .help("Add Entity") // Remove Entity Button diff --git a/Sources/UntoldEditor/Editor/ToolbarView.swift b/Sources/UntoldEditor/Editor/ToolbarView.swift index 7d2ff24..9428338 100644 --- a/Sources/UntoldEditor/Editor/ToolbarView.swift +++ b/Sources/UntoldEditor/Editor/ToolbarView.swift @@ -16,7 +16,7 @@ @ObservedObject var selectionManager: SelectionManager var onSave: () -> Void - var onLoad: () -> Void + var onSaveAs: () -> Void var onClear: () -> Void var onPlayToggled: (Bool) -> Void var dirLightCreate: () -> Void @@ -98,9 +98,16 @@ var centeredButtons: some View { HStack(spacing: 12) { - ToolbarButton(iconName: "clear.fill", action: onClear, tooltip: "Clear Scene") - - ToolbarButton(iconName: "square.and.arrow.down", action: onLoad, tooltip: "Import JSON Scene") + Button(action: onClear) { + Text("Clear") + .font(.system(size: 12, weight: .semibold)) + .foregroundColor(.white) + .padding(.vertical, 6) + .padding(.horizontal, 12) + .background(Color.red.opacity(0.8)) + .cornerRadius(6) + } + .buttonStyle(.plain) Button(action: { isPlaying.toggle() @@ -118,50 +125,26 @@ } .buttonStyle(.plain) - ToolbarButton(iconName: "square.and.arrow.up", action: onSave, tooltip: "Export JSON Scene") + Menu { + Button("Save", systemImage: "square.and.arrow.down.on.square", action: onSave) + Button("Save As…", systemImage: "square.and.arrow.down", action: onSaveAs) + } label: { + HStack(spacing: 6) { + Image(systemName: "square.and.arrow.down.on.square") + Text("Save") + } + .padding(.vertical, 6) + .padding(.horizontal, 10) + .background(Color.gray.opacity(0.8)) + .foregroundColor(.white) + .cornerRadius(8) + } + .menuStyle(.borderlessButton) } } var leftSection: some View { HStack(spacing: 8) { - // Primitives Section - Text("Primitives:") - .font(.system(size: 11)) - .foregroundColor(.secondary) - - Button(action: onCreateCube) { - Image(systemName: "cube.fill") - .font(.system(size: 14)) - .foregroundColor(.white) - } - .padding(2) - .background(Color.gray.opacity(0.8)) - .cornerRadius(6) - .buttonStyle(.plain) - .help("Add Cube") - - Button(action: onCreateSphere) { - Image(systemName: "circle.fill") - .font(.system(size: 14)) - .foregroundColor(.white) - } - .padding(2) - .background(Color.gray.opacity(0.8)) - .cornerRadius(6) - .buttonStyle(.plain) - .help("Add Sphere") - - Button(action: onCreatePlane) { - Image(systemName: "rectangle.fill") - .font(.system(size: 14)) - .foregroundColor(.white) - } - .padding(2) - .background(Color.gray.opacity(0.8)) - .cornerRadius(6) - .buttonStyle(.plain) - .help("Add Plane") - // Button(action: onCreateCylinder) { // Image(systemName: "cylinder.fill") // .font(.system(size: 14)) @@ -186,63 +169,24 @@ Divider().frame(height: 24) - Text("Scripts:") - .font(.system(size: 11)) - .foregroundColor(.secondary) - - // New Script - Button(action: { - guard ensureAssetBasePath() else { return } - showingNewScriptDialog = true - }) { - HStack(spacing: 4) { - Image(systemName: "plus.circle.fill") - Text("New") - } - .font(.system(size: 12)) - .foregroundColor(.white) - .padding(.vertical, 4) - .padding(.horizontal, 8) - .background(Color.green) - .cornerRadius(5) - } - .buttonStyle(.plain) - - // Open in Xcode - Button(action: { - guard ensureAssetBasePath() else { return } - openInXcode() - }) { - HStack(spacing: 4) { - Image(systemName: "hammer.circle.fill") - Text("Open in Xcode") - } - .font(.system(size: 12)) - .foregroundColor(.white) - .padding(.vertical, 4) - .padding(.horizontal, 8) - .background(Color.blue) - .cornerRadius(5) - } - .buttonStyle(.plain) - - // Open in-app script editor + // Scripts cluster: primary editor button + overflow menu Button(action: { guard ensureAssetBasePath() else { return } toggleScriptEditorWindow() }) { - HStack(spacing: 4) { + HStack(spacing: 6) { Image(systemName: "chevron.left.forwardslash.chevron.right") Text("Script Editor") } - .font(.system(size: 12)) + .font(.system(size: 12, weight: .semibold)) .foregroundColor(.white) - .padding(.vertical, 4) - .padding(.horizontal, 8) - .background(Color.gray.opacity(0.9)) - .cornerRadius(5) + .padding(.vertical, 6) + .padding(.horizontal, 10) + .background(Color.blue) + .cornerRadius(8) } .buttonStyle(.plain) + } } From 4cc671437a81bb323c18a9be78342ff4d73af23d Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Mon, 15 Dec 2025 22:50:12 -0700 Subject: [PATCH 28/41] [Patch] Fixed the asset browser and console log view --- .../Editor/AssetBrowserView.swift | 16 ++-- Sources/UntoldEditor/Editor/EditorView.swift | 41 +++++---- .../UntoldEditor/Editor/LogConsoleView.swift | 86 ++++++------------- 3 files changed, 64 insertions(+), 79 deletions(-) diff --git a/Sources/UntoldEditor/Editor/AssetBrowserView.swift b/Sources/UntoldEditor/Editor/AssetBrowserView.swift index f18f9e1..a7402f7 100644 --- a/Sources/UntoldEditor/Editor/AssetBrowserView.swift +++ b/Sources/UntoldEditor/Editor/AssetBrowserView.swift @@ -56,6 +56,7 @@ struct AssetBrowserView: View { @State private var searchQuery: String = "" @State private var statusMessage: String? @State private var statusIsError = false + @State private var compactMode = false var editor_addEntityWithAsset: () -> Void private var currentFolderPath: URL? { folderPathStack.last @@ -150,6 +151,11 @@ struct AssetBrowserView: View { Image(systemName: "magnifyingglass") TextField("Filter assets", text: $searchQuery) .textFieldStyle(RoundedBorderTextFieldStyle()) + Spacer() + Toggle("Compact", isOn: $compactMode) + .toggleStyle(.switch) + .labelsHidden() + .help("Toggle compact list spacing") } .padding(.horizontal, 10) .padding(.bottom, 4) @@ -160,7 +166,7 @@ struct AssetBrowserView: View { // MARK: - Sidebar ScrollView(.vertical, showsIndicators: false) { - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: compactMode ? 4 : 8) { ForEach(AssetCategory.allCases, id: \.self) { category in HStack { Image(systemName: selectedCategory == category.rawValue ? "folder.fill" : "folder") @@ -169,7 +175,7 @@ struct AssetBrowserView: View { .font(.system(size: 14, weight: .bold, design: .monospaced)) .foregroundColor(selectedCategory == category.rawValue ? .blue : .primary) } - .padding(.vertical, 6) + .padding(.vertical, compactMode ? 4 : 6) .padding(.horizontal, 8) .background(selectedCategory == category.rawValue ? Color.blue.opacity(0.1) : Color.clear) .cornerRadius(6) @@ -189,14 +195,14 @@ struct AssetBrowserView: View { .padding(8) } - .frame(width: 120) + .frame(width: compactMode ? 110 : 140) .background(Color.secondary.opacity(0.05)) .cornerRadius(8) // MARK: - Asset List ScrollView(.vertical, showsIndicators: true) { - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: compactMode ? 6 : 8) { if let selectedCategory { // Scripts: flat, no breadcrumbs or folders let isScripts = (selectedCategory == AssetCategory.scripts.rawValue) @@ -264,7 +270,7 @@ struct AssetBrowserView: View { } .padding(10) } - .frame(maxHeight: 200) + .frame(maxHeight: .infinity) .onAppear(perform: loadAssets) .onChange(of: editorBaseAssetPath.basePath) { loadAssets() diff --git a/Sources/UntoldEditor/Editor/EditorView.swift b/Sources/UntoldEditor/Editor/EditorView.swift index fe4e8f5..b9fbbae 100644 --- a/Sources/UntoldEditor/Editor/EditorView.swift +++ b/Sources/UntoldEditor/Editor/EditorView.swift @@ -30,6 +30,7 @@ public struct EditorView: View { @State private var pendingTargetURL: URL? @State private var isSaveAs = false @State private var showSaveBasePathAlert = false + @State private var assetPaneWidth: Double = 400 var renderer: UntoldRenderer? @@ -90,22 +91,32 @@ public struct EditorView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) TransformManipulationToolbar(controller: editorController!) .frame(height: 40) - HStack(spacing: 0) { - AssetBrowserView( - assets: $assets, - selectedAsset: $selectedAsset, - selectionManager: selectionManager, - sceneGraphModel: sceneGraphModel, - editor_addEntityWithAsset: editor_addEntityWithAsset - ) - .frame(width: 400) - // .tabItem { Label("Assets", systemImage: "shippingbox") } - Divider() - LogConsoleView() - .tabItem { Label("Console", systemImage: "terminal") } + GeometryReader { geo in + HStack(alignment: .top, spacing: 0) { + AssetBrowserView( + assets: $assets, + selectedAsset: $selectedAsset, + selectionManager: selectionManager, + sceneGraphModel: sceneGraphModel, + editor_addEntityWithAsset: editor_addEntityWithAsset + ) + .frame(width: max(240, min(Double(assetPaneWidth), geo.size.width - 200))) + + Divider() + .frame(width: 4) + .background(Color.secondary.opacity(0.2)) + .gesture( + DragGesture(minimumDistance: 1).onChanged { value in + let newWidth = assetPaneWidth + value.translation.width + assetPaneWidth = max(240, min(Double(newWidth), geo.size.width - 200)) + } + ) + + LogConsoleView() + .tabItem { Label("Console", systemImage: "terminal") } + } } - .frame(height: 200) - // .clipped() + .frame(height: 260) } VStack(spacing: 8) { diff --git a/Sources/UntoldEditor/Editor/LogConsoleView.swift b/Sources/UntoldEditor/Editor/LogConsoleView.swift index b172bec..69d5587 100644 --- a/Sources/UntoldEditor/Editor/LogConsoleView.swift +++ b/Sources/UntoldEditor/Editor/LogConsoleView.swift @@ -17,6 +17,7 @@ struct LogConsoleView: View { @State private var search = "" @State private var autoScroll = true @State private var clearLog = false + @State private var showControls = false private func passes(_ e: LogEvent) -> Bool { (selectedLevel == nil || e.level == selectedLevel!) && @@ -33,21 +34,34 @@ struct LogConsoleView: View { .bold() .foregroundColor(.primary) Spacer() - TextField("Search…", text: $search) - .textFieldStyle(.roundedBorder) - .frame(maxWidth: 200) + if showControls { + TextField("Search…", text: $search) + .textFieldStyle(.roundedBorder) + .frame(maxWidth: 200) + } - Toggle("Auto‑scroll", isOn: $autoScroll) - .toggleStyle(.checkbox) + if showControls { + Toggle("Auto‑scroll", isOn: $autoScroll) + .toggleStyle(.checkbox) + } - Toggle("Clear", isOn: $clearLog) - .toggleStyle(.checkbox) - .onChange(of: clearLog) { _, newValue in - if newValue { - LogStore.shared.clear() - clearLog = false - } + Button { + withAnimation(.easeInOut(duration: 0.15)) { + showControls.toggle() } + } label: { + Image(systemName: showControls ? "chevron.down" : "ellipsis.circle") + .foregroundColor(.primary) + } + .buttonStyle(.plain) + + Button(action: { + LogStore.shared.clear() + }) { + Image(systemName: "trash") + } + .buttonStyle(BorderlessButtonStyle()) + .help("Clear console") } .padding(.horizontal, 10) .padding(.vertical, 6) @@ -61,16 +75,6 @@ struct LogConsoleView: View { .font(.caption).foregroundColor(.secondary) .frame(width: 84, alignment: .leading) -// Text("[\(e.category)]") -// .font(.caption).foregroundColor(.secondary) -// .frame(width: 120, alignment: .leading) -// -// Text(tag(for: e.level)) -// .font(.caption2) -// .padding(.horizontal, 6).padding(.vertical, 2) -// .background(badgeColor(for: e.level).opacity(0.15)) -// .clipShape(RoundedRectangle(cornerRadius: 6)) - Text(e.message) .font(.system(.body, design: .monospaced)) .textSelection(.enabled) @@ -82,44 +86,8 @@ struct LogConsoleView: View { if autoScroll, let last { withAnimation { proxy.scrollTo(last, anchor: .bottom) } } } } - - /* - HStack { - Picker("Level", selection: $selectedLevel) { - Text("All").tag(LogLevel?.none) - Text("Error").tag(LogLevel?.some(.error)) - Text("Warning").tag(LogLevel?.some(.warning)) - Text("Info").tag(LogLevel?.some(.info)) - Text("Debug").tag(LogLevel?.some(.debug)) - Text("Test").tag(LogLevel?.some(.test)) - } - .pickerStyle(.segmented) - - //Disabling Buttons for now - Spacer() - - Button("Copy") { - let text = store.entries.filter(passes).map { - "[\($0.level)] \($0.message)" - }.joined(separator: "\n") - #if os(macOS) - NSPasteboard.general.clearContents() - NSPasteboard.general.setString(text, forType: .string) - #endif - } - - Button("Export") { - //exportLog(store.entries.filter(passes)) - } - - Button("Clear") { - // optional: expose a clear API on Logger/LogStore if you want - } - - } - .padding(.horizontal, 8) - */ } + .frame(minHeight: 140) .padding(10) } From ff3b35d53dead7a598ac8aa81ed7d55df0e6aad3 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Mon, 15 Dec 2025 23:26:15 -0700 Subject: [PATCH 29/41] [Patch] Fixed console and Asset browser. only one is visible at a time --- Sources/UntoldEditor/Editor/EditorView.swift | 33 ++++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/Sources/UntoldEditor/Editor/EditorView.swift b/Sources/UntoldEditor/Editor/EditorView.swift index b9fbbae..7348cbe 100644 --- a/Sources/UntoldEditor/Editor/EditorView.swift +++ b/Sources/UntoldEditor/Editor/EditorView.swift @@ -30,7 +30,8 @@ public struct EditorView: View { @State private var pendingTargetURL: URL? @State private var isSaveAs = false @State private var showSaveBasePathAlert = false - @State private var assetPaneWidth: Double = 400 + enum BottomPaneSelection: String, CaseIterable, Hashable { case assets = "Assets"; case console = "Console" } + @State private var bottomPaneSelection: BottomPaneSelection = .assets var renderer: UntoldRenderer? @@ -91,8 +92,16 @@ public struct EditorView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) TransformManipulationToolbar(controller: editorController!) .frame(height: 40) - GeometryReader { geo in - HStack(alignment: .top, spacing: 0) { + VStack(spacing: 6) { + Picker("", selection: $bottomPaneSelection) { + ForEach(BottomPaneSelection.allCases, id: \.self) { sel in + Text(sel.rawValue).tag(sel) + } + } + .pickerStyle(.segmented) + .padding(.horizontal, 8) + + if bottomPaneSelection == .assets { AssetBrowserView( assets: $assets, selectedAsset: $selectedAsset, @@ -100,23 +109,13 @@ public struct EditorView: View { sceneGraphModel: sceneGraphModel, editor_addEntityWithAsset: editor_addEntityWithAsset ) - .frame(width: max(240, min(Double(assetPaneWidth), geo.size.width - 200))) - - Divider() - .frame(width: 4) - .background(Color.secondary.opacity(0.2)) - .gesture( - DragGesture(minimumDistance: 1).onChanged { value in - let newWidth = assetPaneWidth + value.translation.width - assetPaneWidth = max(240, min(Double(newWidth), geo.size.width - 200)) - } - ) - + .frame(maxHeight: 260) + } else { LogConsoleView() - .tabItem { Label("Console", systemImage: "terminal") } + .frame(maxHeight: 260) } } - .frame(height: 260) + .frame(height: 300) } VStack(spacing: 8) { From f5b01dbdc57f35f3baf029d9f48d636cb6369da1 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Mon, 15 Dec 2025 23:47:29 -0700 Subject: [PATCH 30/41] [Patch] Improved the color scheme of the editor --- .../Editor/AssetBrowserView.swift | 28 ++- .../UntoldEditor/Editor/EditorScheme.swift | 8 +- Sources/UntoldEditor/Editor/EditorView.swift | 7 +- .../UntoldEditor/Editor/LogConsoleView.swift | 2 +- Sources/UntoldEditor/Editor/ToolbarView.swift | 193 +++++++++++++----- 5 files changed, 170 insertions(+), 68 deletions(-) diff --git a/Sources/UntoldEditor/Editor/AssetBrowserView.swift b/Sources/UntoldEditor/Editor/AssetBrowserView.swift index a7402f7..180b55b 100644 --- a/Sources/UntoldEditor/Editor/AssetBrowserView.swift +++ b/Sources/UntoldEditor/Editor/AssetBrowserView.swift @@ -73,7 +73,7 @@ struct AssetBrowserView: View { Text("Assets") .font(.title3) .bold() - .foregroundColor(.primary) + .foregroundColor(.white) Button(action: importAsset) { HStack(spacing: 6) { @@ -83,8 +83,8 @@ struct AssetBrowserView: View { } .padding(.vertical, 6) .padding(.horizontal, 12) - .background(Color.gray) - .foregroundColor(.white) + .background(Color.editorAccent) + .foregroundColor(.black.opacity(0.9)) .cornerRadius(8) .shadow(color: Color.black.opacity(0.2), radius: 4, x: 0, y: 2) } @@ -118,7 +118,7 @@ struct AssetBrowserView: View { } .padding(.vertical, 6) .padding(.horizontal, 12) - .background(Color.gray) + .background(Color.editorSurface) .foregroundColor(.white) .cornerRadius(8) .shadow(color: Color.black.opacity(0.2), radius: 4, x: 0, y: 2) @@ -127,7 +127,7 @@ struct AssetBrowserView: View { } .padding(.horizontal, 10) .padding(.vertical, 6) - .background(Color.secondary.opacity(0.1)) + .background(Color.editorPanelBackground.opacity(0.9)) .cornerRadius(8) // MARK: - Path Indicator @@ -135,7 +135,7 @@ struct AssetBrowserView: View { if let resourceDir = editorBaseAssetPath.basePath { Text("Current Path: \(resourceDir.lastPathComponent)") .font(.caption) - .foregroundColor(.green) + .foregroundColor(Color.editorAccent) .padding(.horizontal, 10) .padding(.bottom, 5) } else { @@ -167,17 +167,23 @@ struct AssetBrowserView: View { ScrollView(.vertical, showsIndicators: false) { VStack(alignment: .leading, spacing: compactMode ? 4 : 8) { + Text("Categories") + .font(.caption) + .foregroundColor(.secondary) + .padding(.horizontal, 8) + .padding(.bottom, 2) + ForEach(AssetCategory.allCases, id: \.self) { category in HStack { Image(systemName: selectedCategory == category.rawValue ? "folder.fill" : "folder") - .foregroundColor(selectedCategory == category.rawValue ? .blue : .gray) + .foregroundColor(selectedCategory == category.rawValue ? Color.editorAccent : .gray) Text(category.rawValue) .font(.system(size: 14, weight: .bold, design: .monospaced)) - .foregroundColor(selectedCategory == category.rawValue ? .blue : .primary) + .foregroundColor(selectedCategory == category.rawValue ? .white : .primary) } .padding(.vertical, compactMode ? 4 : 6) .padding(.horizontal, 8) - .background(selectedCategory == category.rawValue ? Color.blue.opacity(0.1) : Color.clear) + .background(selectedCategory == category.rawValue ? Color.editorAccentSoft : Color.clear) .cornerRadius(6) .onTapGesture { // If reselecting the same category, force a reload @@ -202,7 +208,7 @@ struct AssetBrowserView: View { // MARK: - Asset List ScrollView(.vertical, showsIndicators: true) { - VStack(alignment: .leading, spacing: compactMode ? 6 : 8) { + VStack(alignment: .leading, spacing: compactMode ? 6 : 10) { if let selectedCategory { // Scripts: flat, no breadcrumbs or folders let isScripts = (selectedCategory == AssetCategory.scripts.rawValue) @@ -263,7 +269,7 @@ struct AssetBrowserView: View { .padding(.horizontal, 8) } .frame(maxHeight: 300) - .background(Color.secondary.opacity(0.05)) + .background(Color.editorSurface.opacity(0.7)) .cornerRadius(8) } .frame(maxHeight: 300) diff --git a/Sources/UntoldEditor/Editor/EditorScheme.swift b/Sources/UntoldEditor/Editor/EditorScheme.swift index 4f40205..54b9794 100644 --- a/Sources/UntoldEditor/Editor/EditorScheme.swift +++ b/Sources/UntoldEditor/Editor/EditorScheme.swift @@ -9,5 +9,11 @@ import SwiftUI extension Color { - static let editorBackground = Color(red: 52 / 255, green: 64 / 255, blue: 68 / 255) + static let editorBackground = Color(red: 0.11, green: 0.12, blue: 0.14) // deep charcoal + static let editorPanelBackground = Color(red: 0.16, green: 0.18, blue: 0.21) // muted slate + static let editorSurface = Color(red: 0.20, green: 0.22, blue: 0.25) + static let editorAccent = Color(red: 0.17, green: 0.59, blue: 0.78) // teal/blue accent + static let editorAccentSoft = Color(red: 0.17, green: 0.59, blue: 0.78, opacity: 0.14) + static let editorSecondaryAccent = Color(red: 0.90, green: 0.64, blue: 0.24) // warm amber secondary + static let editorDivider = Color.white.opacity(0.06) } diff --git a/Sources/UntoldEditor/Editor/EditorView.swift b/Sources/UntoldEditor/Editor/EditorView.swift index 7348cbe..34240f9 100644 --- a/Sources/UntoldEditor/Editor/EditorView.swift +++ b/Sources/UntoldEditor/Editor/EditorView.swift @@ -154,7 +154,12 @@ public struct EditorView: View { } } .background( - Color.editorBackground.ignoresSafeArea()) + LinearGradient( + colors: [Color.editorBackground, Color.editorPanelBackground.opacity(0.95)], + startPoint: .top, + endPoint: .bottom + ) + .ignoresSafeArea()) .onAppear { sceneGraphModel.refreshHierarchy() } diff --git a/Sources/UntoldEditor/Editor/LogConsoleView.swift b/Sources/UntoldEditor/Editor/LogConsoleView.swift index 69d5587..67e9f44 100644 --- a/Sources/UntoldEditor/Editor/LogConsoleView.swift +++ b/Sources/UntoldEditor/Editor/LogConsoleView.swift @@ -65,7 +65,7 @@ struct LogConsoleView: View { } .padding(.horizontal, 10) .padding(.vertical, 6) - .background(Color.secondary.opacity(0.1)) + .background(Color.editorPanelBackground.opacity(0.8)) .cornerRadius(8) ScrollViewReader { proxy in diff --git a/Sources/UntoldEditor/Editor/ToolbarView.swift b/Sources/UntoldEditor/Editor/ToolbarView.swift index 9428338..5244bd5 100644 --- a/Sources/UntoldEditor/Editor/ToolbarView.swift +++ b/Sources/UntoldEditor/Editor/ToolbarView.swift @@ -50,11 +50,15 @@ .padding(.horizontal, 20) .padding(.vertical, 6) .background( - Color.secondary.opacity(0.1) - .ignoresSafeArea() + LinearGradient( + colors: [Color.editorPanelBackground.opacity(0.95), Color.editorPanelBackground.opacity(0.85)], + startPoint: .top, + endPoint: .bottom + ) + .ignoresSafeArea() ) .cornerRadius(8) - .shadow(color: Color.black.opacity(0.05), radius: 3, x: 0, y: 1) + .shadow(color: Color.black.opacity(0.08), radius: 4, x: 0, y: 2) .sheet(isPresented: $showBuildSettings) { BuildSettingsView() } @@ -86,8 +90,8 @@ } .padding(.vertical, 6) .padding(.horizontal, 12) - .background(Color.green) - .foregroundColor(.white) + .background(Color.editorAccent) + .foregroundColor(.black.opacity(0.9)) .cornerRadius(6) } .buttonStyle(.plain) @@ -98,16 +102,7 @@ var centeredButtons: some View { HStack(spacing: 12) { - Button(action: onClear) { - Text("Clear") - .font(.system(size: 12, weight: .semibold)) - .foregroundColor(.white) - .padding(.vertical, 6) - .padding(.horizontal, 12) - .background(Color.red.opacity(0.8)) - .cornerRadius(6) - } - .buttonStyle(.plain) + ToolbarButton(iconName: "gobackward", action: onClear, tooltip: "Clear Scene") Button(action: { isPlaying.toggle() @@ -179,10 +174,10 @@ Text("Script Editor") } .font(.system(size: 12, weight: .semibold)) - .foregroundColor(.white) + .foregroundColor(.black.opacity(0.9)) .padding(.vertical, 6) .padding(.horizontal, 10) - .background(Color.blue) + .background(Color.editorAccent) .cornerRadius(8) } .buttonStyle(.plain) @@ -384,6 +379,8 @@ buildLog } .frame(minWidth: 600, idealWidth: 1100, minHeight: 500, idealHeight: 800) + .background(Color.editorBackground) + .accentColor(Color.editorAccent) .onAppear { prepareScripts() installControlCopyPasteShortcuts() @@ -442,12 +439,6 @@ .buttonStyle(.borderedProminent) .disabled(isBuilding) - Button("New Script") { - showingNewScriptDialogInSheet = true - } - .buttonStyle(.borderedProminent) - .tint(.green) - Button("Save") { saveCurrentScript() } @@ -463,26 +454,32 @@ // } // .disabled(undoStack.isEmpty) - Button("Delete") { - showDeleteConfirm = true - } - .disabled(selectedFile == nil || isProtectedFile(selectedFile)) - .buttonStyle(.borderedProminent) - .tint(.red) - .confirmationDialog( - "Delete script?", - isPresented: $showDeleteConfirm, - titleVisibility: .visible - ) { - Button("Delete", role: .destructive) { - deleteSelectedScript() - } - Button("Cancel", role: .cancel) { showDeleteConfirm = false } - } message: { - if let selectedFile { - Text("Are you sure you want to delete \(selectedFile.lastPathComponent)? This cannot be undone.") - } - } +// Button("New Script") { +// showingNewScriptDialogInSheet = true +// } +// .buttonStyle(.borderedProminent) +// .tint(.green) + +// Button("Delete") { +// showDeleteConfirm = true +// } +// .disabled(selectedFile == nil || isProtectedFile(selectedFile)) +// .buttonStyle(.borderedProminent) +// .tint(.red) +// .confirmationDialog( +// "Delete script?", +// isPresented: $showDeleteConfirm, +// titleVisibility: .visible +// ) { +// Button("Delete", role: .destructive) { +// deleteSelectedScript() +// } +// Button("Cancel", role: .cancel) { showDeleteConfirm = false } +// } message: { +// if let selectedFile { +// Text("Are you sure you want to delete \(selectedFile.lastPathComponent)? This cannot be undone.") +// } +// } Button("Close") { if isDirty { @@ -497,13 +494,64 @@ } private var scriptList: some View { - List(selection: $selectedFile) { - ForEach(scriptFiles, id: \.self) { url in - Text(url.lastPathComponent) - .lineLimit(1) - .tag(Optional(url)) + VStack(alignment: .leading, spacing: 0) { + HStack { + Text("Scripts") + .font(.headline) + .foregroundColor(.white) + Spacer() + Button { + showingNewScriptDialogInSheet = true + } label: { + Image(systemName: "plus.circle.fill") + .foregroundColor(Color.editorAccent) + } + .buttonStyle(.plain) + .help("New Script") + Button { + showDeleteConfirm = true + } label: { + Image(systemName: "trash.fill") + .foregroundColor(.red) + } + .buttonStyle(.plain) + .disabled(selectedFile == nil || isProtectedFile(selectedFile)) + .help("Delete Script") + .confirmationDialog( + "Delete script?", + isPresented: $showDeleteConfirm, + titleVisibility: .visible + ) { + Button("Delete", role: .destructive) { + deleteSelectedScript() + } + Button("Cancel", role: .cancel) { showDeleteConfirm = false } + } message: { + if let selectedFile { + Text("Are you sure you want to delete \(selectedFile.lastPathComponent)? This cannot be undone.") + } + } + } + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background(Color.editorSurface) + + Divider() + .background(Color.editorDivider) + + List(selection: $selectedFile) { + ForEach(scriptFiles, id: \.self) { url in + Text(url.lastPathComponent) + .lineLimit(1) + .foregroundColor(.primary) + .tag(Optional(url)) + } } + .listStyle(.plain) + .scrollContentBackground(.hidden) } + .background(Color.editorPanelBackground) + .cornerRadius(8) .frame(minWidth: 180, idealWidth: 220, maxWidth: 260, maxHeight: .infinity) .onChange(of: selectedFile) { newSelection in handleSelectionChange(newSelection) @@ -527,7 +575,7 @@ colorScheme == .dark ? Theme.defaultDark : Theme.defaultLight ) .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color(NSColor.textBackgroundColor)) + .background(Color.editorSurface) .cornerRadius(6) } else { VStack { @@ -551,16 +599,53 @@ ProgressView() .progressViewStyle(.circular) } + Spacer() + Button { + let output = buildOutput + #if os(macOS) + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(output, forType: .string) + #endif + } label: { + Image(systemName: "doc.on.doc") + } + .help("Copy build output") + .disabled(buildOutput.isEmpty) + + Button { + buildOutput = "" + } label: { + Image(systemName: "trash") + } + .help("Clear build output") + .disabled(buildOutput.isEmpty) } - ScrollView { - Text(buildOutput.isEmpty ? "No builds yet." : buildOutput) - .font(.system(.body, design: .monospaced)) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(4) + .padding(.horizontal, 4) + + ScrollViewReader { proxy in + ScrollView { + Text(buildOutput.isEmpty ? "No builds yet." : buildOutput) + .font(.system(.body, design: .monospaced)) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(4) + .id("buildOutputText") + .textSelection(.enabled) + } + .onChange(of: buildOutput) { _ in + withAnimation { + proxy.scrollTo("buildOutputText", anchor: .bottom) + } + } } .frame(maxWidth: .infinity) } .padding() + .background(Color.editorPanelBackground) + .cornerRadius(8) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Color.editorDivider, lineWidth: 1) + ) .frame(minHeight: 160, maxHeight: 200) } From b4b18e34c870bf7301bfb5993dbb42bee126cf6e Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 16 Dec 2025 06:21:07 -0700 Subject: [PATCH 31/41] [Patch] Made asset browser a window --- .../Editor/AssetWindowDelegate.swift | 13 +++ Sources/UntoldEditor/Editor/EditorView.swift | 106 +++++++++++++----- Sources/UntoldEditor/Editor/ToolbarView.swift | 14 +++ 3 files changed, 105 insertions(+), 28 deletions(-) create mode 100644 Sources/UntoldEditor/Editor/AssetWindowDelegate.swift diff --git a/Sources/UntoldEditor/Editor/AssetWindowDelegate.swift b/Sources/UntoldEditor/Editor/AssetWindowDelegate.swift new file mode 100644 index 0000000..d5157d6 --- /dev/null +++ b/Sources/UntoldEditor/Editor/AssetWindowDelegate.swift @@ -0,0 +1,13 @@ +import AppKit + +final class AssetWindowDelegate: NSObject, NSWindowDelegate { + private let onClose: () -> Void + + init(onClose: @escaping () -> Void) { + self.onClose = onClose + } + + func windowWillClose(_ notification: Notification) { + onClose() + } +} diff --git a/Sources/UntoldEditor/Editor/EditorView.swift b/Sources/UntoldEditor/Editor/EditorView.swift index 34240f9..4ec6ddf 100644 --- a/Sources/UntoldEditor/Editor/EditorView.swift +++ b/Sources/UntoldEditor/Editor/EditorView.swift @@ -30,8 +30,9 @@ public struct EditorView: View { @State private var pendingTargetURL: URL? @State private var isSaveAs = false @State private var showSaveBasePathAlert = false - enum BottomPaneSelection: String, CaseIterable, Hashable { case assets = "Assets"; case console = "Console" } - @State private var bottomPaneSelection: BottomPaneSelection = .assets + @State private var showAssetLibrary = false + @State private var assetWindow: NSWindow? + @State private var assetWindowDelegate: AssetWindowDelegate? var renderer: UntoldRenderer? @@ -55,9 +56,16 @@ public struct EditorView: View { public var body: some View { VStack { ToolbarView( - selectionManager: selectionManager, onSave: editor_handleSave, onSaveAs: editor_handleSaveAs, + selectionManager: selectionManager, + onSave: editor_handleSave, + onSaveAs: editor_handleSaveAs, onClear: editor_clearScene, - onPlayToggled: { isPlaying in editor_handlePlayToggle(isPlaying) }, + onPlayToggled: { isPlaying in + editor_handlePlayToggle(isPlaying) + }, + onShowAssets: { + openAssetWindow() + }, dirLightCreate: editor_createDirLight, pointLightCreate: editor_createPointLight, spotLightCreate: editor_createSpotLight, @@ -68,6 +76,31 @@ public struct EditorView: View { onCreateCylinder: editor_createCylinder, onCreateCone: editor_createCone ) + .popover(isPresented: $showAssetLibrary, arrowEdge: .bottom) { + VStack(spacing: 0) { + HStack { + Text("Assets Library") + .font(.headline) + Spacer() + Button("Close") { showAssetLibrary = false } + .keyboardShortcut(.cancelAction) + } + .padding() + .background(Color.editorPanelBackground.opacity(0.9)) + + Divider() + + AssetBrowserView( + assets: $assets, + selectedAsset: $selectedAsset, + selectionManager: selectionManager, + sceneGraphModel: sceneGraphModel, + editor_addEntityWithAsset: editor_addEntityWithAsset + ) + .frame(minWidth: 700, minHeight: 500) + } + .frame(minWidth: 720, minHeight: 540) + } Divider() HStack { VStack { @@ -92,30 +125,8 @@ public struct EditorView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) TransformManipulationToolbar(controller: editorController!) .frame(height: 40) - VStack(spacing: 6) { - Picker("", selection: $bottomPaneSelection) { - ForEach(BottomPaneSelection.allCases, id: \.self) { sel in - Text(sel.rawValue).tag(sel) - } - } - .pickerStyle(.segmented) - .padding(.horizontal, 8) - - if bottomPaneSelection == .assets { - AssetBrowserView( - assets: $assets, - selectedAsset: $selectedAsset, - selectionManager: selectionManager, - sceneGraphModel: sceneGraphModel, - editor_addEntityWithAsset: editor_addEntityWithAsset - ) - .frame(maxHeight: 260) - } else { - LogConsoleView() - .frame(maxHeight: 260) - } - } - .frame(height: 300) + LogConsoleView() + .frame(height: 260) } VStack(spacing: 8) { @@ -284,6 +295,45 @@ public struct EditorView: View { isSaveAs = false } + // MARK: - Asset Library Window + + private func openAssetWindow() { + if let existing = assetWindow { + existing.makeKeyAndOrderFront(nil) + NSApp.activate(ignoringOtherApps: true) + return + } + + let content = AssetBrowserView( + assets: $assets, + selectedAsset: $selectedAsset, + selectionManager: selectionManager, + sceneGraphModel: sceneGraphModel, + editor_addEntityWithAsset: editor_addEntityWithAsset + ) + + let hosting = NSHostingController(rootView: content) + let window = NSWindow(contentViewController: hosting) + window.title = "Assets Library" + window.styleMask = [.titled, .closable, .resizable, .miniaturizable] + window.setContentSize(NSSize(width: 900, height: 600)) + window.minSize = NSSize(width: 700, height: 500) + window.isReleasedWhenClosed = false + window.center() + window.makeKeyAndOrderFront(nil) + NSApp.activate(ignoringOtherApps: true) + + let delegate = AssetWindowDelegate { + // Window is closing — clear our references + self.assetWindow = nil + self.assetWindowDelegate = nil + } + window.delegate = delegate + assetWindowDelegate = delegate + + assetWindow = window + } + private func editor_handleLoad() { var sceneData: SceneData? diff --git a/Sources/UntoldEditor/Editor/ToolbarView.swift b/Sources/UntoldEditor/Editor/ToolbarView.swift index 5244bd5..807cb57 100644 --- a/Sources/UntoldEditor/Editor/ToolbarView.swift +++ b/Sources/UntoldEditor/Editor/ToolbarView.swift @@ -19,6 +19,7 @@ var onSaveAs: () -> Void var onClear: () -> Void var onPlayToggled: (Bool) -> Void + var onShowAssets: () -> Void var dirLightCreate: () -> Void var pointLightCreate: () -> Void var spotLightCreate: () -> Void @@ -83,6 +84,19 @@ var rightSection: some View { HStack(spacing: 12) { + Button(action: onShowAssets) { + HStack(spacing: 6) { + Image(systemName: "shippingbox.fill") + Text("Assets Library") + } + .padding(.vertical, 6) + .padding(.horizontal, 10) + .background(Color.editorSurface) + .foregroundColor(.white) + .cornerRadius(8) + } + .buttonStyle(.plain) + Button(action: { showBuildSettings = true }) { HStack(spacing: 6) { Image(systemName: "hammer.fill") From 11c619e1830dc1a9c08cfc20bb05c2d316419364 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 16 Dec 2025 06:57:29 -0700 Subject: [PATCH 32/41] [Patch] Improved the console log --- .../UntoldEditor/Editor/LogConsoleView.swift | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/Sources/UntoldEditor/Editor/LogConsoleView.swift b/Sources/UntoldEditor/Editor/LogConsoleView.swift index 67e9f44..202ca8c 100644 --- a/Sources/UntoldEditor/Editor/LogConsoleView.swift +++ b/Sources/UntoldEditor/Editor/LogConsoleView.swift @@ -17,7 +17,6 @@ struct LogConsoleView: View { @State private var search = "" @State private var autoScroll = true @State private var clearLog = false - @State private var showControls = false private func passes(_ e: LogEvent) -> Bool { (selectedLevel == nil || e.level == selectedLevel!) && @@ -33,27 +32,25 @@ struct LogConsoleView: View { .font(.title3) .bold() .foregroundColor(.primary) +// Spacer().frame(width: 16) +// Picker("Level", selection: $selectedLevel) { +// Text("All").tag(LogLevel?.none) +// Text("Error").tag(LogLevel?.some(.error)) +// Text("Warning").tag(LogLevel?.some(.warning)) +// Text("Info").tag(LogLevel?.some(.info)) +// Text("Debug").tag(LogLevel?.some(.debug)) +// Text("Test").tag(LogLevel?.some(.test)) +// } +// .pickerStyle(.segmented) +// .frame(width: 360) +// .accentColor(.gray) Spacer() - if showControls { - TextField("Search…", text: $search) - .textFieldStyle(.roundedBorder) - .frame(maxWidth: 200) - } - - if showControls { - Toggle("Auto‑scroll", isOn: $autoScroll) - .toggleStyle(.checkbox) - } + TextField("Search…", text: $search) + .textFieldStyle(.roundedBorder) + .frame(maxWidth: 200) - Button { - withAnimation(.easeInOut(duration: 0.15)) { - showControls.toggle() - } - } label: { - Image(systemName: showControls ? "chevron.down" : "ellipsis.circle") - .foregroundColor(.primary) - } - .buttonStyle(.plain) + Toggle("Auto‑scroll", isOn: $autoScroll) + .toggleStyle(.checkbox) Button(action: { LogStore.shared.clear() @@ -77,6 +74,7 @@ struct LogConsoleView: View { Text(e.message) .font(.system(.body, design: .monospaced)) + .foregroundColor(colorForLevel(e.level)) .textSelection(.enabled) .lineLimit(4) } @@ -97,6 +95,17 @@ struct LogConsoleView: View { return f.string(from: d) } + private func colorForLevel(_ level: LogLevel) -> Color { + switch level { + case .error: return .red + case .warning: return .yellow + case .info: return .primary + case .debug: return .gray + case .test: return Color.editorAccent + case .none: return .primary + } + } + private func tag(for level: LogLevel) -> String { switch level { case .error: return "ERROR" From ea799410cd60d3cfcfc416a0fedf6b4360c9e952 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 16 Dec 2025 08:20:44 -0700 Subject: [PATCH 33/41] [Patch] Make the asset library view permanent when clicking on viewport --- Sources/UntoldEditor/Editor/EditorView.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/UntoldEditor/Editor/EditorView.swift b/Sources/UntoldEditor/Editor/EditorView.swift index 4ec6ddf..4293dfc 100644 --- a/Sources/UntoldEditor/Editor/EditorView.swift +++ b/Sources/UntoldEditor/Editor/EditorView.swift @@ -316,8 +316,9 @@ public struct EditorView: View { let window = NSWindow(contentViewController: hosting) window.title = "Assets Library" window.styleMask = [.titled, .closable, .resizable, .miniaturizable] - window.setContentSize(NSSize(width: 900, height: 600)) - window.minSize = NSSize(width: 700, height: 500) + window.setContentSize(NSSize(width: 760, height: 520)) + window.minSize = NSSize(width: 620, height: 420) + window.level = .floating // keep above editor while arranging assets window.isReleasedWhenClosed = false window.center() window.makeKeyAndOrderFront(nil) From 674e3d499989ad0ad19d6294baa9a66fccd79bb8 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 16 Dec 2025 08:21:36 -0700 Subject: [PATCH 34/41] [Patch] Ignore destroy-marked entities during ray casting. --- Sources/UntoldEditor/Systems/RayModelIntersecions.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/UntoldEditor/Systems/RayModelIntersecions.swift b/Sources/UntoldEditor/Systems/RayModelIntersecions.swift index 0205b3a..181d90a 100644 --- a/Sources/UntoldEditor/Systems/RayModelIntersecions.swift +++ b/Sources/UntoldEditor/Systems/RayModelIntersecions.swift @@ -91,6 +91,9 @@ func createAccelerationStructures(_: Bool) { // Iterate over the entities found by the component query for (i, entityId) in entities.enumerated() { + // Skip entities pending destroy + if scene.mask(for: entityId) == nil { continue } + guard let renderComponent = scene.get(component: RenderComponent.self, for: entityId) else { handleError(.noRenderComponent, entityId) continue From 0b11d9df1943691c3b35bbab5aec0002a2d2baa1 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 16 Dec 2025 08:41:40 -0700 Subject: [PATCH 35/41] [Patch] Improved console log ux design --- Sources/UntoldEditor/Editor/EditorView.swift | 1 + Sources/UntoldEditor/Editor/LogConsoleView.swift | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/Sources/UntoldEditor/Editor/EditorView.swift b/Sources/UntoldEditor/Editor/EditorView.swift index 4293dfc..dbc05da 100644 --- a/Sources/UntoldEditor/Editor/EditorView.swift +++ b/Sources/UntoldEditor/Editor/EditorView.swift @@ -320,6 +320,7 @@ public struct EditorView: View { window.minSize = NSSize(width: 620, height: 420) window.level = .floating // keep above editor while arranging assets window.isReleasedWhenClosed = false + window.alphaValue = 0.9 window.center() window.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) diff --git a/Sources/UntoldEditor/Editor/LogConsoleView.swift b/Sources/UntoldEditor/Editor/LogConsoleView.swift index 202ca8c..f9c49dd 100644 --- a/Sources/UntoldEditor/Editor/LogConsoleView.swift +++ b/Sources/UntoldEditor/Editor/LogConsoleView.swift @@ -80,6 +80,17 @@ struct LogConsoleView: View { } .id(e.id) } + .listStyle(.plain) + .scrollContentBackground(.hidden) + .listRowBackground(Color.clear) + .background( + RoundedRectangle(cornerRadius: 10) + .fill(Color.editorSurface.opacity(0.9)) + ) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(Color.editorDivider, lineWidth: 1) + ) .onChange(of: store.entries.last?.id) { _, last in if autoScroll, let last { withAnimation { proxy.scrollTo(last, anchor: .bottom) } } } From 7598cb1555359281f1c14ea76e1233e200074c82 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 16 Dec 2025 08:45:56 -0700 Subject: [PATCH 36/41] [Patch] updated next-version script --- scripts/next-version.sh | 56 +++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/scripts/next-version.sh b/scripts/next-version.sh index 70b1a34..3c94301 100755 --- a/scripts/next-version.sh +++ b/scripts/next-version.sh @@ -5,7 +5,7 @@ # ------------------------------------------------------------- # Description: # Determines the next semantic version of the Untold Engine -# by comparing the current release branch (e.g. release/0.3.0) +# by comparing the current base ref (release/x.y.z branch or vX.Y.Z tag) # against the latest commits in develop. # # The script scans commit messages between the release branch @@ -25,14 +25,14 @@ # snapshot documentation for the new release # # Usage Examples: -# ./scripts/next-version.sh +# ./scripts/next-version.sh # auto-picks latest vX.Y.Z tag from develop # ./scripts/next-version.sh --with-v # ./scripts/next-version.sh release/0.3.0 --cliff -# ./scripts/next-version.sh --cliff --docs +# ./scripts/next-version.sh v0.3.0 --cliff --docs # # Notes: # - Must be executed from the repository root. -# - Assumes release branches follow the pattern release/x.y.z. +# - Base ref can be a release/x.y.z branch or vX.Y.Z tag. # - Designed to simplify the Untold Engine release flow by # automating version calculation, changelog generation, and # docs versioning in a single step. @@ -44,38 +44,44 @@ set -euo pipefail WITH_V="false" DO_CLIFF="false" DO_DOCS="false" -BASE_BRANCH="" +BASE_REF="" for arg in "$@"; do case "$arg" in --with-v) WITH_V="true" ;; --cliff) DO_CLIFF="true" ;; --docs) DO_DOCS="true" ;; - release/*) BASE_BRANCH="$arg" ;; + release/*) BASE_REF="$arg" ;; + v[0-9]*|[0-9]*.[0-9]*.[0-9]*) BASE_REF="$arg" ;; # allow tags like v0.3.0 or 0.3.0 *) echo "Unknown argument: $arg" >&2; exit 2 ;; esac done -# Auto-pick latest local release/* branch if none provided -if [[ -z "${BASE_BRANCH}" ]]; then - BASE_BRANCH="$(git for-each-ref --sort=-committerdate --format='%(refname:short)' refs/heads/release/ | head -n1 || true)" +# Auto-pick latest reachable tag (vX.Y.Z) from develop if none provided +if [[ -z "${BASE_REF}" ]]; then + BASE_REF="$(git describe --tags --match 'v[0-9]*' --abbrev=0 develop 2>/dev/null || true)" fi -[[ -n "${BASE_BRANCH}" ]] || { echo "No local release/* branch found; provide one (e.g., release/0.3.0)." >&2; exit 1; } +[[ -n "${BASE_REF}" ]] || { echo "No base ref found. Pass a release branch (release/x.y.z) or tag (vX.Y.Z)." >&2; exit 1; } -# Parse base version from branch name release/x.y.z -if [[ ! "${BASE_BRANCH}" =~ ^release/([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then - echo "Base branch must be named like release/x.y.z (got: ${BASE_BRANCH})" >&2 +# Parse base version from branch or tag name +if [[ "${BASE_REF}" =~ ^release/([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + BASE_MAJOR="${BASH_REMATCH[1]}" + BASE_MINOR="${BASH_REMATCH[2]}" + BASE_PATCH="${BASH_REMATCH[3]}" +elif [[ "${BASE_REF}" =~ ^v?([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + BASE_MAJOR="${BASH_REMATCH[1]}" + BASE_MINOR="${BASH_REMATCH[2]}" + BASE_PATCH="${BASH_REMATCH[3]}" +else + echo "Base ref must look like release/x.y.z or vX.Y.Z (got: ${BASE_REF})" >&2 exit 1 fi -BASE_MAJOR="${BASH_REMATCH[1]}" -BASE_MINOR="${BASH_REMATCH[2]}" -BASE_PATCH="${BASH_REMATCH[3]}" # Ensure develop exists locally git rev-parse --verify develop >/dev/null 2>&1 || { echo "Local branch 'develop' not found." >&2; exit 1; } # Collect commit messages BASE..develop (local) -LOG="$(git log --pretty=%B "${BASE_BRANCH}..develop" || true)" +LOG="$(git log --pretty=%B "${BASE_REF}..develop" || true)" # Highest bump wins if echo "$LOG" | grep -q "\[API Change\]"; then @@ -109,7 +115,7 @@ fi if [[ "${DO_CLIFF}" == "true" ]]; then command -v git-cliff >/dev/null 2>&1 || { echo "git-cliff not found. Install it first." >&2; exit 1; } TAG="v${NEXT}" - RANGE="${BASE_BRANCH}..HEAD" + RANGE="${BASE_REF}..HEAD" git cliff "${RANGE}" --tag "${TAG}" --prepend CHANGELOG.md fi @@ -117,9 +123,17 @@ fi if [[ "${DO_DOCS}" == "true" ]]; then command -v npm >/dev/null 2>&1 || { echo "npm not found. Please install Node.js." >&2; exit 1; } - echo "🧭 Running Docusaurus versioning for ${NEXT}..." - npm run docusaurus docs:version "${NEXT}" + DOCS_DIR="website" + if [[ -d "${DOCS_DIR}" && -f "${DOCS_DIR}/package.json" ]]; then + echo "🧭 Running Docusaurus versioning for ${NEXT} (in ${DOCS_DIR})..." + ( + cd "${DOCS_DIR}" + npm run docusaurus docs:version "${NEXT}" + ) + else + echo "🧭 Running Docusaurus versioning for ${NEXT} (current directory)..." + npm run docusaurus docs:version "${NEXT}" + fi echo "✅ Docusaurus version ${NEXT} created." fi - From c52401d6e8526ca466a8fabcbc333114a9720a34 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 16 Dec 2025 17:03:33 -0700 Subject: [PATCH 37/41] [Patch] Fixed create ap bundle --- create_app_bundle.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/create_app_bundle.sh b/create_app_bundle.sh index 66ee708..593383a 100755 --- a/create_app_bundle.sh +++ b/create_app_bundle.sh @@ -8,7 +8,23 @@ echo "🔨 Building UntoldEditor app bundle..." APP_NAME="Untold Engine Studio" EXECUTABLE_NAME="UntoldEditor" BUNDLE_ID="com.untoldengine.studio" -VERSION="0.2.0" + +# Determine version (env -> release/* branch -> latest tag vX.Y.Z -> fallback) +detect_version() { + if [ -n "${VERSION:-}" ]; then + echo "$VERSION"; return + fi + branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "") + if [[ "$branch" == release/* ]]; then + echo "${branch#release/}"; return + fi + tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + if [[ "$tag" == v* ]]; then + echo "${tag#v}"; return + fi + echo "0.0.0-dev" +} +VERSION="$(detect_version)" BUILD_DIR=".build/arm64-apple-macosx/release" APP_BUNDLE="$APP_NAME.app" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" From a79da24249a035a903182f817df7f370b793ac11 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Tue, 16 Dec 2025 17:04:22 -0700 Subject: [PATCH 38/41] [Patch] Added visual feedback in asset browser view --- .../Editor/AssetBrowserView.swift | 56 ++++++++++++++++--- Sources/UntoldEditor/Editor/EditorView.swift | 2 +- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/Sources/UntoldEditor/Editor/AssetBrowserView.swift b/Sources/UntoldEditor/Editor/AssetBrowserView.swift index 180b55b..8d4eac3 100644 --- a/Sources/UntoldEditor/Editor/AssetBrowserView.swift +++ b/Sources/UntoldEditor/Editor/AssetBrowserView.swift @@ -57,6 +57,7 @@ struct AssetBrowserView: View { @State private var statusMessage: String? @State private var statusIsError = false @State private var compactMode = false + @State private var targetEntityName: String = "None" var editor_addEntityWithAsset: () -> Void private var currentFolderPath: URL? { folderPathStack.last @@ -139,14 +140,27 @@ struct AssetBrowserView: View { .padding(.horizontal, 10) .padding(.bottom, 5) } else { - Text("No Path Selected") - .font(.caption) - .foregroundColor(.red) - .padding(.horizontal, 10) - .padding(.bottom, 5) - } + Text("No Path Selected") + .font(.caption) + .foregroundColor(.red) + .padding(.horizontal, 10) + .padding(.bottom, 5) + } - // MARK: - Search + HStack { + Text("Target Entity:") + .font(.caption) + .foregroundColor(.secondary) + Text(targetEntityName) + .font(.caption) + .foregroundColor(.white) + .lineLimit(1) + Spacer() + } + .padding(.horizontal, 10) + .padding(.bottom, 2) + + // MARK: - Search HStack { Image(systemName: "magnifyingglass") TextField("Filter assets", text: $searchQuery) @@ -277,7 +291,14 @@ struct AssetBrowserView: View { .padding(10) } .frame(maxHeight: .infinity) - .onAppear(perform: loadAssets) + .onAppear { + loadAssets() + updateTargetEntityName(for: selectionManager.selectedEntity) + } + // Keep the target label in sync with editor selection changes. + .onReceive(selectionManager.$selectedEntity) { entityId in + updateTargetEntityName(for: entityId) + } .onChange(of: editorBaseAssetPath.basePath) { loadAssets() } @@ -670,6 +691,7 @@ struct AssetBrowserView: View { private func selectAsset(_ asset: Asset) { selectedAsset = asset selectedAssetName = asset.name + updateTargetEntityName(for: selectionManager.selectedEntity) } // MARK: - Delete Asset @@ -730,6 +752,8 @@ struct AssetBrowserView: View { // Select the newly created entity in the editor selectionManager.selectedEntity = entityId + + showStatus("Added model \(uniqueName)") } // Handle Gaussian files (ply) else if asset.category == AssetCategory.gaussians.rawValue, @@ -750,6 +774,8 @@ struct AssetBrowserView: View { // Select the newly created entity in the editor selectionManager.selectedEntity = entityId + + showStatus("Added Gaussian \(uniqueName)") } // Handle Animation files (usdz in Animations category) else if asset.category == AssetCategory.animations.rawValue, @@ -760,6 +786,7 @@ struct AssetBrowserView: View { entityId != .invalid else { print("⚠️ Please select an entity first to add animation") + showStatus("Select an entity before adding animation", isError: true) return } @@ -778,6 +805,7 @@ struct AssetBrowserView: View { // Refresh view selectionManager.objectWillChange.send() + showStatus("Animation linked to \(targetEntityName)", isError: false) } // Handle Script files (uscript) else if asset.category == AssetCategory.scripts.rawValue, @@ -788,6 +816,7 @@ struct AssetBrowserView: View { entityId != .invalid else { print("⚠️ Please select an entity first to add script") + showStatus("Select an entity before adding script", isError: true) return } @@ -820,6 +849,7 @@ struct AssetBrowserView: View { // Refresh view selectionManager.objectWillChange.send() + showStatus("Script linked to \(targetEntityName)", isError: false) } catch { print("❌ Failed to load script: \(error.localizedDescription)") } @@ -843,7 +873,17 @@ struct AssetBrowserView: View { generateHDR(filename, from: directoryURL) print("✅ HDR environment loaded: \(filename)") + showStatus("HDR loaded: \(filename)") + } + } + + private func updateTargetEntityName(for entityId: EntityID?) { + guard let entityId, entityId != .invalid else { + targetEntityName = "None" + return } + let name = getEntityName(entityId: entityId) + targetEntityName = name.isEmpty ? "Entity \(entityId)" : name } // MARK: - Load Scene Helper diff --git a/Sources/UntoldEditor/Editor/EditorView.swift b/Sources/UntoldEditor/Editor/EditorView.swift index dbc05da..4a33787 100644 --- a/Sources/UntoldEditor/Editor/EditorView.swift +++ b/Sources/UntoldEditor/Editor/EditorView.swift @@ -161,7 +161,7 @@ public struct EditorView: View { inspectorTab = .inspector } } - .frame(minWidth: 240, maxWidth: 320) + .frame(minWidth: 216, maxWidth: 288) } } .background( From 965d4374fc0e1d636b224f5e53ebeacf0eac69dd Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Thu, 18 Dec 2025 22:13:54 -0700 Subject: [PATCH 39/41] [Patch] Removed code editor view --- Sources/UntoldEditor/Editor/ToolbarView.swift | 565 +----------------- 1 file changed, 21 insertions(+), 544 deletions(-) diff --git a/Sources/UntoldEditor/Editor/ToolbarView.swift b/Sources/UntoldEditor/Editor/ToolbarView.swift index 807cb57..2ead9ae 100644 --- a/Sources/UntoldEditor/Editor/ToolbarView.swift +++ b/Sources/UntoldEditor/Editor/ToolbarView.swift @@ -8,8 +8,6 @@ // #if canImport(AppKit) import SwiftUI - import CodeEditorView - import LanguageSupport import AppKit struct ToolbarView: View { @@ -34,7 +32,6 @@ @State private var showBuildSettings = false @State private var showingNewScriptDialog = false @State private var newScriptName = "" - @State private var scriptEditorWindow: NSWindow? @State private var showBasePathAlert = false var body: some View { @@ -178,23 +175,38 @@ Divider().frame(height: 24) - // Scripts cluster: primary editor button + overflow menu Button(action: { guard ensureAssetBasePath() else { return } - toggleScriptEditorWindow() + showingNewScriptDialog = true }) { HStack(spacing: 6) { - Image(systemName: "chevron.left.forwardslash.chevron.right") - Text("Script Editor") + Image(systemName: "plus.circle.fill") + Text("New Script") } .font(.system(size: 12, weight: .semibold)) - .foregroundColor(.black.opacity(0.9)) + .foregroundColor(.white) .padding(.vertical, 6) .padding(.horizontal, 10) - .background(Color.editorAccent) + .background(Color.green.opacity(0.85)) .cornerRadius(8) } .buttonStyle(.plain) + .help("Create a new script in the Scripts project") + + Button(action: openInXcode) { + HStack(spacing: 6) { + Image(systemName: "hammer.fill") + Text("Open in Xcode") + } + .font(.system(size: 12, weight: .semibold)) + .foregroundColor(.white) + .padding(.vertical, 6) + .padding(.horizontal, 10) + .background(Color.blue.opacity(0.85)) + .cornerRadius(8) + } + .buttonStyle(.plain) + .help("Open Scripts project in Xcode") } } @@ -259,28 +271,6 @@ print("✅ Opening Scripts project in Xcode") } - private func toggleScriptEditorWindow() { - guard ensureAssetBasePath() else { return } - if let window = scriptEditorWindow { - window.makeKeyAndOrderFront(nil) - NSApp.activate(ignoringOtherApps: true) - return - } - - let editorView = ScriptEditorSheet { self.scriptEditorWindow?.close(); self.scriptEditorWindow = nil } - let hosting = NSHostingController(rootView: editorView) - let window = NSWindow(contentViewController: hosting) - window.title = "Script Editor" - window.styleMask = [.titled, .closable, .resizable, .miniaturizable] - window.setContentSize(NSSize(width: 1200, height: 900)) - window.minSize = NSSize(width: 400, height: 300) - window.isReleasedWhenClosed = false - window.center() - window.makeKeyAndOrderFront(nil) - NSApp.activate(ignoringOtherApps: true) - self.scriptEditorWindow = window - } - private func ensureAssetBasePath() -> Bool { guard EditorAssetBasePath.shared.basePath != nil else { showBasePathAlert = true @@ -350,517 +340,4 @@ } } - struct ScriptEditorSheet: View { - var onClose: (() -> Void)? - - @State private var scriptFiles: [URL] = [] - @State private var selectedFile: URL? - @State private var scriptText: String = "" - @State private var editPosition: CodeEditor.Position = .init() - @State private var messages: Set> = [] - @State private var statusMessage: String? - @State private var isBuilding = false - @State private var buildOutput: String = "" - @State private var keyMonitor: Any? - @State private var undoStack: [String] = [] - @State private var lastSavedText: String = "" - @State private var showDeleteConfirm = false - @State private var pendingSelection: URL? - @State private var lastKnownSelection: URL? - @State private var showUnsavedAlert = false - @State private var pendingClose = false - @State private var showingNewScriptDialogInSheet = false - @State private var newScriptNameInSheet = "" - @Environment(\.colorScheme) private var colorScheme - - private let manager = ScriptProjectManager.shared - private let uscDocsURL = URL(string: "https://untoldengine.github.io/UntoldEngine/docs/Scripting/usc-scripting-api")! - - private var isDirty: Bool { - selectedFile != nil && scriptText != lastSavedText - } - - var body: some View { - VStack(spacing: 0) { - header - Divider() - HStack(spacing: 0) { - scriptList - Divider() - editorPanel - } - Divider() - buildLog - } - .frame(minWidth: 600, idealWidth: 1100, minHeight: 500, idealHeight: 800) - .background(Color.editorBackground) - .accentColor(Color.editorAccent) - .onAppear { - prepareScripts() - installControlCopyPasteShortcuts() - } - .onDisappear { - removeControlCopyPasteShortcuts() - } - .interactiveDismissDisabled(isDirty) // Prevent accidental ESC dismissal with unsaved edits (if presented modally elsewhere) - .alert("Unsaved changes", isPresented: $showUnsavedAlert) { - Button("Save") { - saveCurrentScript() - continuePendingAction() - } - Button("Discard", role: .destructive) { - continuePendingAction() - } - Button("Cancel", role: .cancel) { - cancelPendingAction() - } - } message: { - Text("You have unsaved changes. Do you want to save before continuing?") - } - .sheet(isPresented: $showingNewScriptDialogInSheet) { - NewScriptDialog( - scriptName: $newScriptNameInSheet, - onCancel: { - showingNewScriptDialogInSheet = false - newScriptNameInSheet = "" - }, - onCreate: { - createNewScriptInSheet() - } - ) - } - } - - private var header: some View { - HStack { - VStack(alignment: .leading, spacing: 4) { - Text("Script Editor") - .font(.headline) - if let statusMessage { - Text(statusMessage) - .font(.caption) - .foregroundColor(.secondary) - } - Link("View Untold Engine API Docs", destination: uscDocsURL) - .font(.caption) - } - - Spacer() - - Button("Build All") { - runBuild() - } - .buttonStyle(.borderedProminent) - .disabled(isBuilding) - - Button("Save") { - saveCurrentScript() - } - .disabled(selectedFile == nil) - -// Button("Revert to Saved") { -// revertToLastSaved() -// } -// .disabled(selectedFile == nil) -// -// Button("Undo Last Change") { -// undoLastChange() -// } -// .disabled(undoStack.isEmpty) - -// Button("New Script") { -// showingNewScriptDialogInSheet = true -// } -// .buttonStyle(.borderedProminent) -// .tint(.green) - -// Button("Delete") { -// showDeleteConfirm = true -// } -// .disabled(selectedFile == nil || isProtectedFile(selectedFile)) -// .buttonStyle(.borderedProminent) -// .tint(.red) -// .confirmationDialog( -// "Delete script?", -// isPresented: $showDeleteConfirm, -// titleVisibility: .visible -// ) { -// Button("Delete", role: .destructive) { -// deleteSelectedScript() -// } -// Button("Cancel", role: .cancel) { showDeleteConfirm = false } -// } message: { -// if let selectedFile { -// Text("Are you sure you want to delete \(selectedFile.lastPathComponent)? This cannot be undone.") -// } -// } - - Button("Close") { - if isDirty { - pendingClose = true - showUnsavedAlert = true - } else { - onClose?() - } - } - } - .padding() - } - - private var scriptList: some View { - VStack(alignment: .leading, spacing: 0) { - HStack { - Text("Scripts") - .font(.headline) - .foregroundColor(.white) - Spacer() - Button { - showingNewScriptDialogInSheet = true - } label: { - Image(systemName: "plus.circle.fill") - .foregroundColor(Color.editorAccent) - } - .buttonStyle(.plain) - .help("New Script") - Button { - showDeleteConfirm = true - } label: { - Image(systemName: "trash.fill") - .foregroundColor(.red) - } - .buttonStyle(.plain) - .disabled(selectedFile == nil || isProtectedFile(selectedFile)) - .help("Delete Script") - .confirmationDialog( - "Delete script?", - isPresented: $showDeleteConfirm, - titleVisibility: .visible - ) { - Button("Delete", role: .destructive) { - deleteSelectedScript() - } - Button("Cancel", role: .cancel) { showDeleteConfirm = false } - } message: { - if let selectedFile { - Text("Are you sure you want to delete \(selectedFile.lastPathComponent)? This cannot be undone.") - } - } - } - .padding(.horizontal, 10) - .padding(.vertical, 6) - .background(Color.editorSurface) - - Divider() - .background(Color.editorDivider) - - List(selection: $selectedFile) { - ForEach(scriptFiles, id: \.self) { url in - Text(url.lastPathComponent) - .lineLimit(1) - .foregroundColor(.primary) - .tag(Optional(url)) - } - } - .listStyle(.plain) - .scrollContentBackground(.hidden) - } - .background(Color.editorPanelBackground) - .cornerRadius(8) - .frame(minWidth: 180, idealWidth: 220, maxWidth: 260, maxHeight: .infinity) - .onChange(of: selectedFile) { newSelection in - handleSelectionChange(newSelection) - } - } - - private var editorPanel: some View { - VStack(alignment: .leading, spacing: 8) { - if let selectedFile { - Text(selectedFile.lastPathComponent) - .font(.subheadline) - .foregroundColor(.secondary) - CodeEditor( - text: $scriptText, - position: $editPosition, - messages: $messages, - language: .swift() - ) - .environment( - \.codeEditorTheme, - colorScheme == .dark ? Theme.defaultDark : Theme.defaultLight - ) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.editorSurface) - .cornerRadius(6) - } else { - VStack { - Text("Select a script to edit") - .foregroundColor(.secondary) - Spacer() - } - } - } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .layoutPriority(1) - } - - private var buildLog: some View { - VStack(alignment: .leading, spacing: 8) { - HStack { - Text("Build Output") - .font(.subheadline.bold()) - if isBuilding { - ProgressView() - .progressViewStyle(.circular) - } - Spacer() - Button { - let output = buildOutput - #if os(macOS) - NSPasteboard.general.clearContents() - NSPasteboard.general.setString(output, forType: .string) - #endif - } label: { - Image(systemName: "doc.on.doc") - } - .help("Copy build output") - .disabled(buildOutput.isEmpty) - - Button { - buildOutput = "" - } label: { - Image(systemName: "trash") - } - .help("Clear build output") - .disabled(buildOutput.isEmpty) - } - .padding(.horizontal, 4) - - ScrollViewReader { proxy in - ScrollView { - Text(buildOutput.isEmpty ? "No builds yet." : buildOutput) - .font(.system(.body, design: .monospaced)) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(4) - .id("buildOutputText") - .textSelection(.enabled) - } - .onChange(of: buildOutput) { _ in - withAnimation { - proxy.scrollTo("buildOutputText", anchor: .bottom) - } - } - } - .frame(maxWidth: .infinity) - } - .padding() - .background(Color.editorPanelBackground) - .cornerRadius(8) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(Color.editorDivider, lineWidth: 1) - ) - .frame(minHeight: 160, maxHeight: 200) - } - - private func prepareScripts() { - do { - if !manager.isProjectInitialized() { - try manager.initializeProject() - } - statusMessage = nil - } catch { - statusMessage = "Failed to initialize scripts: \(error.localizedDescription)" - } - - reloadScripts() - } - - private func reloadScripts() { - scriptFiles = manager.listScriptFiles().sorted { $0.lastPathComponent < $1.lastPathComponent } - if selectedFile == nil { - selectedFile = scriptFiles.first - } - lastKnownSelection = selectedFile - loadSelectedScript() - } - - private func loadSelectedScript() { - guard let selectedFile else { - scriptText = "" - return - } - - do { - scriptText = try String(contentsOf: selectedFile, encoding: .utf8) - statusMessage = nil - lastSavedText = scriptText - undoStack.removeAll() - } catch { - scriptText = "" - statusMessage = "Failed to load \(selectedFile.lastPathComponent)" - } - } - - private func saveCurrentScript() { - guard let selectedFile else { return } - - do { - if scriptText != lastSavedText { - undoStack.append(lastSavedText) - } - try scriptText.write(to: selectedFile, atomically: true, encoding: .utf8) - statusMessage = "Saved \(selectedFile.lastPathComponent)" - lastSavedText = scriptText - } catch { - statusMessage = "Failed to save \(selectedFile.lastPathComponent)" - } - } - - private func runBuild() { - // Ensure current edits are saved before building - saveCurrentScript() - - isBuilding = true - buildOutput = "Running swift run...\n" - - manager.buildScripts { result in - isBuilding = false - - switch result { - case .success(let output): - buildOutput += output - statusMessage = "Scripts built successfully" - case .failure(let error): - buildOutput += error.localizedDescription - statusMessage = "Build failed" - } - } - } - - private func revertToLastSaved() { - guard selectedFile != nil else { return } - scriptText = lastSavedText - statusMessage = "Reverted to last saved." - } - - private func undoLastChange() { - guard let previous = undoStack.popLast() else { - statusMessage = "Nothing to undo." - return - } - scriptText = previous - statusMessage = "Reverted last change." - } - - private func deleteSelectedScript() { - guard let file = selectedFile else { return } - guard !isProtectedFile(file) else { - statusMessage = "Cannot delete protected file." - return - } - do { - try FileManager.default.removeItem(at: file) - statusMessage = "Deleted \(file.lastPathComponent)" - showDeleteConfirm = false - manager.removeScriptInvocationFromMain(name: file.deletingPathExtension().lastPathComponent) - reloadScripts() - if scriptFiles.isEmpty { - selectedFile = nil - scriptText = "" - } else { - selectedFile = scriptFiles.first - loadSelectedScript() - } - } catch { - statusMessage = "Failed to delete \(file.lastPathComponent)" - } - } - - private func isProtectedFile(_ url: URL?) -> Bool { - guard let url else { return true } - return url.lastPathComponent == "GenerateScripts.swift" - } - - // Map Cmd+C/V/X/A to standard copy/paste/cut/select-all while the sheet is active. - private func installControlCopyPasteShortcuts() { - keyMonitor = NSEvent.addLocalMonitorForEvents(matching: [.keyDown]) { event in - guard event.modifierFlags.contains(.command), - let character = event.charactersIgnoringModifiers?.lowercased() - else { return event } - - switch character { - case "c": - NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: nil) - return nil - case "v": - NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: nil) - return nil - case "x": - NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: nil) - return nil - case "a": - NSApp.sendAction(#selector(NSText.selectAll(_:)), to: nil, from: nil) - return nil - default: - return event - } - } - } - - private func removeControlCopyPasteShortcuts() { - if let monitor = keyMonitor { - NSEvent.removeMonitor(monitor) - keyMonitor = nil - } - } - - private func handleSelectionChange(_ newSelection: URL?) { - guard let newSelection else { return } - - if isDirty { - pendingSelection = newSelection - selectedFile = lastKnownSelection - showUnsavedAlert = true - return - } - - lastKnownSelection = newSelection - loadSelectedScript() - } - - private func continuePendingAction() { - if let target = pendingSelection { - selectedFile = target - lastKnownSelection = target - loadSelectedScript() - } else if pendingClose { - onClose?() - } - pendingSelection = nil - pendingClose = false - } - - private func cancelPendingAction() { - pendingSelection = nil - pendingClose = false - } - - private func createNewScriptInSheet() { - do { - try manager.createNewScript(name: newScriptNameInSheet) - reloadScripts() - if let created = scriptFiles.first(where: { $0.deletingPathExtension().lastPathComponent == newScriptNameInSheet }) { - selectedFile = created - lastKnownSelection = created - loadSelectedScript() - } - statusMessage = "Created \(newScriptNameInSheet).swift" - } catch { - statusMessage = "Failed to create script: \(error.localizedDescription)" - } - newScriptNameInSheet = "" - showingNewScriptDialogInSheet = false - } - } #endif From cdad0e7b2cd587090e346eba805c69163894a4b8 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Thu, 18 Dec 2025 22:21:15 -0700 Subject: [PATCH 40/41] [Patch] set asset browser view opacity to 1.0 --- Sources/UntoldEditor/Editor/EditorView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/UntoldEditor/Editor/EditorView.swift b/Sources/UntoldEditor/Editor/EditorView.swift index 4a33787..38615c3 100644 --- a/Sources/UntoldEditor/Editor/EditorView.swift +++ b/Sources/UntoldEditor/Editor/EditorView.swift @@ -320,7 +320,7 @@ public struct EditorView: View { window.minSize = NSSize(width: 620, height: 420) window.level = .floating // keep above editor while arranging assets window.isReleasedWhenClosed = false - window.alphaValue = 0.9 + window.alphaValue = 1.0 window.center() window.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) From c6196e5969d6e08a239fef150cb7269a24a485e0 Mon Sep 17 00:00:00 2001 From: Harold Serrano Date: Fri, 19 Dec 2025 08:43:15 -0700 Subject: [PATCH 41/41] [Release] prepare for 0.1.0 --- Package.swift | 5 +-- .../Editor/AssetBrowserView.swift | 43 ++++++++++--------- .../Editor/AssetWindowDelegate.swift | 2 +- Sources/UntoldEditor/Editor/EditorView.swift | 8 ++-- Sources/UntoldEditor/Editor/ToolbarView.swift | 3 +- .../Systems/ScriptProjectManager.swift | 4 +- Sources/UntoldEditor/main.swift | 2 +- .../AssetBrowserViewTests.swift | 2 + .../UntoldEditorTests/ToolbarViewTests.swift | 30 ++++++++----- 9 files changed, 54 insertions(+), 45 deletions(-) diff --git a/Package.swift b/Package.swift index 1eecfba..fada1bc 100644 --- a/Package.swift +++ b/Package.swift @@ -10,19 +10,16 @@ let package = Package( .executable(name: "UntoldEditor", targets: ["UntoldEditor"]), ], dependencies: [ - .package(url: "https://github.com/mchakravarty/CodeEditorView.git", branch: "main"), // Use a branch during active development: // .package(url: "https://github.com/untoldengine/UntoldEngine.git", branch: "develop"), // Or pin to a release: - .package(url: "https://github.com/untoldengine/UntoldEngine.git", exact: "0.6.0"), + .package(url: "https://github.com/untoldengine/UntoldEngine.git", exact: "0.6.1"), ], targets: [ .executableTarget( name: "UntoldEditor", dependencies: [ .product(name: "UntoldEngine", package: "UntoldEngine"), - .product(name: "CodeEditorView", package: "CodeEditorView"), - .product(name: "LanguageSupport", package: "CodeEditorView"), ], path: "Sources/UntoldEditor", resources: [ diff --git a/Sources/UntoldEditor/Editor/AssetBrowserView.swift b/Sources/UntoldEditor/Editor/AssetBrowserView.swift index 8d4eac3..5d08e9e 100644 --- a/Sources/UntoldEditor/Editor/AssetBrowserView.swift +++ b/Sources/UntoldEditor/Editor/AssetBrowserView.swift @@ -140,27 +140,28 @@ struct AssetBrowserView: View { .padding(.horizontal, 10) .padding(.bottom, 5) } else { - Text("No Path Selected") - .font(.caption) - .foregroundColor(.red) - .padding(.horizontal, 10) - .padding(.bottom, 5) - } + Text("No Path Selected") + .font(.caption) + .foregroundColor(.red) + .padding(.horizontal, 10) + .padding(.bottom, 5) + } - HStack { - Text("Target Entity:") - .font(.caption) - .foregroundColor(.secondary) - Text(targetEntityName) - .font(.caption) - .foregroundColor(.white) - .lineLimit(1) - Spacer() - } - .padding(.horizontal, 10) - .padding(.bottom, 2) + HStack { + Text("Target Entity:") + .font(.caption) + .foregroundColor(.secondary) + Text(targetEntityName) + .font(.caption) + .foregroundColor(.white) + .lineLimit(1) + Spacer() + } + .padding(.horizontal, 10) + .padding(.bottom, 2) + + // MARK: - Search - // MARK: - Search HStack { Image(systemName: "magnifyingglass") TextField("Filter assets", text: $searchQuery) @@ -254,7 +255,7 @@ struct AssetBrowserView: View { if let currentFolderPath, !isScripts { folderContentsView(for: currentFolderPath, selectionManager: selectionManager) } else { - if let categoryAssets = assets[selectedCategory] { + if let categoryAssets = assets[selectedCategory] { ForEach(categoryAssets.filter { matchesSearch($0) }) { asset in // For Scripts, we never navigate into folders (we won't list folders anyway) assetRow(asset) @@ -409,7 +410,7 @@ struct AssetBrowserView: View { let fm = FileManager.default let categoryRoot = basePath.appendingPathComponent(selectedCategory!, isDirectory: true) // Ensure category folder exists (e.g., /Models) - try? fm.createDirectory(at: categoryRoot, withIntermediateDirectories: true) + try? fm.createDirectory(at: categoryRoot, withIntermediateDirectories: true) if openPanel.runModal() == .OK { for sourceURL in openPanel.urls { diff --git a/Sources/UntoldEditor/Editor/AssetWindowDelegate.swift b/Sources/UntoldEditor/Editor/AssetWindowDelegate.swift index d5157d6..ff81547 100644 --- a/Sources/UntoldEditor/Editor/AssetWindowDelegate.swift +++ b/Sources/UntoldEditor/Editor/AssetWindowDelegate.swift @@ -7,7 +7,7 @@ final class AssetWindowDelegate: NSObject, NSWindowDelegate { self.onClose = onClose } - func windowWillClose(_ notification: Notification) { + func windowWillClose(_: Notification) { onClose() } } diff --git a/Sources/UntoldEditor/Editor/EditorView.swift b/Sources/UntoldEditor/Editor/EditorView.swift index 38615c3..15992b3 100644 --- a/Sources/UntoldEditor/Editor/EditorView.swift +++ b/Sources/UntoldEditor/Editor/EditorView.swift @@ -283,7 +283,7 @@ public struct EditorView: View { return } - if FileManager.default.fileExists(atPath: destinationURL.path) && !overwrite { + if FileManager.default.fileExists(atPath: destinationURL.path), !overwrite { showOverwriteAlert = true return } @@ -325,10 +325,10 @@ public struct EditorView: View { window.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) - let delegate = AssetWindowDelegate { + let delegate = AssetWindowDelegate { // Window is closing — clear our references - self.assetWindow = nil - self.assetWindowDelegate = nil + assetWindow = nil + assetWindowDelegate = nil } window.delegate = delegate assetWindowDelegate = delegate diff --git a/Sources/UntoldEditor/Editor/ToolbarView.swift b/Sources/UntoldEditor/Editor/ToolbarView.swift index 2ead9ae..1f42bf8 100644 --- a/Sources/UntoldEditor/Editor/ToolbarView.swift +++ b/Sources/UntoldEditor/Editor/ToolbarView.swift @@ -7,8 +7,8 @@ // See the LICENSE file or for details. // #if canImport(AppKit) - import SwiftUI import AppKit + import SwiftUI struct ToolbarView: View { @ObservedObject var selectionManager: SelectionManager @@ -207,7 +207,6 @@ } .buttonStyle(.plain) .help("Open Scripts project in Xcode") - } } diff --git a/Sources/UntoldEditor/Systems/ScriptProjectManager.swift b/Sources/UntoldEditor/Systems/ScriptProjectManager.swift index 3adb61c..23026ec 100644 --- a/Sources/UntoldEditor/Systems/ScriptProjectManager.swift +++ b/Sources/UntoldEditor/Systems/ScriptProjectManager.swift @@ -220,7 +220,7 @@ class ScriptProjectManager { // Preserve existing indentation from the anchor line let lineStart = contents[.., selectedAsset: Binding, selectionManager: SelectionManager = SelectionManager(), + sceneGraphModel: SceneGraphModel = SceneGraphModel(), editor_addEntityWithAsset: @escaping () -> Void = {}) -> AssetBrowserView { AssetBrowserView( assets: assets, selectedAsset: selectedAsset, selectionManager: selectionManager, + sceneGraphModel: sceneGraphModel, editor_addEntityWithAsset: editor_addEntityWithAsset ) } diff --git a/Tests/UntoldEditorTests/ToolbarViewTests.swift b/Tests/UntoldEditorTests/ToolbarViewTests.swift index 1424699..5df6387 100644 --- a/Tests/UntoldEditorTests/ToolbarViewTests.swift +++ b/Tests/UntoldEditorTests/ToolbarViewTests.swift @@ -19,8 +19,9 @@ import XCTest private func makeSUT( selectionManager: SelectionManager = SelectionManager(), onSaveCalled: UnsafeMutablePointer, - onLoadCalled: UnsafeMutablePointer, + onSaveAsCalled: UnsafeMutablePointer, onClearCalled: UnsafeMutablePointer, + onShowAssetsCalled: UnsafeMutablePointer, onCameraSaveCalled _: UnsafeMutablePointer, onPlayToggledValues: UnsafeMutablePointer<[Bool]>, onDirLightCalled: UnsafeMutablePointer, @@ -36,9 +37,10 @@ import XCTest ToolbarView( selectionManager: selectionManager, onSave: { onSaveCalled.pointee = true }, - onLoad: { onLoadCalled.pointee = true }, + onSaveAs: { onSaveAsCalled.pointee = true }, onClear: { onClearCalled.pointee = true }, onPlayToggled: { value in onPlayToggledValues.pointee.append(value) }, + onShowAssets: { onShowAssetsCalled.pointee = true }, dirLightCreate: { onDirLightCalled.pointee = true }, pointLightCreate: { onPointLightCalled.pointee = true }, spotLightCreate: { onSpotLightCalled.pointee = true }, @@ -53,8 +55,9 @@ import XCTest func test_actionsAreWired_up() { var onSave = false - var onLoad = false + var onSaveAs = false var onClear = false + var onShowAssets = false var onCameraSave = false var playValues: [Bool] = [] var onDir = false @@ -69,8 +72,9 @@ import XCTest let sut = makeSUT( onSaveCalled: &onSave, - onLoadCalled: &onLoad, + onSaveAsCalled: &onSaveAs, onClearCalled: &onClear, + onShowAssetsCalled: &onShowAssets, onCameraSaveCalled: &onCameraSave, onPlayToggledValues: &playValues, onDirLightCalled: &onDir, @@ -87,8 +91,9 @@ import XCTest // We cannot programmatically tap SwiftUI Buttons without a host and introspection. // Instead, assert that injected closures can be called and flip their flags. sut.onSave() - sut.onLoad() + sut.onSaveAs() sut.onClear() + sut.onShowAssets() sut.dirLightCreate() sut.pointLightCreate() sut.spotLightCreate() @@ -100,8 +105,9 @@ import XCTest sut.onCreateCone() XCTAssertTrue(onSave, "onSave should be wired.") - XCTAssertTrue(onLoad, "onLoad should be wired.") + XCTAssertTrue(onSaveAs, "onSaveAs should be wired.") XCTAssertTrue(onClear, "onClear should be wired.") + XCTAssertTrue(onShowAssets, "onShowAssets should be wired.") XCTAssertTrue(onDir, "dirLightCreate should be wired.") XCTAssertTrue(onPoint, "pointLightCreate should be wired.") XCTAssertTrue(onSpot, "spotLightCreate should be wired.") @@ -127,9 +133,10 @@ import XCTest let sut = ToolbarView( selectionManager: selection, onSave: {}, - onLoad: {}, + onSaveAs: {}, onClear: {}, onPlayToggled: { playValues.append($0) }, + onShowAssets: {}, dirLightCreate: {}, pointLightCreate: {}, spotLightCreate: {}, @@ -161,9 +168,10 @@ import XCTest let sut = ToolbarView( selectionManager: SelectionManager(), onSave: {}, - onLoad: {}, + onSaveAs: {}, onClear: {}, onPlayToggled: { _ in }, + onShowAssets: {}, dirLightCreate: {}, pointLightCreate: {}, spotLightCreate: {}, @@ -196,9 +204,10 @@ import XCTest let sut = ToolbarView( selectionManager: SelectionManager(), onSave: {}, - onLoad: {}, + onSaveAs: {}, onClear: {}, onPlayToggled: { _ in }, + onShowAssets: {}, dirLightCreate: {}, pointLightCreate: {}, spotLightCreate: {}, @@ -228,9 +237,10 @@ import XCTest let sut = ToolbarView( selectionManager: SelectionManager(), onSave: {}, - onLoad: {}, + onSaveAs: {}, onClear: {}, onPlayToggled: { _ in }, + onShowAssets: {}, dirLightCreate: {}, pointLightCreate: {}, spotLightCreate: {},