[Editor] Enable main view's tab selection on change | implement line selection from compilation logs on opened tabs
This commit is contained in:
@@ -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) }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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<GameMap, UndoableScope>({ 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 <reified T, S : Scope> openItem(
|
||||
findItem: (item: T) -> Boolean,
|
||||
updateExistingScope: (S) -> Unit = {},
|
||||
createItem: () -> Pair<Scope, T>
|
||||
) {
|
||||
@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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<UpdateCompilationLogEvent> {
|
||||
compilationLogItem.expanded = true
|
||||
}
|
||||
|
||||
subscribe<SelectMainViewTabEvent> { 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) {
|
||||
|
||||
Reference in New Issue
Block a user