[Editor] Create scaffolding for creating objects in ObjectLayer

This commit is contained in:
2021-04-02 22:16:18 +02:00
parent cf9d42d4ee
commit abc8f9ba3d
13 changed files with 309 additions and 71 deletions

View File

@@ -19,6 +19,9 @@ class ImagePositionPaintingTrace(val map: GameMapVM, override val commandName: S
private var newY = 0.0
private lateinit var layer: ImageLayer
override var executed = false
private set
override fun beginTrace(editorStateVM: EditorStateVM, brushVM: BrushVM, mouseEvent: MapMouseEvent) {
this.layerIndex = editorStateVM.selectedLayerIndex
@@ -44,6 +47,10 @@ class ImagePositionPaintingTrace(val map: GameMapVM, override val commandName: S
layer.x = newX.toInt()
layer.y = newY.toInt()
if (dx != 0.0 || dy != 0.0) {
executed = true
}
}
override fun commitTrace(editorStateVM: EditorStateVM, brushVM: BrushVM, mouseEvent: MapMouseEvent) {
@@ -55,6 +62,10 @@ class ImagePositionPaintingTrace(val map: GameMapVM, override val commandName: S
layer.x = newX.toInt()
layer.y = newY.toInt()
if (dx != 0.0 || dy != 0.0) {
executed = true
}
}
override fun undo() {

View File

@@ -100,7 +100,7 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr
private fun dispatchLayerRender(gc: GraphicsContext, layer: Layer) {
when (layer) {
is TileLayer -> renderTileLayer(gc, layer)
is ObjectLayer -> renderObjectPassageMap(gc, layer)
is ObjectLayer -> renderObjectLayer(gc, layer)
is ColorLayer -> renderColorLayer(gc, layer)
is ImageLayer -> renderImageLayer(gc, layer)
}
@@ -120,11 +120,16 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr
}
}
private fun renderObjectPassageMap(gc: GraphicsContext, objectLayer: ObjectLayer) {
private fun renderObjectLayer(gc: GraphicsContext, objectLayer: ObjectLayer) {
if (editorStateVM.selectedLayer !is ObjectLayer) {
return
}
renderObjectPassageMap(gc, objectLayer)
renderObjects(gc, objectLayer)
}
private fun renderObjectPassageMap(gc: GraphicsContext, objectLayer: ObjectLayer) {
for ((row, columns) in objectLayer.passageMap.withIndex()) {
for ((column, passage) in columns.withIndex()) {
PassageAbilitySymbol.render(gc, column * tileWidth, row * tileHeight, tileWidth, tileHeight, passage)
@@ -132,6 +137,26 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr
}
}
private fun renderObjects(gc: GraphicsContext, objectLayer: ObjectLayer) {
for (mapObject in objectLayer.objects) {
val alpha = gc.globalAlpha
val fill = gc.fill
gc.globalAlpha = 0.6
gc.fill = OBJECT_COLOR
gc.fillOval(
mapObject.x * tileWidth + OBJECT_MARGIN,
mapObject.y * tileHeight + OBJECT_MARGIN,
tileWidth - 2 * OBJECT_MARGIN,
tileHeight - 2 * OBJECT_MARGIN
)
gc.globalAlpha = alpha
gc.fill = fill
}
}
private fun renderColorLayer(gc: GraphicsContext, colorLayer: ColorLayer) {
val alpha = gc.globalAlpha
val color = gc.fill
@@ -172,5 +197,8 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr
companion object {
private val BACKGROUND_COLOR1 = Color.color(1.0, 1.0, 1.0, 1.0)
private val BACKGROUND_COLOR2 = Color.color(0.8, 0.8, 0.8, 1.0)
private val OBJECT_COLOR = Color.WHITE
private const val OBJECT_MARGIN = 4
}
}

View File

@@ -1,5 +1,6 @@
package com.bartlomiejpluta.base.editor.map.canvas
import com.bartlomiejpluta.base.editor.map.model.brush.BrushTool
import com.bartlomiejpluta.base.editor.map.model.layer.ImageLayer
import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer
import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer
@@ -25,12 +26,18 @@ class MapPainter(
private var currentTrace: PaintingTrace? = null
init {
editorStateVM.selectedLayerProperty.addListener { _, _, layer ->
cursor = when (layer) {
is TileLayer -> TilePaintingCursor(tileWidth, tileHeight, editorStateVM, brushVM)
is ObjectLayer -> ObjectPaintingCursor(tileWidth, tileHeight, editorStateVM, brushVM)
editorStateVM.selectedLayerProperty.addListener { _, _, _ -> updateCursor() }
brushVM.toolProperty.addListener { _, _, _ -> updateCursor() }
}
private fun updateCursor() {
cursor = when (editorStateVM.selectedLayer) {
is TileLayer -> TilePaintingCursor(tileWidth, tileHeight, editorStateVM, brushVM)
is ObjectLayer -> when (brushVM.tool) {
BrushTool.PASSAGE -> PassageAbilityPaintingCursor(tileWidth, tileHeight, editorStateVM, brushVM)
else -> null
}
else -> null
}
}
@@ -58,7 +65,10 @@ class MapPainter(
if (currentTrace == null && editorStateVM.selectedLayerIndex >= 0) {
currentTrace = when (editorStateVM.selectedLayer) {
is TileLayer -> TilePaintingTrace(mapVM, "Paint trace")
is ObjectLayer -> ObjectPaintingTrace(mapVM, "Toggle passage")
is ObjectLayer -> when (brushVM.tool) {
BrushTool.DEFAULT -> ObjectPaintingTrace(mapVM, "Update object")
else -> PassageAbilityPaintingTrace(mapVM, "Toggle passage")
}
is ImageLayer -> ImagePositionPaintingTrace(mapVM, "Move Image Layer")
else -> null
}

View File

@@ -1,95 +1,108 @@
package com.bartlomiejpluta.base.editor.map.canvas
import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode
import com.bartlomiejpluta.base.editor.map.model.enumeration.PassageAbility
import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer
import com.bartlomiejpluta.base.editor.map.model.obj.MapObject
import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import com.bartlomiejpluta.base.editor.render.input.MapMouseEvent
import javafx.collections.ObservableList
import javafx.scene.input.MouseButton
class ObjectPaintingTrace(val map: GameMapVM, override val commandName: String) : PaintingTrace {
private val trace = mutableListOf<Element>()
private lateinit var objects: ObservableList<MapObject>
private lateinit var event: MapMouseEvent
private fun paint(layerIndex: Int, row: Int, column: Int, passageAbility: PassageAbility) {
if (row >= map.rows || column >= map.columns || row < 0 || column < 0 || layerIndex < 0) {
private var newObject: MapObject? = null
private var formerObject: MapObject? = null
private var x = 0
private var y = 0
override var executed = false
private set
override fun beginTrace(editorStateVM: EditorStateVM, brushVM: BrushVM, mouseEvent: MapMouseEvent) {
x = editorStateVM.cursorColumn
y = editorStateVM.cursorRow
if (y >= map.rows || x >= map.columns || y < 0 || x < 0 || editorStateVM.selectedLayerIndex < 0) {
return
}
val passageMap = (map.layers[layerIndex] as ObjectLayer).passageMap
val formerPassageAbility = passageMap[row][column]
objects = (editorStateVM.selectedLayer as ObjectLayer).objects
formerObject = objects.firstOrNull { it.x == x && it.y == y }
if (trace.isEmpty()) {
trace += Element(layerIndex, row, column, formerPassageAbility, passageAbility)
passageMap[row][column] = passageAbility
event = mouseEvent
}
private fun createOrUpdateObject() {
newObject = MapObject(x, y, "")
objects.remove(formerObject)
objects.add(newObject)
executed = true
}
private fun moveObject(newX: Int, newY: Int) {
if (newY >= map.rows || newX >= map.columns || newY < 0 || newX < 0) {
return
}
val tileAlreadyPainted =
trace.find { it.layerIndex == layerIndex && it.row == row && it.column == column } != null
if (!tileAlreadyPainted) {
trace += Element(layerIndex, row, column, formerPassageAbility, passageAbility)
passageMap[row][column] = passageAbility
formerObject?.let {
newObject = MapObject(newX, newY, it.code)
objects.remove(formerObject)
objects.add(newObject)
executed = true
}
}
override fun beginTrace(editorStateVM: EditorStateVM, brushVM: BrushVM, mouseEvent: MapMouseEvent) {
brushVM.forEach { row, column, centerRow, centerColumn, _ ->
paint(
editorStateVM.selectedLayerIndex,
editorStateVM.cursorRow - centerRow + row,
editorStateVM.cursorColumn - centerColumn + column,
when {
brushVM.mode == BrushMode.ERASING_MODE -> PassageAbility.ALLOW
mouseEvent.button == MouseButton.PRIMARY -> PassageAbility.BLOCK
else -> PassageAbility.ALLOW
}
)
}
private fun removeObject() {
objects.remove(formerObject)
executed = true
}
override fun proceedTrace(editorStateVM: EditorStateVM, brushVM: BrushVM, mouseEvent: MapMouseEvent) {
brushVM.forEach { row, column, centerRow, centerColumn, _ ->
paint(
editorStateVM.selectedLayerIndex,
editorStateVM.cursorRow - centerRow + row,
editorStateVM.cursorColumn - centerColumn + column,
when {
brushVM.mode == BrushMode.ERASING_MODE -> PassageAbility.ALLOW
mouseEvent.button == MouseButton.PRIMARY -> PassageAbility.BLOCK
else -> PassageAbility.ALLOW
}
)
}
}
override fun commitTrace(editorStateVM: EditorStateVM, brushVM: BrushVM, mouseEvent: MapMouseEvent) {
val newX = editorStateVM.cursorColumn
val newY = editorStateVM.cursorRow
val dx = newX - x
val dy = newY - y
// Moving
if (brushVM.mode == BrushMode.PAINTING_MODE && (dx != 0 || dy != 0)) {
moveObject(newX, newY)
return
}
// Creating new or updating existing one or removing
if (event.event.clickCount > 1 && brushVM.mode == BrushMode.PAINTING_MODE) {
when (event.button) {
MouseButton.PRIMARY -> createOrUpdateObject()
MouseButton.SECONDARY -> removeObject()
else -> {
}
}
return
}
// Removing
if (brushVM.mode == BrushMode.ERASING_MODE) {
removeObject()
}
}
override fun undo() {
trace.forEach {
(map.layers[it.layerIndex] as ObjectLayer).passageMap[it.row][it.column] = it.formerPassageAbility
}
objects.remove(newObject)
formerObject?.let(objects::add)
}
override fun redo() {
trace.forEach {
(map.layers[it.layerIndex] as ObjectLayer).passageMap[it.row][it.column] = it.passageAbility
}
objects.remove(formerObject)
newObject?.let(objects::add)
}
override val supportedButtons = arrayOf(MouseButton.PRIMARY, MouseButton.SECONDARY)
companion object {
private data class Element(
val layerIndex: Int,
val row: Int,
val column: Int,
val formerPassageAbility: PassageAbility,
val passageAbility: PassageAbility
)
}
}

View File

@@ -12,4 +12,6 @@ interface PaintingTrace : Undoable {
fun commitTrace(editorStateVM: EditorStateVM, brushVM: BrushVM, mouseEvent: MapMouseEvent)
val supportedButtons: Array<MouseButton>
val executed: Boolean
}

View File

@@ -6,7 +6,7 @@ import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import javafx.scene.canvas.GraphicsContext
import javafx.scene.paint.Color
class ObjectPaintingCursor(
class PassageAbilityPaintingCursor(
private val tileWidth: Double,
private val tileHeight: Double,
private val editorStateVM: EditorStateVM,

View File

@@ -0,0 +1,99 @@
package com.bartlomiejpluta.base.editor.map.canvas
import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode
import com.bartlomiejpluta.base.editor.map.model.enumeration.PassageAbility
import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer
import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import com.bartlomiejpluta.base.editor.render.input.MapMouseEvent
import javafx.scene.input.MouseButton
class PassageAbilityPaintingTrace(val map: GameMapVM, override val commandName: String) : PaintingTrace {
private val trace = mutableListOf<Element>()
override var executed = false
private set
private fun paint(layerIndex: Int, row: Int, column: Int, passageAbility: PassageAbility) {
if (row >= map.rows || column >= map.columns || row < 0 || column < 0 || layerIndex < 0) {
return
}
val passageMap = (map.layers[layerIndex] as ObjectLayer).passageMap
val formerPassageAbility = passageMap[row][column]
if (trace.isEmpty()) {
trace += Element(layerIndex, row, column, formerPassageAbility, passageAbility)
passageMap[row][column] = passageAbility
return
}
val tileAlreadyPainted =
trace.find { it.layerIndex == layerIndex && it.row == row && it.column == column } != null
if (!tileAlreadyPainted) {
trace += Element(layerIndex, row, column, formerPassageAbility, passageAbility)
passageMap[row][column] = passageAbility
executed = true
}
}
override fun beginTrace(editorStateVM: EditorStateVM, brushVM: BrushVM, mouseEvent: MapMouseEvent) {
brushVM.forEach { row, column, centerRow, centerColumn, _ ->
paint(
editorStateVM.selectedLayerIndex,
editorStateVM.cursorRow - centerRow + row,
editorStateVM.cursorColumn - centerColumn + column,
when {
brushVM.mode == BrushMode.ERASING_MODE -> PassageAbility.ALLOW
mouseEvent.button == MouseButton.PRIMARY -> PassageAbility.BLOCK
else -> PassageAbility.ALLOW
}
)
}
}
override fun proceedTrace(editorStateVM: EditorStateVM, brushVM: BrushVM, mouseEvent: MapMouseEvent) {
brushVM.forEach { row, column, centerRow, centerColumn, _ ->
paint(
editorStateVM.selectedLayerIndex,
editorStateVM.cursorRow - centerRow + row,
editorStateVM.cursorColumn - centerColumn + column,
when {
brushVM.mode == BrushMode.ERASING_MODE -> PassageAbility.ALLOW
mouseEvent.button == MouseButton.PRIMARY -> PassageAbility.BLOCK
else -> PassageAbility.ALLOW
}
)
}
}
override fun commitTrace(editorStateVM: EditorStateVM, brushVM: BrushVM, mouseEvent: MapMouseEvent) {
}
override fun undo() {
trace.forEach {
(map.layers[it.layerIndex] as ObjectLayer).passageMap[it.row][it.column] = it.formerPassageAbility
}
}
override fun redo() {
trace.forEach {
(map.layers[it.layerIndex] as ObjectLayer).passageMap[it.row][it.column] = it.passageAbility
}
}
override val supportedButtons = arrayOf(MouseButton.PRIMARY, MouseButton.SECONDARY)
companion object {
private data class Element(
val layerIndex: Int,
val row: Int,
val column: Int,
val formerPassageAbility: PassageAbility,
val passageAbility: PassageAbility
)
}
}

View File

@@ -12,6 +12,9 @@ import javafx.scene.input.MouseButton
data class TilePaintingTrace(val map: GameMapVM, override val commandName: String) : PaintingTrace {
private val trace = mutableListOf<Element>()
override var executed = false
private set
private fun paint(layerIndex: Int, row: Int, column: Int, tile: Tile?) {
if (row >= map.rows || column >= map.columns || row < 0 || column < 0 || layerIndex < 0) {
return
@@ -32,6 +35,7 @@ data class TilePaintingTrace(val map: GameMapVM, override val commandName: Strin
if (!tileAlreadyPainted) {
trace += Element(layerIndex, row, column, formerTile, tile)
layer[row][column] = tile
executed = true
}
}

View File

@@ -28,6 +28,10 @@ class Brush {
var mode by modeProperty
private set
val toolProperty = SimpleObjectProperty(BrushTool.DEFAULT)
var tool by toolProperty
private set
private constructor(brushArray: Array<Array<Tile>>) {
rowsProperty.value = brushArray.size
@@ -81,6 +85,7 @@ class Brush {
private fun clone() = Brush(brush, rows, columns).apply {
this.range = this@Brush.range
this.mode = this@Brush.mode
this.tool = this@Brush.tool
}
fun withRange(range: Int) = clone().apply {
@@ -91,6 +96,10 @@ class Brush {
this.mode = mode
}
fun withTool(tool: BrushTool) = clone().apply {
this.tool = tool
}
companion object {
fun of(brushArray: Array<Array<Tile>>) = Brush(brushArray)
}

View File

@@ -0,0 +1,8 @@
package com.bartlomiejpluta.base.editor.map.model.brush
enum class BrushTool {
DEFAULT,
// Object Layer
PASSAGE
}

View File

@@ -5,6 +5,7 @@ import com.bartlomiejpluta.base.editor.command.service.UndoRedoService
import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent
import com.bartlomiejpluta.base.editor.map.controller.MapController
import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode
import com.bartlomiejpluta.base.editor.map.model.brush.BrushTool
import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer
import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer
import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM
@@ -25,11 +26,9 @@ class MapToolbarView : View() {
private val brushVM = find<BrushVM>()
private val editorStateVM = find<EditorStateVM>()
private val brushMode = ToggleGroup().apply {
brushVM.itemProperty.addListener { _, _, brush ->
selectedValueProperty<BrushMode>().value = brush.mode
}
}
private val brushMode = ToggleGroup()
private val objectLayerTool = ToggleGroup()
private val isTileLayerSelected = Bindings.createBooleanBinding(
{ editorStateVM.selectedLayer is TileLayer },
@@ -41,6 +40,17 @@ class MapToolbarView : View() {
editorStateVM.selectedLayerProperty
)
init {
brushVM.itemProperty.addListener { _, _, brush ->
brushMode.selectedValueProperty<BrushMode>().value = brush.mode
objectLayerTool.selectedValueProperty<BrushTool>().value = brush.tool
}
editorStateVM.selectedLayerProperty.addListener { _, _, _ ->
brushVM.item = brushVM.withTool(BrushTool.DEFAULT)
}
}
override val root = toolbar {
button(graphic = FontIcon("fa-floppy-o")) {
action {
@@ -62,6 +72,8 @@ class MapToolbarView : View() {
}
}
separator()
togglebutton {
graphic = FontIcon("fa-window-restore")
@@ -78,6 +90,8 @@ class MapToolbarView : View() {
}
}
separator()
togglebutton(value = BrushMode.PAINTING_MODE, group = brushMode) {
graphic = FontIcon("fa-paint-brush")
@@ -120,5 +134,31 @@ class MapToolbarView : View() {
}
this += FontIcon("fa-paint-brush").apply { iconSize = 15 }
separator {
visibleWhen(isObjectLayerSelected)
}
togglebutton(value = BrushTool.DEFAULT, group = objectLayerTool) {
graphic = FontIcon("fa-cube")
visibleWhen(isObjectLayerSelected)
action {
brushVM.item = brushVM.withTool(BrushTool.DEFAULT)
brushVM.commit()
}
}
togglebutton(value = BrushTool.PASSAGE, group = objectLayerTool) {
graphic = FontIcon("fa-minus-circle")
visibleWhen(isObjectLayerSelected)
action {
brushVM.item = brushVM.withTool(BrushTool.PASSAGE)
brushVM.commit()
}
}
}
}

View File

@@ -3,6 +3,7 @@ package com.bartlomiejpluta.base.editor.map.view.editor
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.map.canvas.PaintingTrace
import com.bartlomiejpluta.base.editor.map.component.MapPane
import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
@@ -27,7 +28,7 @@ class MapView : View() {
private val editorStateVM = find<EditorStateVM>()
private val mapPane = MapPane(mapVM, brushVM, editorStateVM) { undoRedoService.push(it, scope) }
private val mapPane = MapPane(mapVM, brushVM, editorStateVM, this::pushPaintingTraceToUndoRedoService)
private val zoom = Scale(1.0, 1.0, 0.0, 0.0).apply {
xProperty().bind(editorStateVM.zoomProperty)
@@ -41,6 +42,12 @@ class MapView : View() {
subscribe<RedrawMapRequestEvent> { mapPane.render() }
}
private fun pushPaintingTraceToUndoRedoService(trace: PaintingTrace) {
if (trace.executed) {
undoRedoService.push(trace, scope)
}
}
override val root = scrollpane {
prefWidth = 640.0
prefHeight = 480.0

View File

@@ -2,6 +2,7 @@ package com.bartlomiejpluta.base.editor.map.viewmodel
import com.bartlomiejpluta.base.editor.map.model.brush.Brush
import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode
import com.bartlomiejpluta.base.editor.map.model.brush.BrushTool
import com.bartlomiejpluta.base.editor.tileset.model.Tile
import tornadofx.ItemViewModel
import tornadofx.getValue
@@ -21,9 +22,15 @@ class BrushVM : ItemViewModel<Brush>(Brush.of(arrayOf(arrayOf()))) {
val modeProperty = bind(Brush::modeProperty)
val mode by modeProperty
fun forEach(consumer: (row: Int, column: Int, centerRow: Int, centerColumn: Int, tile: Tile?) -> Unit) = item.forEach(consumer)
val toolProperty = bind(Brush::toolProperty)
val tool by toolProperty
fun forEach(consumer: (row: Int, column: Int, centerRow: Int, centerColumn: Int, tile: Tile?) -> Unit) =
item.forEach(consumer)
fun withRange(range: Int) = item.withRange(range)
fun withMode(mode: BrushMode) = item.withMode(mode)
fun withTool(tool: BrushTool) = item.withTool(tool)
}