diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/api/AppAssets.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/api/AppAssets.kt index 7d4a31c7b..722f74860 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/api/AppAssets.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/api/AppAssets.kt @@ -15,36 +15,39 @@ interface AppAssetsLoader { } object AppAssets : AppAssetsLoader { - var impl: AppAssetsLoader = DefaultLoader() + var impl: AppAssetsLoader = DefaultLoader("assets") override suspend fun loadHdriEnvironment(path: String): EnvironmentMaps? = impl.loadHdriEnvironment(path) override suspend fun loadModel(modelPath: String): GltfFile? = impl.loadModel(modelPath) override suspend fun loadTexture2d(path: String): Texture2d? = impl.loadTexture2d(path) - class DefaultLoader : AppAssetsLoader { + class DefaultLoader(val pathPrefix: String) : AppAssetsLoader { override suspend fun loadHdriEnvironment(path: String): EnvironmentMaps? { + val prefixed = "${pathPrefix}/${path}" return try { - EnvironmentHelper.hdriEnvironment(Assets.loadTexture2d(path)) + EnvironmentHelper.hdriEnvironment(Assets.loadTexture2d(prefixed)) } catch (e: Exception) { - logE { "Failed loading HDRI: $path" } + logE { "Failed loading HDRI: $prefixed" } null } } override suspend fun loadModel(modelPath: String): GltfFile? { + val prefixed = "${pathPrefix}/${modelPath}" return try { - Assets.loadGltfFile(modelPath) + Assets.loadGltfFile(prefixed) } catch (e: Exception) { - logE { "Failed loading model: $modelPath" } + logE { "Failed loading model: $prefixed" } null } } override suspend fun loadTexture2d(path: String): Texture2d? { + val prefixed = "${pathPrefix}/${path}" return try { - Assets.loadTexture2d(path) + Assets.loadTexture2d(prefixed) } catch (e: Exception) { - logE { "Failed loading texture: $path" } + logE { "Failed loading texture: $prefixed" } null } } diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/model/EditorProject.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/model/EditorProject.kt index 776c891ec..7124aebef 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/model/EditorProject.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/model/EditorProject.kt @@ -1,13 +1,13 @@ package de.fabmax.kool.editor.model import de.fabmax.kool.Assets -import de.fabmax.kool.editor.data.MaterialData -import de.fabmax.kool.editor.data.PbrShaderData -import de.fabmax.kool.editor.data.ProjectData -import de.fabmax.kool.editor.data.SceneNodeData +import de.fabmax.kool.editor.data.* +import de.fabmax.kool.math.MutableMat4d +import de.fabmax.kool.math.Vec3d +import de.fabmax.kool.math.deg import de.fabmax.kool.modules.ui2.mutableStateListOf +import de.fabmax.kool.util.MdColor import de.fabmax.kool.util.logE -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json class EditorProject(val projectData: ProjectData) { @@ -129,5 +129,43 @@ class EditorProject(val projectData: ProjectData) { null } } + + fun emptyProject(): EditorProject = EditorProject( + ProjectData().apply { + val sceneId = nextId++ + val camId = nextId++ + val boxId = nextId++ + val lightId = nextId++ + sceneNodeIds += sceneId + sceneNodes += SceneNodeData("New Scene", sceneId).apply { + childNodeIds += listOf(camId, boxId, lightId) + components += ScenePropertiesComponentData(cameraNodeId = camId) + components += SceneBackgroundComponentData( + SceneBackgroundData.SingleColor(ColorData(MdColor.GREY toneLin 900)) + ) + } + sceneNodes += SceneNodeData("Camera", camId).apply { + components += CameraComponentData(CameraTypeData.Perspective()) + components += TransformComponentData( + TransformData.fromMatrix( + MutableMat4d() + .translate(0.0, 2.5, 5.0) + .rotate(-30.0.deg, Vec3d.X_AXIS) + )) + } + sceneNodes += SceneNodeData("Default Cube", boxId).apply { + components += MeshComponentData(MeshShapeData.Box(Vec3Data(1.0, 1.0, 1.0))) + } + sceneNodes += SceneNodeData("Directional Light", lightId).apply { + components += DiscreteLightComponentData(LightTypeData.Directional()) + components += TransformComponentData( + TransformData.fromMatrix( + MutableMat4d() + .translate(5.0, 5.0, 5.0) + .rotate(0.0.deg, 30.0.deg, (-120.0).deg) + )) + } + } + ) } } diff --git a/kool-core/src/jsMain/kotlin/de/fabmax/kool/modules/filesystem/FileSystem.js.kt b/kool-core/src/jsMain/kotlin/de/fabmax/kool/modules/filesystem/FileSystem.js.kt index 70e530bd3..81a15bea3 100644 --- a/kool-core/src/jsMain/kotlin/de/fabmax/kool/modules/filesystem/FileSystem.js.kt +++ b/kool-core/src/jsMain/kotlin/de/fabmax/kool/modules/filesystem/FileSystem.js.kt @@ -43,7 +43,7 @@ class ZipFileSytem(zip: JsZip) : FileSystem { .filter { it.isNotEmpty() } .fold(root as Directory) { parent, name -> parent.items.getOrPut(name) { - val newDir = Directory("${parent.path}/${name}") + val newDir = Directory(FileSystem.sanitizePath("${parent.path}/${name}")) fsItems[newDir.path] = newDir newDir } as Directory diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/AppModeController.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/AppModeController.kt index 84e78c787..892a4eb35 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/AppModeController.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/AppModeController.kt @@ -11,8 +11,8 @@ import de.fabmax.kool.util.logI class AppModeController(val editor: KoolEditor) { fun startApp() { - val app = EditorState.loadedApp.value?.app ?: return - val sceneModel = EditorState.projectModel.getCreatedScenes().getOrNull(0) ?: return + val app = editor.loadedApp.value?.app ?: return + val sceneModel = editor.projectModel.getCreatedScenes().getOrNull(0) ?: return logI { "Start app" } diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/AvailableAssets.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/AvailableAssets.kt index fc82d48f5..77d401ee3 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/AvailableAssets.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/AvailableAssets.kt @@ -12,6 +12,7 @@ class AvailableAssets(private val projectFiles: ProjectFiles) { val textureAssets = mutableStateListOf() val hdriAssets = mutableStateListOf() + private val assetPathPrefix = projectFiles.assets.path private val assetsByPath = mutableMapOf() private val fsWatcher = object : FileSystemWatcher { @@ -27,7 +28,7 @@ class AvailableAssets(private val projectFiles: ProjectFiles) { if (parent == null) { refreshAllAssets() } else { - val assetItem = AssetItem(fileItem) + val assetItem = AssetItem(fileItem, fileItem.assetPath) assetsByPath[fileItem.path] = assetItem parent.children += assetItem parent.sortChildrenByName() @@ -67,21 +68,25 @@ class AvailableAssets(private val projectFiles: ProjectFiles) { } fun createAssetDir(createPath: String) { - val parentPath = FileSystem.parentPath(createPath) + val prefixedPath = FileSystem.sanitizePath("${assetPathPrefix}/${createPath}") + val parentPath = FileSystem.parentPath(prefixedPath) val parentItem = assetsByPath[parentPath]?.fileItem as? WritableFileSystemDirectory? if (parentItem == null) { logE { "Unable to create directory: ${createPath}. Parent directory not found or not writable" } return } - parentItem.createDirectory(createPath.removePrefix(parentPath)) + parentItem.createDirectory(prefixedPath.removePrefix(parentPath)) } suspend fun renameAsset(sourcePath: String, destPath: String) { - projectFiles.fileSystem.move(sourcePath, destPath) + val prefixedSrc = FileSystem.sanitizePath("${assetPathPrefix}/${sourcePath}") + val prefixedDst = FileSystem.sanitizePath("${assetPathPrefix}/${destPath}") + projectFiles.fileSystem.move(prefixedSrc, prefixedDst) } fun deleteAsset(deletePath: String) { - val deleteItem = assetsByPath[deletePath]?.fileItem as? WritableFileSystemItem? + val prefixedPath = FileSystem.sanitizePath("${assetPathPrefix}/${deletePath}") + val deleteItem = assetsByPath[prefixedPath]?.fileItem as? WritableFileSystemItem? if (deleteItem == null) { logE { "Unable to delete asset: ${deletePath}. Path not found or not writable" } return @@ -90,7 +95,8 @@ class AvailableAssets(private val projectFiles: ProjectFiles) { } suspend fun importAssets(targetPath: String, assetFiles: List) { - val targetDir = assetsByPath[FileSystem.sanitizePath(targetPath)]?.fileItem as? WritableFileSystemDirectory? + val prefixedPath = FileSystem.sanitizePath("${assetPathPrefix}/${targetPath}") + val targetDir = assetsByPath[prefixedPath]?.fileItem as? WritableFileSystemDirectory? if (targetDir == null) { logE { "Unable to import assets into target directory: ${targetPath}. Path not found or not writable" } return @@ -113,7 +119,7 @@ class AvailableAssets(private val projectFiles: ProjectFiles) { val parent = assetsByPath[parentPath] val assetItem = assetsByPath.getOrPut(file.path) { - AssetItem(file) + AssetItem(file, file.assetPath) } if (parent != null) { @@ -135,13 +141,18 @@ class AvailableAssets(private val projectFiles: ProjectFiles) { addAll(newRootAssets) } } + + private val FileSystemItem.assetPath: String + get() = path.removePrefix(assetPathPrefix) } -class AssetItem(val fileItem: FileSystemItem, val type: AppAssetType = AppAssetType.fromFileItem(fileItem)) { +class AssetItem( + val fileItem: FileSystemItem, + val path: String, + val type: AppAssetType = AppAssetType.fromFileItem(fileItem) +) { val name: String get() = fileItem.name - val path: String - get() = fileItem.path val children = mutableStateListOf() diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorCamTransform.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorCamTransform.kt index 4296d2893..323e831e2 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorCamTransform.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorCamTransform.kt @@ -50,7 +50,7 @@ class EditorCamTransform(val editor: KoolEditor) : OrbitInputTransform("Editor c } } - fun focusSelectedObject() = focusObjects(EditorState.getSelectedSceneNodes()) + fun focusSelectedObject() = focusObjects(editor.selectionOverlay.getSelectedSceneNodes()) fun focusObject(objectModel: SceneNodeModel) = focusObjects(listOf(objectModel)) diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorCopyAndPaste.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorClipboard.kt similarity index 72% rename from kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorCopyAndPaste.kt rename to kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorClipboard.kt index 0a4394f11..dca6548d4 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorCopyAndPaste.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorClipboard.kt @@ -7,19 +7,21 @@ import de.fabmax.kool.editor.model.SceneNodeModel import de.fabmax.kool.util.launchOnMainThread import de.fabmax.kool.util.logD import de.fabmax.kool.util.logW -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString -object EditorCopyAndPaste { +object EditorClipboard { + + private val editor: KoolEditor + get() = KoolEditor.instance fun copySelection() { - val selection = EditorState.getSelectedSceneNodes() + val selection = editor.selectionOverlay.getSelectedSceneNodes() if (selection.isNotEmpty()) { logD { "Copy ${selection.size} selected objects" } if (selection.any { it.nodeData.childNodeIds.isNotEmpty() }) { logW { "Copied nodes contain child nodes, hierarchy won't be preserved during copy and paste. All copied nodes will be flattened" } } - val json = EditorState.jsonCodec.encodeToString(selection.map { it.nodeData }) + val json = KoolEditor.jsonCodec.encodeToString(selection.map { it.nodeData }) Clipboard.copyToClipboard(json) } else { logD { "Nothing to copy: Selection is empty" } @@ -28,19 +30,19 @@ object EditorCopyAndPaste { fun paste() { Clipboard.getStringFromClipboard { json -> - val scene = EditorState.activeScene.value + val scene = editor.activeScene.value if (json != null && scene != null) { try { - val copyData = EditorState.jsonCodec.decodeFromString>(json) + val copyData = KoolEditor.jsonCodec.decodeFromString>(json) if (copyData.isNotEmpty()) { logD { "Pasting ${copyData.size} objects from clipboard" } sanitizeCopiedNodeIds(copyData) - val selection = EditorState.getSelectedNodes() + val selection = editor.selectionOverlay.getSelectedNodes() val parent = if (selection.size == 1) selection[0] else scene val sceneNodes = copyData.map { SceneNodeModel(it, parent, scene) } AddNodeAction(sceneNodes).apply() - EditorState.setSelection(sceneNodes) + editor.selectionOverlay.setSelection(sceneNodes) } } catch (e: Exception) { logW { "Unable to paste clipboard content: Invalid content" } @@ -50,11 +52,11 @@ object EditorCopyAndPaste { } fun duplicateSelection() { - val selection = EditorState.getSelectedSceneNodes() + val selection = editor.selectionOverlay.getSelectedSceneNodes() logD { "Duplicate ${selection.size} selected objects" } val duplicatedNodes = selection.map { nodeModel -> - val json = EditorState.jsonCodec.encodeToString(nodeModel.nodeData) - val copyData = EditorState.jsonCodec.decodeFromString(json) + val json = KoolEditor.jsonCodec.encodeToString(nodeModel.nodeData) + val copyData = KoolEditor.jsonCodec.decodeFromString(json) sanitizeCopiedNodeIds(listOf(copyData)) val parent = nodeModel.parent @@ -64,14 +66,14 @@ object EditorCopyAndPaste { // update selection via launchOnMainThread so that it is called after node is inserted and components // are created launchOnMainThread { - EditorState.setSelection(duplicatedNodes) + editor.selectionOverlay.setSelection(duplicatedNodes) } } private fun sanitizeCopiedNodeIds(copyData: List) { // todo: support pasting node hierarchies, for now hierarchies are flattened copyData.forEach { - it.nodeId = EditorState.projectModel.nextId() + it.nodeId = editor.projectModel.nextId() it.childNodeIds.clear() } } diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorDefaults.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorDefaults.kt index dd4d56c80..44092e3ba 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorDefaults.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorDefaults.kt @@ -6,6 +6,6 @@ import de.fabmax.kool.math.Vec3d object EditorDefaults { val DEFAULT_LIGHT_POSITION = Vec3d(5.0, 5.0, 5.0) - val DEFAULT_LIGHT_ROTATION = QuatD(-0.09500162, -0.36375725, 0.6792372, -0.46138754) + val DEFAULT_LIGHT_ROTATION = QuatD(0.224144, 0.129410, -0.836516, 0.482963).normed() } \ No newline at end of file diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorState.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorState.kt deleted file mode 100644 index 98636b4f8..000000000 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/EditorState.kt +++ /dev/null @@ -1,126 +0,0 @@ -package de.fabmax.kool.editor - -import de.fabmax.kool.editor.data.* -import de.fabmax.kool.editor.model.EditorProject -import de.fabmax.kool.editor.model.NodeModel -import de.fabmax.kool.editor.model.SceneModel -import de.fabmax.kool.editor.model.SceneNodeModel -import de.fabmax.kool.input.KeyboardInput -import de.fabmax.kool.math.MutableMat4d -import de.fabmax.kool.math.Vec3d -import de.fabmax.kool.math.deg -import de.fabmax.kool.modules.ui2.mutableStateListOf -import de.fabmax.kool.modules.ui2.mutableStateOf -import de.fabmax.kool.util.MdColor -import de.fabmax.kool.util.copy -import de.fabmax.kool.util.logE -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.Json - -object EditorState { - - //val projectModel = PlatformFunctions.loadProjectModel(KoolEditor.instance.paths.projectFile) ?: newProject() - val projectModel = newProject() - - val loadedApp = mutableStateOf(null) - - val activeScene = mutableStateOf(null) - - val selection = mutableStateListOf() - val onSelectionChanged = mutableListOf<(List) -> Unit>() - - val transformMode = mutableStateOf(TransformOrientation.GLOBAL) - - @OptIn(ExperimentalSerializationApi::class) - val jsonCodec = Json { - prettyPrint = true - prettyPrintIndent = " " - } - - fun selectSingle(selectModel: NodeModel?, expandIfShiftIsDown: Boolean = true, toggleSelect: Boolean = true) { - val selectList = selectModel?.let { listOf(it) } ?: emptyList() - - if (toggleSelect && selectModel in selection) { - if (expandIfShiftIsDown && KeyboardInput.isShiftDown) { - reduceSelection(selectList) - } else { - clearSelection() - } - } else if (expandIfShiftIsDown && KeyboardInput.isShiftDown) { - expandSelection(selectList) - } else { - setSelection(selectList) - } - } - - fun clearSelection() = setSelection(emptyList()) - - fun expandSelection(addModels: List) = setSelection(selection.toSet() + addModels.toSet()) - - fun reduceSelection(removeModels: List) = setSelection(selection.toSet() - removeModels.toSet()) - - fun setSelection(selectModels: Collection) { - if (selection != selectModels) { - selection.atomic { - clear() - addAll(selectModels) - } - onSelectionChanged.forEach { it(selection) } - } - } - - fun getSelectedNodes(filter: (NodeModel) -> Boolean = { true }): List { - return selection.copy().filter(filter) - } - - fun getSelectedSceneNodes(filter: (SceneNodeModel) -> Boolean = { true }): List { - return selection.copy().filterIsInstance().filter(filter) - } - - fun newProject() = EditorProject( - ProjectData().apply { - val sceneId = nextId++ - val camId = nextId++ - val boxId = nextId++ - val lightId = nextId++ - sceneNodeIds += sceneId - sceneNodes += SceneNodeData("New Scene", sceneId).apply { - childNodeIds += listOf(camId, boxId, lightId) - components += ScenePropertiesComponentData(cameraNodeId = camId) - components += SceneBackgroundComponentData( - SceneBackgroundData.SingleColor(ColorData(MdColor.GREY toneLin 900)) - ) - } - sceneNodes += SceneNodeData("Camera", camId).apply { - components += CameraComponentData(CameraTypeData.Perspective()) - components += TransformComponentData(TransformData.fromMatrix( - MutableMat4d() - .translate(0.0, 2.5, 5.0) - .rotate(-30.0.deg, Vec3d.X_AXIS) - )) - } - sceneNodes += SceneNodeData("Default Cube", boxId).apply { - components += MeshComponentData(MeshShapeData.Box(Vec3Data(1.0, 1.0, 1.0))) - } - sceneNodes += SceneNodeData("Directional Light", lightId).apply { - components += DiscreteLightComponentData(LightTypeData.Directional()) - components += TransformComponentData(TransformData.fromMatrix( - MutableMat4d() - .translate(3.0, 3.0, 3.0) - .rotate(EditorDefaults.DEFAULT_LIGHT_ROTATION) - )) - } - } - ) - - //fun saveProject() = PlatformFunctions.saveProjectModel(KoolEditor.instance.paths.projectFile) - fun saveProject() { - logE { "saveProject" } - } - - enum class TransformOrientation(val label: String) { - LOCAL("Local"), - PARENT("Parent"), - GLOBAL("Global") - } -} \ No newline at end of file diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/KoolEditor.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/KoolEditor.kt index e09caf98e..4b89a4c68 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/KoolEditor.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/KoolEditor.kt @@ -1,8 +1,6 @@ package de.fabmax.kool.editor -import de.fabmax.kool.ApplicationCallbacks -import de.fabmax.kool.KoolContext -import de.fabmax.kool.LoadableFile +import de.fabmax.kool.* import de.fabmax.kool.editor.actions.DeleteNodeAction import de.fabmax.kool.editor.actions.EditorActions import de.fabmax.kool.editor.actions.SetVisibilityAction @@ -10,6 +8,9 @@ import de.fabmax.kool.editor.api.AppAssets import de.fabmax.kool.editor.api.AppMode import de.fabmax.kool.editor.api.AppState import de.fabmax.kool.editor.components.SsaoComponent +import de.fabmax.kool.editor.data.ProjectData +import de.fabmax.kool.editor.model.EditorProject +import de.fabmax.kool.editor.model.SceneModel import de.fabmax.kool.editor.model.SceneNodeModel import de.fabmax.kool.editor.overlays.GridOverlay import de.fabmax.kool.editor.overlays.SceneObjectsOverlay @@ -17,25 +18,35 @@ import de.fabmax.kool.editor.overlays.SelectionOverlay import de.fabmax.kool.editor.overlays.TransformGizmoOverlay import de.fabmax.kool.editor.ui.EditorUi import de.fabmax.kool.editor.ui.FloatingToolbar -import de.fabmax.kool.fileSystemAssetLoader import de.fabmax.kool.input.InputStack import de.fabmax.kool.input.KeyboardInput import de.fabmax.kool.input.LocalKeyCode import de.fabmax.kool.input.PointerState import de.fabmax.kool.math.RayTest import de.fabmax.kool.modules.ui2.docking.DockLayout +import de.fabmax.kool.modules.ui2.mutableStateOf import de.fabmax.kool.pipeline.ao.AoPipeline import de.fabmax.kool.scene.Node import de.fabmax.kool.scene.PerspectiveCamera import de.fabmax.kool.scene.scene -import de.fabmax.kool.util.Color -import de.fabmax.kool.util.launchOnMainThread -import de.fabmax.kool.util.logW - -class KoolEditor(val projectFiles: ProjectFiles, val ctx: KoolContext) { - +import de.fabmax.kool.util.* +import kotlinx.coroutines.launch +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +suspend fun KoolEditor(projectFiles: ProjectFiles, ctx: KoolContext): KoolEditor { + val projectData = Json.decodeFromString(projectFiles.projectModelFile.read().decodeToString()) + val projectModel = EditorProject(projectData) + return KoolEditor(projectFiles, projectModel, ctx) +} + +class KoolEditor(val projectFiles: ProjectFiles, val projectModel: EditorProject, val ctx: KoolContext) { init { instance = this } + val loadedApp = mutableStateOf(null) + val activeScene = mutableStateOf(null) + val editorInputContext = InputStack.InputHandler("Editor input") val editorCameraTransform = EditorCamTransform(this) private val editorBackgroundScene = scene("editor-camera") { @@ -70,9 +81,11 @@ class KoolEditor(val projectFiles: ProjectFiles, val ctx: KoolContext) { val availableAssets = AvailableAssets(projectFiles) val ui = EditorUi(this) + val transformMode = mutableStateOf(TransformOrientation.GLOBAL) + private val editorAppCallbacks = object : ApplicationCallbacks { override fun onWindowCloseRequest(ctx: KoolContext): Boolean { - EditorState.saveProject() + saveProject() saveEditorConfig() return PlatformFunctions.onWindowCloseRequest(ctx) } @@ -86,8 +99,7 @@ class KoolEditor(val projectFiles: ProjectFiles, val ctx: KoolContext) { } init { - //Assets.defaultLoader = fileSystemAssetLoader(projectFiles.assets) - AppAssets.impl = CachedAppAssets(fileSystemAssetLoader(projectFiles.fileSystem.root)) + AppAssets.impl = CachedAppAssets(fileSystemAssetLoader(projectFiles.assets)) AppState.isInEditorState.set(true) AppState.appModeState.set(AppMode.EDIT) @@ -120,9 +132,7 @@ class KoolEditor(val projectFiles: ProjectFiles, val ctx: KoolContext) { fun editBehaviorSource(behavior: AppBehavior) = editBehaviorSource(behavior.qualifiedName) fun editBehaviorSource(behaviorClassName: String) { - TODO() - //val sourcePath = "${paths.commonSrcPath}/${behaviorClassName.replace('.', '/')}.kt" - //PlatformFunctions.editBehavior(sourcePath) + PlatformFunctions.editBehavior(behaviorClassName) } private fun registerAutoSaveOnFocusLoss() { @@ -130,7 +140,7 @@ class KoolEditor(val projectFiles: ProjectFiles, val ctx: KoolContext) { var wasFocused = false editorContent.onUpdate { if (wasFocused && !ctx.isWindowFocused) { - EditorState.saveProject() + saveProject() } wasFocused = ctx.isWindowFocused } @@ -156,34 +166,34 @@ class KoolEditor(val projectFiles: ProjectFiles, val ctx: KoolContext) { keyCode = LocalKeyCode('C'), filter = InputStack.KEY_FILTER_CTRL_PRESSED ) { - EditorCopyAndPaste.copySelection() + EditorClipboard.copySelection() } editorInputContext.addKeyListener( name = "Paste", keyCode = LocalKeyCode('V'), filter = InputStack.KEY_FILTER_CTRL_PRESSED ) { - EditorCopyAndPaste.paste() + EditorClipboard.paste() } editorInputContext.addKeyListener( name = "Duplicate", keyCode = LocalKeyCode('D'), filter = InputStack.KEY_FILTER_CTRL_PRESSED ) { - EditorCopyAndPaste.duplicateSelection() + EditorClipboard.duplicateSelection() } editorInputContext.addKeyListener( name = "Delete selected objects", keyCode = KeyboardInput.KEY_DEL ) { - DeleteNodeAction(EditorState.getSelectedSceneNodes()).apply() + DeleteNodeAction(selectionOverlay.getSelectedSceneNodes()).apply() } editorInputContext.addKeyListener( name = "Hide selected objects", keyCode = LocalKeyCode('H'), filter = { it.isPressed && !it.isAltDown } ) { - val selection = EditorState.getSelectedSceneNodes() + val selection = selectionOverlay.getSelectedSceneNodes() SetVisibilityAction(selection, selection.any { !it.isVisibleState.value }).apply() } editorInputContext.addKeyListener( @@ -191,7 +201,7 @@ class KoolEditor(val projectFiles: ProjectFiles, val ctx: KoolContext) { keyCode = LocalKeyCode('H'), filter = { it.isPressed && it.isAltDown } ) { - EditorState.activeScene.value?.sceneNodes?.filter { !it.isVisibleState.value } ?.let { nodes -> + activeScene.value?.sceneNodes?.filter { !it.isVisibleState.value } ?.let { nodes -> SetVisibilityAction(nodes, true).apply() } } @@ -246,7 +256,7 @@ class KoolEditor(val projectFiles: ProjectFiles, val ctx: KoolContext) { val rayTest = RayTest() override fun handlePointer(pointerState: PointerState, ctx: KoolContext) { - val sceneModel = EditorState.activeScene.value ?: return + val sceneModel = activeScene.value ?: return val appScene = sceneModel.drawNode val ptr = pointerState.primaryPointer if (ptr.isLeftButtonClicked && !ptr.isConsumed()) { @@ -263,7 +273,7 @@ class KoolEditor(val projectFiles: ProjectFiles, val ctx: KoolContext) { } it = it.parent } - EditorState.selectSingle(selectedNodeModel) + selectionOverlay.selectSingle(selectedNodeModel) } } } @@ -277,18 +287,18 @@ class KoolEditor(val projectFiles: ProjectFiles, val ctx: KoolContext) { editorCameraTransform.addNode(editorOverlay.camera) // dispose old scene + objects - EditorState.projectModel.getCreatedScenes().forEach { sceneModel -> + projectModel.getCreatedScenes().forEach { sceneModel -> ctx.scenes -= sceneModel.drawNode sceneModel.drawNode.removeOffscreenPass(selectionOverlay.selectionPass) } - EditorState.loadedApp.value?.app?.onDispose(ctx) + this.loadedApp.value?.app?.onDispose(ctx) selectionOverlay.selectionPass.disposePipelines() // initialize newly loaded app - loadedApp.app.loadApp(EditorState.projectModel, ctx) + loadedApp.app.loadApp(projectModel, ctx) // add scene objects from new app - EditorState.projectModel.getCreatedScenes().let { newScenes -> + projectModel.getCreatedScenes().let { newScenes -> if (newScenes.size != 1) { logW { "Unsupported number of scene, currently only single scene setups are supported" } } @@ -311,12 +321,12 @@ class KoolEditor(val projectFiles: ProjectFiles, val ctx: KoolContext) { } } - EditorState.loadedApp.set(loadedApp) - if (EditorState.activeScene.value == null) { - EditorState.activeScene.set(EditorState.projectModel.getCreatedScenes().getOrNull(0)) + this.loadedApp.set(loadedApp) + if (activeScene.value == null) { + activeScene.set(projectModel.getCreatedScenes().getOrNull(0)) } - if (EditorState.selection.isEmpty()) { - EditorState.activeScene.value?.let { EditorState.selection.add(it) } + if (selectionOverlay.selection.isEmpty()) { + activeScene.value?.let { selectionOverlay.selection.add(it) } } if (AppState.appMode == AppMode.EDIT) { @@ -350,10 +360,30 @@ class KoolEditor(val projectFiles: ProjectFiles, val ctx: KoolContext) { DockLayout.saveLayout(ui.dock, "editor.ui.layout") } + fun saveProject() { + Assets.launch { + val text = jsonCodec.encodeToString(projectModel.projectData) + projectFiles.projectModelFile.write(text.encodeToByteArray().toBuffer()) + logI { "Saved project model" } + } + } + companion object { lateinit var instance: KoolEditor private set const val TAG_EDITOR_SUPPORT_CONTENT = "%editor-content-hidden" + + @OptIn(ExperimentalSerializationApi::class) + val jsonCodec = Json { + prettyPrint = true + prettyPrintIndent = " " + } + } + + enum class TransformOrientation(val label: String) { + LOCAL("Local"), + PARENT("Parent"), + GLOBAL("Global") } } \ No newline at end of file diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.kt index 6069f6b13..e411ce0b0 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.kt @@ -1,7 +1,6 @@ package de.fabmax.kool.editor import de.fabmax.kool.KoolContext -import de.fabmax.kool.editor.model.EditorProject @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") expect object PlatformFunctions { @@ -9,11 +8,7 @@ expect object PlatformFunctions { fun onWindowCloseRequest(ctx: KoolContext): Boolean - fun editBehavior(behaviorSourcePath: String) - - fun loadProjectModel(path: String): EditorProject? - - fun saveProjectModel(path: String) + fun editBehavior(behaviorClassName: String) suspend fun chooseFilePath(): String? } \ No newline at end of file diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/AddNodeAction.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/AddNodeAction.kt index a4c855d84..1ff7b004a 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/AddNodeAction.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/AddNodeAction.kt @@ -1,6 +1,5 @@ package de.fabmax.kool.editor.actions -import de.fabmax.kool.editor.EditorState import de.fabmax.kool.editor.KoolEditor import de.fabmax.kool.editor.model.SceneNodeModel import de.fabmax.kool.util.launchOnMainThread @@ -23,8 +22,8 @@ class AddNodeAction( override fun undoAction() { addNodeModels.forEach { addNodeModel -> - if (addNodeModel in EditorState.selection) { - EditorState.selection -= addNodeModel + if (addNodeModel in KoolEditor.instance.selectionOverlay.selection) { + KoolEditor.instance.selectionOverlay.selection -= addNodeModel } addNodeModel.sceneModel.removeSceneNode(addNodeModel) } diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/DeleteMaterialAction.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/DeleteMaterialAction.kt index 05aa88c61..bd50b281d 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/DeleteMaterialAction.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/DeleteMaterialAction.kt @@ -1,6 +1,6 @@ package de.fabmax.kool.editor.actions -import de.fabmax.kool.editor.EditorState +import de.fabmax.kool.editor.KoolEditor import de.fabmax.kool.editor.data.MaterialData class DeleteMaterialAction( @@ -8,10 +8,10 @@ class DeleteMaterialAction( ) : EditorAction { override fun doAction() { // todo: collect material users and clear their material - EditorState.projectModel.removeMaterial(materialToDelete) + KoolEditor.instance.projectModel.removeMaterial(materialToDelete) } override fun undoAction() { - EditorState.projectModel.addMaterial(materialToDelete) + KoolEditor.instance.projectModel.addMaterial(materialToDelete) } } \ No newline at end of file diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/DeleteNodeAction.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/DeleteNodeAction.kt index 35d9c5cd3..b65ea422e 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/DeleteNodeAction.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/DeleteNodeAction.kt @@ -1,6 +1,5 @@ package de.fabmax.kool.editor.actions -import de.fabmax.kool.editor.EditorState import de.fabmax.kool.editor.KoolEditor import de.fabmax.kool.editor.model.SceneNodeModel import de.fabmax.kool.util.launchOnMainThread @@ -12,7 +11,7 @@ class DeleteNodeAction( constructor(removeNodeModel: SceneNodeModel): this(listOf(removeNodeModel)) override fun doAction() { - EditorState.selection.removeAll(removeNodeModels) + KoolEditor.instance.selectionOverlay.selection.removeAll(removeNodeModels) removeNodeModels.forEach { it.sceneModel.removeSceneNode(it) } diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/RenameMaterialAction.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/RenameMaterialAction.kt index 471de1a34..36756d69b 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/RenameMaterialAction.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/RenameMaterialAction.kt @@ -1,6 +1,6 @@ package de.fabmax.kool.editor.actions -import de.fabmax.kool.editor.EditorState +import de.fabmax.kool.editor.KoolEditor import de.fabmax.kool.editor.data.MaterialData class RenameMaterialAction( @@ -12,14 +12,14 @@ class RenameMaterialAction( override fun doAction() { materialData.nameState.set(applyName) // re-add material to keep correct order - EditorState.projectModel.removeMaterial(materialData) - EditorState.projectModel.addMaterial(materialData) + KoolEditor.instance.projectModel.removeMaterial(materialData) + KoolEditor.instance.projectModel.addMaterial(materialData) } override fun undoAction() { materialData.nameState.set(undoName) // re-add material to keep correct order - EditorState.projectModel.removeMaterial(materialData) - EditorState.projectModel.addMaterial(materialData) + KoolEditor.instance.projectModel.removeMaterial(materialData) + KoolEditor.instance.projectModel.addMaterial(materialData) } } \ No newline at end of file diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/UpdateMaterialAction.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/UpdateMaterialAction.kt index 89be87449..c20f8b5d6 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/UpdateMaterialAction.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/actions/UpdateMaterialAction.kt @@ -1,6 +1,6 @@ package de.fabmax.kool.editor.actions -import de.fabmax.kool.editor.EditorState +import de.fabmax.kool.editor.KoolEditor import de.fabmax.kool.editor.components.updateMaterial import de.fabmax.kool.editor.data.MaterialData import de.fabmax.kool.editor.data.MaterialShaderData @@ -12,11 +12,11 @@ class UpdateMaterialAction( ) : EditorAction { override fun doAction() { materialData.shaderDataState.set(applyMaterial) - EditorState.projectModel.updateMaterial(materialData) + KoolEditor.instance.projectModel.updateMaterial(materialData) } override fun undoAction() { materialData.shaderDataState.set(undoMaterial) - EditorState.projectModel.updateMaterial(materialData) + KoolEditor.instance.projectModel.updateMaterial(materialData) } } \ No newline at end of file diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/overlays/SceneObjectsOverlay.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/overlays/SceneObjectsOverlay.kt index 992e760d9..072b1a860 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/overlays/SceneObjectsOverlay.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/overlays/SceneObjectsOverlay.kt @@ -1,6 +1,6 @@ package de.fabmax.kool.editor.overlays -import de.fabmax.kool.editor.EditorState +import de.fabmax.kool.editor.KoolEditor import de.fabmax.kool.editor.components.CameraComponent import de.fabmax.kool.editor.components.ContentComponent import de.fabmax.kool.editor.model.SceneModel @@ -223,7 +223,7 @@ class SceneObjectsOverlay : Node("Scene objects overlay") { addNode(groupMesh) onUpdate { - EditorState.activeScene.value?.let { sceneModel -> + KoolEditor.instance.activeScene.value?.let { sceneModel -> addLightInstances(sceneModel) addCameraInstances(sceneModel) addGroupInstances(sceneModel) diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/overlays/SelectionOverlay.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/overlays/SelectionOverlay.kt index fe01fa528..1f9e35403 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/overlays/SelectionOverlay.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/overlays/SelectionOverlay.kt @@ -1,26 +1,32 @@ package de.fabmax.kool.editor.overlays -import de.fabmax.kool.editor.EditorState import de.fabmax.kool.editor.KoolEditor import de.fabmax.kool.editor.model.NodeModel import de.fabmax.kool.editor.model.SceneModel +import de.fabmax.kool.editor.model.SceneNodeModel +import de.fabmax.kool.input.KeyboardInput import de.fabmax.kool.math.Vec2f import de.fabmax.kool.math.Vec2i import de.fabmax.kool.modules.ksl.KslShader import de.fabmax.kool.modules.ksl.KslUnlitShader import de.fabmax.kool.modules.ksl.lang.* +import de.fabmax.kool.modules.ui2.mutableStateListOf import de.fabmax.kool.pipeline.* import de.fabmax.kool.pipeline.FullscreenShaderUtil.fullscreenQuadVertexStage import de.fabmax.kool.pipeline.FullscreenShaderUtil.generateFullscreenQuad import de.fabmax.kool.scene.Mesh import de.fabmax.kool.scene.Node import de.fabmax.kool.util.Color +import de.fabmax.kool.util.copy import de.fabmax.kool.util.launchDelayed import de.fabmax.kool.util.logT import kotlin.math.max class SelectionOverlay(editor: KoolEditor) : Node("Selection overlay") { + val selection = mutableStateListOf() + val onSelectionChanged = mutableListOf<(List) -> Unit>() + val selectionPass = SelectionPass(editor) private val overlayMesh = Mesh(Attribute.POSITIONS, Attribute.TEXTURE_COORDS) private val outlineShader = SelectionOutlineShader(selectionPass.colorTexture) @@ -37,7 +43,7 @@ class SelectionOverlay(editor: KoolEditor) : Node("Selection overlay") { overlayMesh.isVisible = false addNode(overlayMesh) - EditorState.onSelectionChanged += { updateSelection = true } + onSelectionChanged += { updateSelection = true } onUpdate { selectionColor = editor.ui.uiColors.value.primary @@ -52,7 +58,7 @@ class SelectionOverlay(editor: KoolEditor) : Node("Selection overlay") { if (updateSelection) { updateSelection = false prevSelection.clear() - prevSelection += EditorState.selection + prevSelection += selection meshSelection.clear() prevSelection .filter { it !is SceneModel } @@ -67,6 +73,46 @@ class SelectionOverlay(editor: KoolEditor) : Node("Selection overlay") { } } + fun selectSingle(selectModel: NodeModel?, expandIfShiftIsDown: Boolean = true, toggleSelect: Boolean = true) { + val selectList = selectModel?.let { listOf(it) } ?: emptyList() + + if (toggleSelect && selectModel in selection) { + if (expandIfShiftIsDown && KeyboardInput.isShiftDown) { + reduceSelection(selectList) + } else { + clearSelection() + } + } else if (expandIfShiftIsDown && KeyboardInput.isShiftDown) { + expandSelection(selectList) + } else { + setSelection(selectList) + } + } + + fun clearSelection() = setSelection(emptyList()) + + fun expandSelection(addModels: List) = setSelection(selection.toSet() + addModels.toSet()) + + fun reduceSelection(removeModels: List) = setSelection(selection.toSet() - removeModels.toSet()) + + fun setSelection(selectModels: Collection) { + if (selection != selectModels) { + selection.atomic { + clear() + addAll(selectModels) + } + onSelectionChanged.forEach { it(selection) } + } + } + + fun getSelectedNodes(filter: (NodeModel) -> Boolean = { true }): List { + return selection.copy().filter(filter) + } + + fun getSelectedSceneNodes(filter: (SceneNodeModel) -> Boolean = { true }): List { + return selection.copy().filterIsInstance().filter(filter) + } + fun invalidateSelection() { prevSelection.clear() meshSelection.clear() @@ -227,5 +273,4 @@ class SelectionOverlay(editor: KoolEditor) : Node("Selection overlay") { } } } - } \ No newline at end of file diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/overlays/TransformGizmoOverlay.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/overlays/TransformGizmoOverlay.kt index fefb0d3c9..066eb44d1 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/overlays/TransformGizmoOverlay.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/overlays/TransformGizmoOverlay.kt @@ -1,7 +1,6 @@ package de.fabmax.kool.editor.overlays import de.fabmax.kool.KoolContext -import de.fabmax.kool.editor.EditorState import de.fabmax.kool.editor.KoolEditor import de.fabmax.kool.editor.actions.SetTransformAction import de.fabmax.kool.editor.data.TransformData @@ -299,7 +298,7 @@ class TransformGizmoOverlay(private val editor: KoolEditor) : Node("Transform gi // determine gizmo orientation globalGizmoOrientation.set(QuatD.IDENTITY) - if (EditorState.transformMode.value == EditorState.TransformOrientation.LOCAL) { + if (editor.transformMode.value == KoolEditor.TransformOrientation.LOCAL) { if (selection.size == 1) { // use local orientation of single selected object selection[0].nodeModel.drawNode.modelMatF.decompose(rotation = globalGizmoOrientation) @@ -309,7 +308,7 @@ class TransformGizmoOverlay(private val editor: KoolEditor) : Node("Transform gi // objects have the same parent (or global orientation if not) globalGizmoOrientation.set(parentOrientation) } - } else if (EditorState.transformMode.value == EditorState.TransformOrientation.PARENT && isSameParent) { + } else if (editor.transformMode.value == KoolEditor.TransformOrientation.PARENT && isSameParent) { // use parent orientation if selected objects all have the same parent globalGizmoOrientation.set(parentOrientation) } diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/BehaviorBrowser.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/BehaviorBrowser.kt index 7410c459c..118520a14 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/BehaviorBrowser.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/BehaviorBrowser.kt @@ -1,6 +1,5 @@ package de.fabmax.kool.editor.ui -import de.fabmax.kool.editor.EditorState import de.fabmax.kool.editor.KoolEditor import de.fabmax.kool.modules.ui2.UiScope @@ -14,7 +13,7 @@ class BehaviorBrowser(ui: EditorUi) : BrowserPanel("Behavior Browser", IconMap.m traversedPaths += "/behaviors" scriptDir.children.clear() - EditorState.loadedApp.value?.behaviorClasses?.values?.forEach { + editor.loadedApp.value?.behaviorClasses?.values?.forEach { val scriptItem = browserItems.getOrPut("/behaviors/${it.qualifiedName}") { BrowserBehaviorItem(1, it) } diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/BehaviorEditor.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/BehaviorEditor.kt index c0809bb1f..2808139fc 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/BehaviorEditor.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/BehaviorEditor.kt @@ -1,7 +1,6 @@ package de.fabmax.kool.editor.ui import de.fabmax.kool.editor.BehaviorProperty -import de.fabmax.kool.editor.EditorState import de.fabmax.kool.editor.KoolEditor import de.fabmax.kool.editor.actions.SetBehaviorPropertyAction import de.fabmax.kool.editor.components.BehaviorComponent @@ -42,7 +41,7 @@ class BehaviorEditor(component: BehaviorComponent) : ComponentEditor(var component: T ) : Com get() = component.nodeModel open val sceneModel: SceneModel - get() = requireNotNull(EditorState.activeScene.value) + get() = requireNotNull(KoolEditor.instance.activeScene.value) protected fun removeComponent() { RemoveComponentAction(nodeModel, component).apply() diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/FloatingToolbar.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/FloatingToolbar.kt index e467d0c30..de8f31ce9 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/FloatingToolbar.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/FloatingToolbar.kt @@ -1,6 +1,5 @@ package de.fabmax.kool.editor.ui -import de.fabmax.kool.editor.EditorState import de.fabmax.kool.editor.overlays.TransformGizmoOverlay import de.fabmax.kool.modules.ui2.* import de.fabmax.kool.util.launchOnMainThread @@ -10,7 +9,7 @@ class FloatingToolbar(val ui: EditorUi) : Composable { private val actionMode = mutableStateOf(EditActionMode.NONE) init { - EditorState.onSelectionChanged += { + ui.editor.selectionOverlay.onSelectionChanged += { updateGizmo() } } @@ -18,7 +17,7 @@ class FloatingToolbar(val ui: EditorUi) : Composable { private fun updateGizmo() { if (actionMode.value in transformTools) { launchOnMainThread { - ui.editor.gizmoOverlay.setTransformObjects(EditorState.getSelectedSceneNodes()) + ui.editor.gizmoOverlay.setTransformObjects(ui.editor.selectionOverlay.getSelectedSceneNodes()) ui.editor.gizmoOverlay.transformMode = when (actionMode.value) { EditActionMode.MOVE -> TransformGizmoOverlay.TransformMode.MOVE EditActionMode.ROTATE -> TransformGizmoOverlay.TransformMode.ROTATE diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/MaterialBrowser.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/MaterialBrowser.kt index c63f180e1..4c010e2e2 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/MaterialBrowser.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/MaterialBrowser.kt @@ -1,6 +1,5 @@ package de.fabmax.kool.editor.ui -import de.fabmax.kool.editor.EditorState import de.fabmax.kool.editor.actions.DeleteMaterialAction import de.fabmax.kool.modules.ui2.UiScope @@ -14,7 +13,7 @@ class MaterialBrowser(ui: EditorUi) : BrowserPanel("Material Browser", IconMap.m traversedPaths += "/materials" materialDir.children.clear() - EditorState.projectModel.materials.use().forEach { + editor.projectModel.materials.use().forEach { val materialItem = browserItems.getOrPut("/materials/${it.name}") { BrowserMaterialItem(1, it) } diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/MaterialEditor.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/MaterialEditor.kt index 4a5ffca47..8e51cfc9c 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/MaterialEditor.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/MaterialEditor.kt @@ -1,6 +1,5 @@ package de.fabmax.kool.editor.ui -import de.fabmax.kool.editor.EditorState import de.fabmax.kool.editor.KoolEditor import de.fabmax.kool.editor.actions.RenameMaterialAction import de.fabmax.kool.editor.actions.SetMaterialAction @@ -348,7 +347,7 @@ class MaterialEditor(component: MaterialComponent) : ComponentEditor + KoolEditor.instance.projectModel.materials.use().forEachIndexed { i, material -> if (component.isHoldingMaterial(material)) { index = i + 2 } @@ -391,7 +390,7 @@ class MaterialEditor(component: MaterialComponent) : ComponentEditor "Scene Properties" @@ -43,7 +42,7 @@ class ObjectPropertyEditor(ui: EditorUi) : EditorPanel("Object Properties", Icon .padding(horizontal = sizes.gap) if (selectedObject == null) { - val n = EditorState.selection.size + val n = KoolEditor.instance.selectionOverlay.selection.size val txt = if (n == 0) "Nothing selected" else "$n objects selected" Text(txt) { modifier @@ -165,38 +164,38 @@ class ObjectPropertyEditor(ui: EditorUi) : EditorPanel("Object Properties", Icon createComponent(target)?.let { AddComponentAction(target, it).apply() } } - object AddSsaoComponent : ComponentAdder("Screen-space Ambient Occlusion") { + data object AddSsaoComponent : ComponentAdder("Screen-space Ambient Occlusion") { override fun createComponent(target: NodeModel): SsaoComponent = SsaoComponent(target as SceneModel) override fun accept(nodeModel: NodeModel) = nodeModel is SceneModel && !nodeModel.hasComponent() } - object AddCameraComponent : ComponentAdder("Camera") { + data object AddCameraComponent : ComponentAdder("Camera") { override fun createComponent(target: NodeModel): CameraComponent = CameraComponent(target as SceneNodeModel) override fun accept(nodeModel: NodeModel) = nodeModel is SceneNodeModel && !nodeModel.hasComponent() } - object AddLightComponent : ComponentAdder("Light") { + data object AddLightComponent : ComponentAdder("Light") { override fun createComponent(target: NodeModel): DiscreteLightComponent = DiscreteLightComponent(target as SceneNodeModel) override fun accept(nodeModel: NodeModel) = nodeModel is SceneNodeModel && !nodeModel.hasComponent() } - object AddShadowMapComponent : ComponentAdder("Shadow") { + data object AddShadowMapComponent : ComponentAdder("Shadow") { override fun createComponent(target: NodeModel): ShadowMapComponent = ShadowMapComponent(target as SceneNodeModel) override fun accept(nodeModel: NodeModel) = nodeModel.hasComponent() && !nodeModel.hasComponent() } - object AddMeshComponent : ComponentAdder("Mesh") { + data object AddMeshComponent : ComponentAdder("Mesh") { override fun createComponent(target: NodeModel): MeshComponent = MeshComponent(target as SceneNodeModel) override fun accept(nodeModel: NodeModel) = nodeModel is SceneNodeModel && !nodeModel.hasComponent() } - object AddModelComponent : ComponentAdder("Model") { + data object AddModelComponent : ComponentAdder("Model") { override fun accept(nodeModel: NodeModel) = nodeModel is SceneNodeModel && !nodeModel.hasComponent() @@ -214,17 +213,17 @@ class ObjectPropertyEditor(ui: EditorUi) : EditorPanel("Object Properties", Icon } } - object AddMaterialComponent : ComponentAdder("Material") { + data object AddMaterialComponent : ComponentAdder("Material") { override fun createComponent(target: NodeModel): MaterialComponent = MaterialComponent(target as SceneNodeModel) override fun accept(nodeModel: NodeModel) = !nodeModel.hasComponent() && (nodeModel.hasComponent() || nodeModel.hasComponent()) } - object AddScriptComponent : ComponentAdder("Behavior") { + data object AddScriptComponent : ComponentAdder("Behavior") { override fun accept(nodeModel: NodeModel) = true override fun addMenuItems(target: NodeModel, parentMenu: SubMenuItem) { - val scriptClasses = EditorState.loadedApp.value?.behaviorClasses?.values ?: emptyList() + val scriptClasses = KoolEditor.instance.loadedApp.value?.behaviorClasses?.values ?: emptyList() if (scriptClasses.isNotEmpty()) { parentMenu.subMenu(name) { scriptClasses.forEach { script -> diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/SceneObjectTree.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/SceneObjectTree.kt index 81b2a2197..c479463b9 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/SceneObjectTree.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/SceneObjectTree.kt @@ -1,6 +1,9 @@ package de.fabmax.kool.editor.ui -import de.fabmax.kool.editor.* +import de.fabmax.kool.editor.AppReloadListener +import de.fabmax.kool.editor.AssetItem +import de.fabmax.kool.editor.EditorDefaults +import de.fabmax.kool.editor.KoolEditor import de.fabmax.kool.editor.actions.AddNodeAction import de.fabmax.kool.editor.actions.DeleteNodeAction import de.fabmax.kool.editor.actions.MoveSceneNodeAction @@ -27,6 +30,9 @@ class SceneObjectTree(val sceneBrowser: SceneBrowser) : Composable { private val dndCtx: DragAndDropContext> get() = sceneBrowser.dnd.dndContext + private val editor: KoolEditor + get() = KoolEditor.instance + init { sceneBrowser.editor.appLoader.appReloadListeners += AppReloadListener { nodeTreeItemMap.clear() @@ -38,8 +44,8 @@ class SceneObjectTree(val sceneBrowser: SceneBrowser) : Composable { } private fun addNewMesh(parent: SceneObjectItem, meshShape: MeshShapeData) { - val parentScene = EditorState.activeScene.value ?: return - val id = EditorState.projectModel.nextId() + val parentScene = editor.activeScene.value ?: return + val id = editor.projectModel.nextId() val nodeData = SceneNodeData("${meshShape.name}-$id", id) nodeData.components += MeshComponentData(meshShape) nodeData.components += MaterialComponentData(-1) @@ -48,8 +54,8 @@ class SceneObjectTree(val sceneBrowser: SceneBrowser) : Composable { } private fun addNewModel(parent: SceneObjectItem, modelAsset: AssetItem) { - val parentScene = EditorState.activeScene.value ?: return - val id = EditorState.projectModel.nextId() + val parentScene = editor.activeScene.value ?: return + val id = editor.projectModel.nextId() val nodeData = SceneNodeData(modelAsset.name, id) nodeData.components += ModelComponentData(modelAsset.path) val mesh = SceneNodeModel(nodeData, parent.nodeModel, parentScene) @@ -57,8 +63,8 @@ class SceneObjectTree(val sceneBrowser: SceneBrowser) : Composable { } private fun addNewLight(parent: SceneObjectItem, lightType: LightTypeData) { - val parentScene = EditorState.activeScene.value ?: return - val id = EditorState.projectModel.nextId() + val parentScene = editor.activeScene.value ?: return + val id = editor.projectModel.nextId() val nodeData = SceneNodeData("${lightType.name}-$id", id) nodeData.components += DiscreteLightComponentData(lightType) val light = SceneNodeModel(nodeData, parent.nodeModel, parentScene) @@ -73,8 +79,8 @@ class SceneObjectTree(val sceneBrowser: SceneBrowser) : Composable { } private fun addEmptyNode(parent: SceneObjectItem) { - val parentScene = EditorState.activeScene.value ?: return - val id = EditorState.projectModel.nextId() + val parentScene = editor.activeScene.value ?: return + val id = editor.projectModel.nextId() val nodeData = SceneNodeData("Empty-$id", id) val empty = SceneNodeModel(nodeData, parent.nodeModel, parentScene) AddNodeAction(empty).apply() @@ -92,11 +98,11 @@ class SceneObjectTree(val sceneBrowser: SceneBrowser) : Composable { } override fun UiScope.compose() { - EditorState.activeScene.use() + editor.activeScene.use() if (!isTreeValid.use()) { treeItems.clear() - EditorState.projectModel.getCreatedScenes().forEach { sceneModel -> + editor.projectModel.getCreatedScenes().forEach { sceneModel -> sceneModel.drawNode.let { treeItems.appendNode(sceneModel, it, sceneModel, 0) } @@ -155,7 +161,7 @@ class SceneObjectTree(val sceneBrowser: SceneBrowser) : Composable { item(modelAsset.name) { addNewModel(it, modelAsset) } } } - EditorState.activeScene.value?.let { sceneModel -> + editor.activeScene.value?.let { sceneModel -> if (sceneModel.drawNode.lighting.lights.size < sceneModel.maxNumLightsState.value) { subMenu("Light") { item("Directional") { addNewLight(it, LightTypeData.Directional()) } @@ -177,7 +183,7 @@ class SceneObjectTree(val sceneBrowser: SceneBrowser) : Composable { .margin(horizontal = sizes.smallGap) .onClick { if (it.pointer.isLeftButtonClicked) { - EditorState.selectSingle(item.nodeModel) + editor.selectionOverlay.selectSingle(item.nodeModel) if (it.pointer.leftButtonRepeatedClickCount == 2 && item.isExpandable) { item.toggleExpanded() } @@ -254,7 +260,7 @@ class SceneObjectTree(val sceneBrowser: SceneBrowser) : Composable { } } - val fgColor = if (item.nodeModel in EditorState.selection.use()) { + val fgColor = if (item.nodeModel in editor.selectionOverlay.selection.use()) { if (item.type != SceneObjectType.NON_MODEL_NODE) { colors.primary } else { diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/SceneView.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/SceneView.kt index 4e3998283..901db5548 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/SceneView.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/SceneView.kt @@ -1,6 +1,6 @@ package de.fabmax.kool.editor.ui -import de.fabmax.kool.editor.EditorState +import de.fabmax.kool.editor.KoolEditor import de.fabmax.kool.modules.ui2.* import de.fabmax.kool.modules.ui2.docking.DockNodeLeaf import de.fabmax.kool.scene.Scene @@ -80,10 +80,10 @@ class SceneView(ui: EditorUi) : EditorPanel("Scene View", IconMap.medium.CAMERA, modifier .width(sizes.baseSize * 2.5f) .alignY(AlignmentY.Center) - .items(EditorState.TransformOrientation.entries.map { it.label }) - .selectedIndex(EditorState.transformMode.use().ordinal) + .items(KoolEditor.TransformOrientation.entries.map { it.label }) + .selectedIndex(editor.transformMode.use().ordinal) .onItemSelected { i -> - EditorState.transformMode.set(EditorState.TransformOrientation.entries[i]) + editor.transformMode.set(KoolEditor.TransformOrientation.entries[i]) } } } diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/TransformEditor.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/TransformEditor.kt index fbbeb092e..eddd488c9 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/TransformEditor.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/TransformEditor.kt @@ -1,6 +1,6 @@ package de.fabmax.kool.editor.ui -import de.fabmax.kool.editor.EditorState +import de.fabmax.kool.editor.KoolEditor import de.fabmax.kool.editor.actions.SetTransformAction import de.fabmax.kool.editor.components.TransformComponent import de.fabmax.kool.editor.data.TransformData @@ -44,7 +44,7 @@ class TransformEditor(component: TransformComponent) : ComponentEditor { + KoolEditor.TransformOrientation.LOCAL -> { // local orientation doesn't make much sense for the transform editor -> use default (parent) // frame instead transformData @@ -238,11 +238,11 @@ class TransformEditor(component: TransformComponent) : ComponentEditor { + KoolEditor.TransformOrientation.PARENT -> { // component transform data already is in parent frame -> no further transforming needed transformData } - EditorState.TransformOrientation.GLOBAL -> { + KoolEditor.TransformOrientation.GLOBAL -> { val parent = component.nodeModel.parent if (parent is SceneNodeModel) { TransformData.fromMatrix(component.nodeModel.drawNode.modelMatD) @@ -256,14 +256,14 @@ class TransformEditor(component: TransformComponent) : ComponentEditor { + return when (KoolEditor.instance.transformMode.value) { + KoolEditor.TransformOrientation.LOCAL -> { transformData } - EditorState.TransformOrientation.PARENT -> { + KoolEditor.TransformOrientation.PARENT -> { transformData } - EditorState.TransformOrientation.GLOBAL -> { + KoolEditor.TransformOrientation.GLOBAL -> { val parent = component.nodeModel.parent if (parent is SceneNodeModel) { val globalToParent = parent.drawNode.invModelMatD diff --git a/kool-editor/src/desktopMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.desktop.kt b/kool-editor/src/desktopMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.desktop.kt index 7e9f81755..f7ff7b9ab 100644 --- a/kool-editor/src/desktopMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.desktop.kt +++ b/kool-editor/src/desktopMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.desktop.kt @@ -1,17 +1,16 @@ package de.fabmax.kool.editor import de.fabmax.kool.* -import de.fabmax.kool.editor.data.ProjectData -import de.fabmax.kool.editor.model.EditorProject import de.fabmax.kool.editor.ui.OkCancelBrowsePathDialog +import de.fabmax.kool.modules.filesystem.PhysicalFileSystem +import de.fabmax.kool.modules.filesystem.getDirectoryOrNull import de.fabmax.kool.platform.Lwjgl3Context import de.fabmax.kool.util.logD import de.fabmax.kool.util.logE import de.fabmax.kool.util.logW -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json import java.io.File import java.io.IOException +import kotlin.io.path.pathString @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") actual object PlatformFunctions { @@ -53,25 +52,12 @@ actual object PlatformFunctions { return true } - actual fun loadProjectModel(path: String): EditorProject? { - val projFile = File(path) - return try { - val projData = Json.decodeFromString(projFile.readText()) - EditorProject(projData) - } catch (e: Exception) { - logW { "Project not found at ${projFile.absolutePath}, creating new empty project" } - return null - } - } - - actual fun saveProjectModel(path: String) { - val modelPath = File(path) - modelPath.parentFile.mkdirs() - modelPath.writeText(EditorState.jsonCodec.encodeToString(EditorState.projectModel.projectData)) - logD { "Saved project to ${modelPath.absolutePath}" } - } + actual fun editBehavior(behaviorClassName: String) { + val srcDir = KoolEditor.instance.projectFiles.fileSystem.getDirectoryOrNull("src/commonMain/kotlin") + val srcPath = (srcDir as? PhysicalFileSystem.Directory?)?.physPath?.pathString ?: "" + val classPath = behaviorClassName.replace('.', '/') + val behaviorSourcePath = "${srcPath}/${classPath}.kt" - actual fun editBehavior(behaviorSourcePath: String) { val behaviorPath = File(behaviorSourcePath).canonicalPath logD { "Edit behavior source: $behaviorPath" } diff --git a/kool-editor/src/jsMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.js.kt b/kool-editor/src/jsMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.js.kt index 30d4e6fe5..29d7c5608 100644 --- a/kool-editor/src/jsMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.js.kt +++ b/kool-editor/src/jsMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.js.kt @@ -3,18 +3,15 @@ package de.fabmax.kool.editor import de.fabmax.kool.Assets import de.fabmax.kool.KoolContext import de.fabmax.kool.NativeAssetLoader -import de.fabmax.kool.editor.model.EditorProject import de.fabmax.kool.util.logE @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") actual object PlatformFunctions { - private var projectModel: EditorProject? = null var loadedApp: LoadedApp? = null - suspend fun initPlatform(projModelPath: String, loadedApp: LoadedApp) { + fun initPlatform(loadedApp: LoadedApp) { Assets.defaultLoader = NativeAssetLoader(".") - projectModel = EditorProject.loadFromAssets(projModelPath) this.loadedApp = loadedApp } @@ -22,18 +19,10 @@ actual object PlatformFunctions { actual fun onWindowCloseRequest(ctx: KoolContext): Boolean = true - actual fun editBehavior(behaviorSourcePath: String) { + actual fun editBehavior(behaviorClassName: String) { logE { "Source editing is not available on JS. Download project and use the JVM variant." } } - actual fun loadProjectModel(path: String): EditorProject? { - return projectModel - } - - actual fun saveProjectModel(path: String) { - logE { "saveProjectModel not yet implemented" } - } - actual suspend fun chooseFilePath(): String? { logE { "chooseFilePath() not available on JS" } return null