[Editor] Make UndoRedoService context aware

This commit is contained in:
2021-02-06 00:35:35 +01:00
parent 1a8e34f25b
commit 1374f2843b
14 changed files with 123 additions and 31 deletions

View File

@@ -0,0 +1,3 @@
package com.bartlomiejpluta.base.editor.command.context
interface UndoableContext

View File

@@ -0,0 +1,5 @@
package com.bartlomiejpluta.base.editor.command.context
import tornadofx.Scope
class UndoableScope : UndoableContext, Scope()

View File

@@ -1,5 +0,0 @@
package com.bartlomiejpluta.base.editor.command.model
interface Command {
fun execute()
}

View File

@@ -0,0 +1,5 @@
package com.bartlomiejpluta.base.editor.command.model.base
interface Command {
fun execute()
}

View File

@@ -1,4 +1,4 @@
package com.bartlomiejpluta.base.editor.command.model
package com.bartlomiejpluta.base.editor.command.model.base
class SimpleCommand<T>(
override val commandName: String,

View File

@@ -1,4 +1,4 @@
package com.bartlomiejpluta.base.editor.command.model
package com.bartlomiejpluta.base.editor.command.model.base
interface Undoable {
fun undo()

View File

@@ -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<Undoable> = ArrayDeque()
private val redo: Deque<Undoable> = ArrayDeque()
private val undo: Deque<Pair<Undoable, UndoableContext>> = ArrayDeque()
private val redo: Deque<Pair<Undoable, UndoableContext>> = 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 {}
}
}

View File

@@ -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
}

View File

@@ -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(

View File

@@ -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

View File

@@ -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<MapFragment>(Scope(), MapFragment::map to map).apply {
tabPane += find<MapFragment>(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<MapFragment>(Scope(), MapFragment::map to map).apply {
tabPane += find<MapFragment>(UndoableScope(), MapFragment::map to map).apply {
title = "Map 2"
}
}

View File

@@ -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<GameMapVM>().apply { item = map }
@@ -14,8 +21,25 @@ class MapFragment : Fragment() {
private val layersView = find<MapLayersView>()
private val tileSetView = find<TileSetView>()
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) {

View File

@@ -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<GameMapVM>()
private var layersPane = TableView(mapVM.layers).apply {

View File

@@ -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<GameMapVM>()
private val brushVM = find<BrushVM>()
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