[Editor] Make UndoRedoService context aware
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
package com.bartlomiejpluta.base.editor.command.context
|
||||
|
||||
interface UndoableContext
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.bartlomiejpluta.base.editor.command.context
|
||||
|
||||
import tornadofx.Scope
|
||||
|
||||
class UndoableScope : UndoableContext, Scope()
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.bartlomiejpluta.base.editor.command.model
|
||||
|
||||
interface Command {
|
||||
fun execute()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.bartlomiejpluta.base.editor.command.model.base
|
||||
|
||||
interface Command {
|
||||
fun execute()
|
||||
}
|
||||
@@ -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,
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.bartlomiejpluta.base.editor.command.model
|
||||
package com.bartlomiejpluta.base.editor.command.model.base
|
||||
|
||||
interface Undoable {
|
||||
fun undo()
|
||||
@@ -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 {}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user