[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
|
package com.bartlomiejpluta.base.editor.code.model
|
||||||
|
|
||||||
import com.bartlomiejpluta.base.editor.command.context.UndoableScope
|
import com.bartlomiejpluta.base.editor.command.context.UndoableScope
|
||||||
import tornadofx.toProperty
|
|
||||||
|
|
||||||
class CodeScope(line: Int, column: Int) : UndoableScope() {
|
class CodeScope(private var line: Int, private var column: Int) : UndoableScope() {
|
||||||
private val requestCaretPositionProperty = (line to column).toProperty()
|
var caretDisplacementRequestListener: ((line: Int, column: Int) -> Unit)? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
field?.let { it(line, column) }
|
||||||
|
}
|
||||||
|
|
||||||
fun setCaretPosition(line: Int, column: Int) {
|
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) {
|
caretDisplacementRequestListener?.let { it(line, column) }
|
||||||
requestCaretPositionProperty.addListener { _, _, position -> listener(position.first, position.second) }
|
|
||||||
listener(requestCaretPositionProperty.value.first, requestCaretPositionProperty.value.second)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ class CodeEditorView : View() {
|
|||||||
private val editor = CodeEditor(highlighter, codeVM.codeProperty)
|
private val editor = CodeEditor(highlighter, codeVM.codeProperty)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
scope.addCaretDisplacementRequestListener { line, column ->
|
scope.caretDisplacementRequestListener = { line, column ->
|
||||||
editor.setCaretPosition(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.model.FileSystemNode
|
||||||
import com.bartlomiejpluta.base.editor.code.viewmodel.CodeVM
|
import com.bartlomiejpluta.base.editor.code.viewmodel.CodeVM
|
||||||
import com.bartlomiejpluta.base.editor.command.context.UndoableScope
|
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.view.importing.ImportImageFragment
|
||||||
import com.bartlomiejpluta.base.editor.image.viewmodel.ImageAssetDataVM
|
import com.bartlomiejpluta.base.editor.image.viewmodel.ImageAssetDataVM
|
||||||
import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset
|
import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset
|
||||||
@@ -76,24 +77,49 @@ class MainController : Controller() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun openMap(uid: String) {
|
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 map = projectContext.loadMap(uid)
|
||||||
val vm = GameMapVM(map)
|
val vm = GameMapVM(map)
|
||||||
val scope = UndoableScope()
|
val scope = UndoableScope()
|
||||||
setInScope(vm, scope)
|
setInScope(vm, scope)
|
||||||
|
|
||||||
openItems[scope] = map
|
scope to map
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openScript(fsNode: FileSystemNode, line: Int = 1, column: Int = 1) {
|
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 code = projectContext.loadScript(fsNode.fileProperty)
|
||||||
val vm = CodeVM(code)
|
val vm = CodeVM(code)
|
||||||
val scope = CodeScope(line, column)
|
val scope = CodeScope(line, column)
|
||||||
setInScope(vm, scope)
|
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.CompilerLogsView
|
||||||
import com.bartlomiejpluta.base.editor.code.view.ScriptFilesView
|
import com.bartlomiejpluta.base.editor.code.view.ScriptFilesView
|
||||||
import com.bartlomiejpluta.base.editor.code.viewmodel.CodeVM
|
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.event.UpdateCompilationLogEvent
|
||||||
import com.bartlomiejpluta.base.editor.main.controller.MainController
|
import com.bartlomiejpluta.base.editor.main.controller.MainController
|
||||||
import com.bartlomiejpluta.base.editor.map.model.map.GameMap
|
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 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 {
|
init {
|
||||||
projectContext.projectProperty.addListener { _, _, project ->
|
projectContext.projectProperty.addListener { _, _, project ->
|
||||||
val projectName = project?.let { " :: ${it.name} (${it.sourceDirectory.absolutePath})" } ?: ""
|
val projectName = project?.let { " :: ${it.name} (${it.sourceDirectory.absolutePath})" } ?: ""
|
||||||
@@ -41,49 +81,18 @@ class MainView : View("BASE Game Editor") {
|
|||||||
subscribe<UpdateCompilationLogEvent> {
|
subscribe<UpdateCompilationLogEvent> {
|
||||||
compilationLogItem.expanded = true
|
compilationLogItem.expanded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subscribe<SelectMainViewTabEvent> { event ->
|
||||||
|
openTabs[event.targetScope]?.let {
|
||||||
|
tabPane.selectionModel.select(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val root = borderpane {
|
override val root = borderpane {
|
||||||
top = mainMenuView.root
|
top = mainMenuView.root
|
||||||
|
|
||||||
center = tabpane {
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
left = drawer(multiselect = true) {
|
left = drawer(multiselect = true) {
|
||||||
item("Code", expanded = false) {
|
item("Code", expanded = false) {
|
||||||
|
|||||||
Reference in New Issue
Block a user