[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>(
|
class SimpleCommand<T>(
|
||||||
override val commandName: String,
|
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 {
|
interface Undoable {
|
||||||
fun undo()
|
fun undo()
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
package com.bartlomiejpluta.base.editor.command.service
|
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.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
|
import java.lang.Integer.toHexString
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class DefaultUndoRedoService : UndoRedoService {
|
class DefaultUndoRedoService : UndoRedoService {
|
||||||
private val undo: Deque<Undoable> = ArrayDeque()
|
private val undo: Deque<Pair<Undoable, UndoableContext>> = ArrayDeque()
|
||||||
private val redo: Deque<Undoable> = ArrayDeque()
|
private val redo: Deque<Pair<Undoable, UndoableContext>> = ArrayDeque()
|
||||||
|
|
||||||
override var sizeMax = 30
|
var sizeMax = 30
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value >= 0) {
|
if (value >= 0) {
|
||||||
for (i in 0 until undo.size - value) {
|
for (i in 0 until undo.size - value) {
|
||||||
@@ -22,21 +24,36 @@ class DefaultUndoRedoService : UndoRedoService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun push(undoable: Undoable) {
|
override fun push(undoable: Undoable) {
|
||||||
|
push(undoable, GLOBAL_CONTEXT)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun push(undoable: Undoable, context: UndoableContext) {
|
||||||
if (undo.size == sizeMax) {
|
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()
|
undo.removeLast()
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("Pushing item to [undo] list: ${undoable.commandName}")
|
log.debug("Pushing item to [undo] stack: ${undoable.commandName} (ctx: ${toHexString(context.hashCode())})")
|
||||||
undo.push(undoable)
|
undo.push(undoable to context)
|
||||||
redo.clear()
|
redo.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun undo() {
|
override fun undo() {
|
||||||
if (undo.isNotEmpty()) {
|
if (undo.isNotEmpty()) {
|
||||||
undo.pop().let {
|
undo.pop().let {
|
||||||
log.debug("Performing undo: ${it.commandName}")
|
log.debug("Performing undo: ${it.first.commandName}")
|
||||||
it.undo()
|
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)
|
redo.push(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,26 +62,49 @@ class DefaultUndoRedoService : UndoRedoService {
|
|||||||
override fun redo() {
|
override fun redo() {
|
||||||
if (redo.isNotEmpty()) {
|
if (redo.isNotEmpty()) {
|
||||||
redo.pop().let {
|
redo.pop().let {
|
||||||
log.debug("Performing redo: ${it.commandName}")
|
log.debug("Performing redo: ${it.first.commandName}")
|
||||||
it.redo()
|
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)
|
undo.push(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val lastUndoable: Undoable?
|
override val lastUndoable: Undoable?
|
||||||
get() = undo.last
|
get() = undo.first?.first
|
||||||
|
|
||||||
override val lastRedoable: Undoable?
|
override val lastRedoable: Undoable?
|
||||||
get() = redo.last
|
get() = redo.first?.first
|
||||||
|
|
||||||
override val undoCommandName: String
|
override val undoCommandName: String
|
||||||
get() = undo.last?.commandName ?: ""
|
get() = undo.first?.first?.commandName ?: ""
|
||||||
|
|
||||||
override val redoCommandName: String
|
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 {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(DefaultUndoRedoService::class.java)
|
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
|
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 {
|
interface UndoRedoService {
|
||||||
fun push(undoable: Undoable)
|
fun push(undoable: Undoable)
|
||||||
fun undo()
|
fun undo()
|
||||||
fun redo()
|
fun redo()
|
||||||
|
|
||||||
|
fun push(undoable: Undoable, context: UndoableContext)
|
||||||
|
fun undo(context: UndoableContext)
|
||||||
|
fun redo(context: UndoableContext)
|
||||||
|
|
||||||
val lastUndoable: Undoable?
|
val lastUndoable: Undoable?
|
||||||
val lastRedoable: Undoable?
|
val lastRedoable: Undoable?
|
||||||
val undoCommandName: String
|
val undoCommandName: String
|
||||||
val redoCommandName: 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) {
|
private fun beginTrace(event: MapMouseEvent) {
|
||||||
if (event.button == MouseButton.PRIMARY) {
|
if (event.button == MouseButton.PRIMARY && mapVM.selectedLayer >= 0) {
|
||||||
currentTrace = MapPaintingTrace(mapVM, "Paint trace").apply {
|
currentTrace = MapPaintingTrace(mapVM, "Paint trace").apply {
|
||||||
brushVM.forEach { row, column, tile ->
|
brushVM.forEach { row, column, tile ->
|
||||||
paint(
|
paint(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.bartlomiejpluta.base.editor.render.canvas.map
|
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.map.layer.TileLayer
|
||||||
import com.bartlomiejpluta.base.editor.model.tileset.Tile
|
import com.bartlomiejpluta.base.editor.model.tileset.Tile
|
||||||
import com.bartlomiejpluta.base.editor.viewmodel.map.GameMapVM
|
import com.bartlomiejpluta.base.editor.viewmodel.map.GameMapVM
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.bartlomiejpluta.base.editor.view.main
|
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.command.service.UndoRedoService
|
||||||
import com.bartlomiejpluta.base.editor.controller.map.MapController
|
import com.bartlomiejpluta.base.editor.controller.map.MapController
|
||||||
import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent
|
import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent
|
||||||
@@ -18,7 +19,7 @@ class MainView : View() {
|
|||||||
button("Map 1") {
|
button("Map 1") {
|
||||||
action {
|
action {
|
||||||
val map = mapController.getMap(1)
|
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"
|
title = "Map 1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,7 +28,7 @@ class MainView : View() {
|
|||||||
button("Map 2") {
|
button("Map 2") {
|
||||||
action {
|
action {
|
||||||
val map = mapController.getMap(2)
|
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"
|
title = "Map 2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
package com.bartlomiejpluta.base.editor.view.map
|
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.model.map.map.GameMap
|
||||||
import com.bartlomiejpluta.base.editor.viewmodel.map.GameMapVM
|
import com.bartlomiejpluta.base.editor.viewmodel.map.GameMapVM
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
|
||||||
|
|
||||||
class MapFragment : Fragment() {
|
class MapFragment : Fragment() {
|
||||||
|
private val undoRedoService: UndoRedoService by di()
|
||||||
|
|
||||||
|
override val scope = super.scope as UndoableScope
|
||||||
val map: GameMap by param()
|
val map: GameMap by param()
|
||||||
|
|
||||||
private val mapVM = find<GameMapVM>().apply { item = map }
|
private val mapVM = find<GameMapVM>().apply { item = map }
|
||||||
@@ -14,8 +21,25 @@ class MapFragment : Fragment() {
|
|||||||
private val layersView = find<MapLayersView>()
|
private val layersView = find<MapLayersView>()
|
||||||
private val tileSetView = find<TileSetView>()
|
private val tileSetView = find<TileSetView>()
|
||||||
|
|
||||||
|
|
||||||
override val root = borderpane {
|
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
|
center = mapView.root
|
||||||
|
|
||||||
right = drawer(multiselect = true) {
|
right = drawer(multiselect = true) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.bartlomiejpluta.base.editor.view.map
|
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.event.RedrawMapRequestEvent
|
||||||
import com.bartlomiejpluta.base.editor.model.map.layer.Layer
|
import com.bartlomiejpluta.base.editor.model.map.layer.Layer
|
||||||
import com.bartlomiejpluta.base.editor.viewmodel.map.GameMapVM
|
import com.bartlomiejpluta.base.editor.viewmodel.map.GameMapVM
|
||||||
@@ -8,6 +10,10 @@ import org.kordamp.ikonli.javafx.FontIcon
|
|||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
|
||||||
class MapLayersView : View() {
|
class MapLayersView : View() {
|
||||||
|
private val undoRedoService: UndoRedoService by di()
|
||||||
|
|
||||||
|
override val scope = super.scope as UndoableScope
|
||||||
|
|
||||||
private val mapVM = find<GameMapVM>()
|
private val mapVM = find<GameMapVM>()
|
||||||
|
|
||||||
private var layersPane = TableView(mapVM.layers).apply {
|
private var layersPane = TableView(mapVM.layers).apply {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.bartlomiejpluta.base.editor.view.map
|
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.command.service.UndoRedoService
|
||||||
import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent
|
import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent
|
||||||
import com.bartlomiejpluta.base.editor.view.component.map.MapPane
|
import com.bartlomiejpluta.base.editor.view.component.map.MapPane
|
||||||
@@ -18,16 +19,19 @@ import tornadofx.scrollpane
|
|||||||
class MapView : View() {
|
class MapView : View() {
|
||||||
private val undoRedoService: UndoRedoService by di()
|
private val undoRedoService: UndoRedoService by di()
|
||||||
|
|
||||||
|
override val scope = super.scope as UndoableScope
|
||||||
|
|
||||||
val zoomProperty = SimpleDoubleProperty(1.0)
|
val zoomProperty = SimpleDoubleProperty(1.0)
|
||||||
|
|
||||||
private val zoom = Scale(1.0, 1.0, 0.0, 0.0).apply {
|
private val zoom = Scale(1.0, 1.0, 0.0, 0.0).apply {
|
||||||
xProperty().bind(zoomProperty)
|
xProperty().bind(zoomProperty)
|
||||||
yProperty().bind(zoomProperty)
|
yProperty().bind(zoomProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mapVM = find<GameMapVM>()
|
private val mapVM = find<GameMapVM>()
|
||||||
|
|
||||||
private val brushVM = find<BrushVM>()
|
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 {
|
init {
|
||||||
brushVM.item = mapVM.tileSet.baseBrush
|
brushVM.item = mapVM.tileSet.baseBrush
|
||||||
|
|||||||
Reference in New Issue
Block a user