From c9716d438d04e93df06d6c4f317780cc19dba4ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Przemys=C5=82aw=20Pluta?= Date: Thu, 25 Feb 2021 12:04:51 +0100 Subject: [PATCH] [Editor] Enable main view's tab selection on change | implement line selection from compilation logs on opened tabs --- .../base/editor/code/model/CodeScope.kt | 17 ++-- .../base/editor/code/view/CodeEditorView.kt | 2 +- .../editor/event/SelectMainViewTabEvent.kt | 7 ++ .../editor/main/controller/MainController.kt | 34 +++++++- .../base/editor/main/view/MainView.kt | 85 ++++++++++--------- 5 files changed, 94 insertions(+), 51 deletions(-) create mode 100644 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/event/SelectMainViewTabEvent.kt diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/model/CodeScope.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/model/CodeScope.kt index c4d44e13..8502775a 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/model/CodeScope.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/model/CodeScope.kt @@ -1,17 +1,18 @@ package com.bartlomiejpluta.base.editor.code.model import com.bartlomiejpluta.base.editor.command.context.UndoableScope -import tornadofx.toProperty -class CodeScope(line: Int, column: Int) : UndoableScope() { - private val requestCaretPositionProperty = (line to column).toProperty() +class CodeScope(private var line: Int, private var column: Int) : UndoableScope() { + var caretDisplacementRequestListener: ((line: Int, column: Int) -> Unit)? = null + set(value) { + field = value + field?.let { it(line, column) } + } fun setCaretPosition(line: Int, column: Int) { - requestCaretPositionProperty.value = line to column - } + this.line = line + this.column = column - fun addCaretDisplacementRequestListener(listener: (line: Int, column: Int) -> Unit) { - requestCaretPositionProperty.addListener { _, _, position -> listener(position.first, position.second) } - listener(requestCaretPositionProperty.value.first, requestCaretPositionProperty.value.second) + caretDisplacementRequestListener?.let { it(line, column) } } } \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/CodeEditorView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/CodeEditorView.kt index 0bb504ce..6a5f61da 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/CodeEditorView.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/CodeEditorView.kt @@ -28,7 +28,7 @@ class CodeEditorView : View() { private val editor = CodeEditor(highlighter, codeVM.codeProperty) init { - scope.addCaretDisplacementRequestListener { line, column -> + scope.caretDisplacementRequestListener = { line, column -> editor.setCaretPosition(line, column) } } diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/event/SelectMainViewTabEvent.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/event/SelectMainViewTabEvent.kt new file mode 100644 index 00000000..d1ea8532 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/event/SelectMainViewTabEvent.kt @@ -0,0 +1,7 @@ +package com.bartlomiejpluta.base.editor.event + +import tornadofx.EventBus +import tornadofx.FXEvent +import tornadofx.Scope + +class SelectMainViewTabEvent(val targetScope: Scope) : FXEvent(EventBus.RunOn.ApplicationThread) \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/controller/MainController.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/controller/MainController.kt index 486152bd..e2231dd0 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/controller/MainController.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/controller/MainController.kt @@ -6,6 +6,7 @@ import com.bartlomiejpluta.base.editor.code.model.CodeScope import com.bartlomiejpluta.base.editor.code.model.FileSystemNode import com.bartlomiejpluta.base.editor.code.viewmodel.CodeVM import com.bartlomiejpluta.base.editor.command.context.UndoableScope +import com.bartlomiejpluta.base.editor.event.SelectMainViewTabEvent import com.bartlomiejpluta.base.editor.image.view.importing.ImportImageFragment import com.bartlomiejpluta.base.editor.image.viewmodel.ImageAssetDataVM import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset @@ -76,24 +77,49 @@ class MainController : Controller() { } fun openMap(uid: String) { - if (openItems.count { (_, item) -> item is GameMap && item.uid == uid } == 0) { + openItem({ it.uid == uid }) { val map = projectContext.loadMap(uid) val vm = GameMapVM(map) val scope = UndoableScope() setInScope(vm, scope) - openItems[scope] = map + scope to map } } fun openScript(fsNode: FileSystemNode, line: Int = 1, column: Int = 1) { - if (openItems.count { (_, item) -> item is Code && item.file.absolutePath == fsNode.file.absolutePath } == 0) { + val findScript = { script: Code -> script.file.absolutePath == fsNode.file.absolutePath } + val updateExistingScope = { scope: CodeScope -> scope.setCaretPosition(line, column) } + + openItem(findScript, updateExistingScope) { val code = projectContext.loadScript(fsNode.fileProperty) val vm = CodeVM(code) val scope = CodeScope(line, column) setInScope(vm, scope) - openItems[scope] = code + scope to code + } + } + + private inline fun openItem( + findItem: (item: T) -> Boolean, + updateExistingScope: (S) -> Unit = {}, + createItem: () -> Pair + ) { + @Suppress("UNCHECKED_CAST") + val pair = openItems.entries + .filter { (_, item) -> item is T } + .map { (scope, item) -> (scope as S) to (item as T) } + .firstOrNull { (_, item) -> findItem(item) } + + if (pair == null) { + val (scope, item) = createItem() + openItems[scope] = item + fire(SelectMainViewTabEvent(scope)) + } else { + val scope = pair.first + updateExistingScope(scope) + fire(SelectMainViewTabEvent(scope)) } } diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/view/MainView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/view/MainView.kt index bf73b22b..b8f653a1 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/view/MainView.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/view/MainView.kt @@ -6,6 +6,7 @@ import com.bartlomiejpluta.base.editor.code.view.CodeEditorFragment import com.bartlomiejpluta.base.editor.code.view.CompilerLogsView import com.bartlomiejpluta.base.editor.code.view.ScriptFilesView import com.bartlomiejpluta.base.editor.code.viewmodel.CodeVM +import com.bartlomiejpluta.base.editor.event.SelectMainViewTabEvent import com.bartlomiejpluta.base.editor.event.UpdateCompilationLogEvent import com.bartlomiejpluta.base.editor.main.controller.MainController import com.bartlomiejpluta.base.editor.map.model.map.GameMap @@ -32,6 +33,45 @@ class MainView : View("BASE Game Editor") { private var compilationLogItem: DrawerItem by singleAssign() + private val tabPane = tabpane { + + // FIXME + // For some reason the plain object binding does not work between tabs and mainController.openItems. + // Because of that, the cache openTabs map has been created and the binding is done by following listener: + mainController.openItems.addListener(MapChangeListener { + when { + it.wasAdded() -> createTab(it.key, it.valueAdded).let { tab -> + openTabs[it.key] = tab + tabs += tab + } + + it.wasRemoved() -> { + val tab = openTabs[it.key] + openTabs.remove(it.key) + + // FIXME + // This ugly hack prevents from double-firing the CLOSED EVENT + // when user tries to close tab from the UI (cross sign). + // When user clicks the close button, the event is fired automatically, + // which removes the item from mainController.openItems. + // Right here, this listener would fire the event once again, + // so essentially we need to check if the tab exists in TabPane.tabs + // (which means the tab wasn't be closed from UI). If so, we need to fire + // the event manually since it won't be fired by TabPane. + // Otherwise, if the tab does not exist in tabs anymore, the tab + // was closed from UI and likely the event has been fired automatically. + // The same goes for the TAB_CLOSE_REQUEST event. + if (tab in tabs) { + Event.fireEvent(tab, Event(Tab.CLOSED_EVENT)) + Event.fireEvent(tab, Event(Tab.TAB_CLOSE_REQUEST_EVENT)) + } + + tabs -= tab + } + } + }) + } + init { projectContext.projectProperty.addListener { _, _, project -> val projectName = project?.let { " :: ${it.name} (${it.sourceDirectory.absolutePath})" } ?: "" @@ -41,49 +81,18 @@ class MainView : View("BASE Game Editor") { subscribe { compilationLogItem.expanded = true } + + subscribe { event -> + openTabs[event.targetScope]?.let { + tabPane.selectionModel.select(it) + } + } } override val root = borderpane { top = mainMenuView.root - center = tabpane { - - // FIXME - // For some reason the plain object binding does not work between tabs and mainController.openItems. - // Because of that, the cache openTabs map has been created and the binding is done by following listener: - mainController.openItems.addListener(MapChangeListener { - when { - it.wasAdded() -> createTab(it.key, it.valueAdded).let { tab -> - openTabs[it.key] = tab - tabs += tab - } - - it.wasRemoved() -> { - val tab = openTabs[it.key] - openTabs.remove(it.key) - - // FIXME - // This ugly hack prevents from double-firing the CLOSED EVENT - // when user tries to close tab from the UI (cross sign). - // When user clicks the close button, the event is fired automatically, - // which removes the item from mainController.openItems. - // Right here, this listener would fire the event once again, - // so essentially we need to check if the tab exists in TabPane.tabs - // (which means the tab wasn't be closed from UI). If so, we need to fire - // the event manually since it won't be fired by TabPane. - // Otherwise, if the tab does not exist in tabs anymore, the tab - // was closed from UI and likely the event has been fired automatically. - // The same goes for the TAB_CLOSE_REQUEST event. - if (tab in tabs) { - Event.fireEvent(tab, Event(Tab.CLOSED_EVENT)) - Event.fireEvent(tab, Event(Tab.TAB_CLOSE_REQUEST_EVENT)) - } - - tabs -= tab - } - } - }) - } + center = tabPane left = drawer(multiselect = true) { item("Code", expanded = false) {