From 1374f2843bb3ffbd6140559644ab0bde9df1640c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Przemys=C5=82aw=20Pluta?= Date: Sat, 6 Feb 2021 00:35:35 +0100 Subject: [PATCH] [Editor] Make UndoRedoService context aware --- .../editor/command/context/UndoableContext.kt | 3 + .../editor/command/context/UndoableScope.kt | 5 ++ .../base/editor/command/model/Command.kt | 5 -- .../base/editor/command/model/base/Command.kt | 5 ++ .../command/model/{ => base}/SimpleCommand.kt | 2 +- .../command/model/{ => base}/Undoable.kt | 2 +- .../command/service/DefaultUndoRedoService.kt | 70 +++++++++++++++---- .../editor/command/service/UndoRedoService.kt | 13 +++- .../editor/render/canvas/map/MapPainter.kt | 2 +- .../render/canvas/map/MapPaintingTrace.kt | 2 +- .../base/editor/view/main/MainView.kt | 5 +- .../base/editor/view/map/MapFragment.kt | 26 ++++++- .../base/editor/view/map/MapLayersView.kt | 6 ++ .../base/editor/view/map/MapView.kt | 8 ++- 14 files changed, 123 insertions(+), 31 deletions(-) create mode 100755 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/context/UndoableContext.kt create mode 100755 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/context/UndoableScope.kt delete mode 100755 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/Command.kt create mode 100755 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/base/Command.kt rename editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/{ => base}/SimpleCommand.kt (81%) rename editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/{ => base}/Undoable.kt (54%) diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/context/UndoableContext.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/context/UndoableContext.kt new file mode 100755 index 00000000..14621919 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/context/UndoableContext.kt @@ -0,0 +1,3 @@ +package com.bartlomiejpluta.base.editor.command.context + +interface UndoableContext \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/context/UndoableScope.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/context/UndoableScope.kt new file mode 100755 index 00000000..0187dc35 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/context/UndoableScope.kt @@ -0,0 +1,5 @@ +package com.bartlomiejpluta.base.editor.command.context + +import tornadofx.Scope + +class UndoableScope : UndoableContext, Scope() \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/Command.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/Command.kt deleted file mode 100755 index 56d6a0b0..00000000 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/Command.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.bartlomiejpluta.base.editor.command.model - -interface Command { - fun execute() -} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/base/Command.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/base/Command.kt new file mode 100755 index 00000000..2ff58153 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/base/Command.kt @@ -0,0 +1,5 @@ +package com.bartlomiejpluta.base.editor.command.model.base + +interface Command { + fun execute() +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/SimpleCommand.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/base/SimpleCommand.kt similarity index 81% rename from editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/SimpleCommand.kt rename to editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/base/SimpleCommand.kt index 2c0f700e..4b7bd981 100755 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/SimpleCommand.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/base/SimpleCommand.kt @@ -1,4 +1,4 @@ -package com.bartlomiejpluta.base.editor.command.model +package com.bartlomiejpluta.base.editor.command.model.base class SimpleCommand( override val commandName: String, diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/Undoable.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/base/Undoable.kt similarity index 54% rename from editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/Undoable.kt rename to editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/base/Undoable.kt index 2fcfa782..30c4e169 100755 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/Undoable.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/model/base/Undoable.kt @@ -1,4 +1,4 @@ -package com.bartlomiejpluta.base.editor.command.model +package com.bartlomiejpluta.base.editor.command.model.base interface Undoable { fun undo() diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/service/DefaultUndoRedoService.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/service/DefaultUndoRedoService.kt index 05c22d2d..daba2c6e 100755 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/service/DefaultUndoRedoService.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/service/DefaultUndoRedoService.kt @@ -1,16 +1,18 @@ package com.bartlomiejpluta.base.editor.command.service -import com.bartlomiejpluta.base.editor.command.model.Undoable +import com.bartlomiejpluta.base.editor.command.context.UndoableContext +import com.bartlomiejpluta.base.editor.command.model.base.Undoable import org.slf4j.LoggerFactory import org.springframework.stereotype.Component +import java.lang.Integer.toHexString import java.util.* @Component class DefaultUndoRedoService : UndoRedoService { - private val undo: Deque = ArrayDeque() - private val redo: Deque = ArrayDeque() + private val undo: Deque> = ArrayDeque() + private val redo: Deque> = ArrayDeque() - override var sizeMax = 30 + var sizeMax = 30 set(value) { if (value >= 0) { for (i in 0 until undo.size - value) { @@ -22,21 +24,36 @@ class DefaultUndoRedoService : UndoRedoService { } override fun push(undoable: Undoable) { + push(undoable, GLOBAL_CONTEXT) + } + + override fun push(undoable: Undoable, context: UndoableContext) { if (undo.size == sizeMax) { - log.debug("The max size of [undo] list has been reached. Removing the last item...") + log.debug("The max size of [undo] stack has been reached. Removing the last item...") undo.removeLast() } - log.debug("Pushing item to [undo] list: ${undoable.commandName}") - undo.push(undoable) + log.debug("Pushing item to [undo] stack: ${undoable.commandName} (ctx: ${toHexString(context.hashCode())})") + undo.push(undoable to context) redo.clear() } override fun undo() { if (undo.isNotEmpty()) { undo.pop().let { - log.debug("Performing undo: ${it.commandName}") - it.undo() + log.debug("Performing undo: ${it.first.commandName}") + it.first.undo() + redo.push(it) + } + } + } + + override fun undo(context: UndoableContext) { + if (undo.isNotEmpty()) { + undo.firstOrNull { it.second === context }?.let { + log.debug("Performing contextual (ctx: ${toHexString(context.hashCode())}) undo: ${it.first.commandName}") + undo.remove(it) + it.first.undo() redo.push(it) } } @@ -45,26 +62,49 @@ class DefaultUndoRedoService : UndoRedoService { override fun redo() { if (redo.isNotEmpty()) { redo.pop().let { - log.debug("Performing redo: ${it.commandName}") - it.redo() + log.debug("Performing redo: ${it.first.commandName}") + it.first.redo() + undo.push(it) + } + } + } + + override fun redo(context: UndoableContext) { + if (redo.isNotEmpty()) { + redo.firstOrNull { it.second === context }?.let { + log.debug("Performing contextual (ctx: ${toHexString(context.hashCode())}) redo: ${it.first.commandName}") + redo.remove(it) + it.first.redo() undo.push(it) } } } override val lastUndoable: Undoable? - get() = undo.last + get() = undo.first?.first override val lastRedoable: Undoable? - get() = redo.last + get() = redo.first?.first override val undoCommandName: String - get() = undo.last?.commandName ?: "" + get() = undo.first?.first?.commandName ?: "" override val redoCommandName: String - get() = redo.last?.commandName ?: "" + get() = redo.first?.first?.commandName ?: "" + + override fun lastUndoable(context: UndoableContext) = undo.firstOrNull { it.second === context }?.first + + override fun lastRedoable(context: UndoableContext) = redo.firstOrNull { it.second === context }?.first + + override fun undoCommandName(context: UndoableContext) = + undo.firstOrNull { it.second === context }?.first?.commandName ?: "" + + override fun redoCommandName(context: UndoableContext) = + redo.firstOrNull { it.second === context }?.first?.commandName ?: "" + companion object { private val log = LoggerFactory.getLogger(DefaultUndoRedoService::class.java) + private val GLOBAL_CONTEXT = object : UndoableContext {} } } \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/service/UndoRedoService.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/service/UndoRedoService.kt index 80cf8305..a10f860d 100755 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/service/UndoRedoService.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/command/service/UndoRedoService.kt @@ -1,15 +1,24 @@ package com.bartlomiejpluta.base.editor.command.service -import com.bartlomiejpluta.base.editor.command.model.Undoable +import com.bartlomiejpluta.base.editor.command.context.UndoableContext +import com.bartlomiejpluta.base.editor.command.model.base.Undoable interface UndoRedoService { fun push(undoable: Undoable) fun undo() fun redo() + fun push(undoable: Undoable, context: UndoableContext) + fun undo(context: UndoableContext) + fun redo(context: UndoableContext) + val lastUndoable: Undoable? val lastRedoable: Undoable? val undoCommandName: String val redoCommandName: String - var sizeMax: Int + + fun lastUndoable(context: UndoableContext): Undoable? + fun lastRedoable(context: UndoableContext): Undoable? + fun undoCommandName(context: UndoableContext): String + fun redoCommandName(context: UndoableContext): String } \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/render/canvas/map/MapPainter.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/render/canvas/map/MapPainter.kt index 0514c3ec..3bb3d6df 100755 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/render/canvas/map/MapPainter.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/render/canvas/map/MapPainter.kt @@ -53,7 +53,7 @@ class MapPainter( } private fun beginTrace(event: MapMouseEvent) { - if (event.button == MouseButton.PRIMARY) { + if (event.button == MouseButton.PRIMARY && mapVM.selectedLayer >= 0) { currentTrace = MapPaintingTrace(mapVM, "Paint trace").apply { brushVM.forEach { row, column, tile -> paint( diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/render/canvas/map/MapPaintingTrace.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/render/canvas/map/MapPaintingTrace.kt index d44bf6ca..71578139 100755 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/render/canvas/map/MapPaintingTrace.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/render/canvas/map/MapPaintingTrace.kt @@ -1,6 +1,6 @@ package com.bartlomiejpluta.base.editor.render.canvas.map -import com.bartlomiejpluta.base.editor.command.model.Undoable +import com.bartlomiejpluta.base.editor.command.model.base.Undoable import com.bartlomiejpluta.base.editor.model.map.layer.TileLayer import com.bartlomiejpluta.base.editor.model.tileset.Tile import com.bartlomiejpluta.base.editor.viewmodel.map.GameMapVM diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/main/MainView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/main/MainView.kt index 5f686bfe..3c4c2926 100755 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/main/MainView.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/main/MainView.kt @@ -1,5 +1,6 @@ package com.bartlomiejpluta.base.editor.view.main +import com.bartlomiejpluta.base.editor.command.context.UndoableScope import com.bartlomiejpluta.base.editor.command.service.UndoRedoService import com.bartlomiejpluta.base.editor.controller.map.MapController import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent @@ -18,7 +19,7 @@ class MainView : View() { button("Map 1") { action { val map = mapController.getMap(1) - tabPane += find(Scope(), MapFragment::map to map).apply { + tabPane += find(UndoableScope(), MapFragment::map to map).apply { title = "Map 1" } } @@ -27,7 +28,7 @@ class MainView : View() { button("Map 2") { action { val map = mapController.getMap(2) - tabPane += find(Scope(), MapFragment::map to map).apply { + tabPane += find(UndoableScope(), MapFragment::map to map).apply { title = "Map 2" } } diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/map/MapFragment.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/map/MapFragment.kt index eac8cb4d..eda2c03b 100755 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/map/MapFragment.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/map/MapFragment.kt @@ -1,11 +1,18 @@ package com.bartlomiejpluta.base.editor.view.map +import com.bartlomiejpluta.base.editor.command.context.UndoableScope +import com.bartlomiejpluta.base.editor.command.service.UndoRedoService +import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent import com.bartlomiejpluta.base.editor.model.map.map.GameMap import com.bartlomiejpluta.base.editor.viewmodel.map.GameMapVM +import org.kordamp.ikonli.javafx.FontIcon import tornadofx.* class MapFragment : Fragment() { + private val undoRedoService: UndoRedoService by di() + + override val scope = super.scope as UndoableScope val map: GameMap by param() private val mapVM = find().apply { item = map } @@ -14,8 +21,25 @@ class MapFragment : Fragment() { private val layersView = find() private val tileSetView = find() - override val root = borderpane { + top = toolbar { + button(graphic = FontIcon("fa-undo")) { + shortcut("Ctrl+Z") + action { + undoRedoService.undo(scope) + fire(RedrawMapRequestEvent) + } + } + + button(graphic = FontIcon("fa-repeat")) { + shortcut("Ctrl+Shift+Z") + action { + undoRedoService.redo(scope) + fire(RedrawMapRequestEvent) + } + } + } + center = mapView.root right = drawer(multiselect = true) { diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/map/MapLayersView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/map/MapLayersView.kt index 840a2844..fd78d174 100755 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/map/MapLayersView.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/map/MapLayersView.kt @@ -1,5 +1,7 @@ package com.bartlomiejpluta.base.editor.view.map +import com.bartlomiejpluta.base.editor.command.context.UndoableScope +import com.bartlomiejpluta.base.editor.command.service.UndoRedoService import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent import com.bartlomiejpluta.base.editor.model.map.layer.Layer import com.bartlomiejpluta.base.editor.viewmodel.map.GameMapVM @@ -8,6 +10,10 @@ import org.kordamp.ikonli.javafx.FontIcon import tornadofx.* class MapLayersView : View() { + private val undoRedoService: UndoRedoService by di() + + override val scope = super.scope as UndoableScope + private val mapVM = find() private var layersPane = TableView(mapVM.layers).apply { diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/map/MapView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/map/MapView.kt index 170ada67..241f3bca 100755 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/map/MapView.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/view/map/MapView.kt @@ -1,5 +1,6 @@ package com.bartlomiejpluta.base.editor.view.map +import com.bartlomiejpluta.base.editor.command.context.UndoableScope import com.bartlomiejpluta.base.editor.command.service.UndoRedoService import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent import com.bartlomiejpluta.base.editor.view.component.map.MapPane @@ -18,16 +19,19 @@ import tornadofx.scrollpane class MapView : View() { private val undoRedoService: UndoRedoService by di() + override val scope = super.scope as UndoableScope + val zoomProperty = SimpleDoubleProperty(1.0) + private val zoom = Scale(1.0, 1.0, 0.0, 0.0).apply { xProperty().bind(zoomProperty) yProperty().bind(zoomProperty) } - private val mapVM = find() + private val brushVM = find() - private val mapPane = MapPane(mapVM, brushVM) { undoRedoService.push(it) } + private val mapPane = MapPane(mapVM, brushVM) { undoRedoService.push(it, scope) } init { brushVM.item = mapVM.tileSet.baseBrush