From b2cda5fd200e556bde8412b607c025ff19d147a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Przemys=C5=82aw=20Pluta?= Date: Sun, 21 Feb 2021 19:49:22 +0100 Subject: [PATCH] [Editor] Enable creating snapshots when map is being saved Because the new map creation is done in different place than saving already existing maps, there is a bug that no snapshot is created when new map is created. The snapshots only are created when map is saved (Save via button with floppy disk icon in Map View toolbar). --- .../base/editor/asset/model/Asset.kt | 4 +++ .../base/editor/asset/model/GraphicAsset.kt | 27 ++++++++++++++++++ .../base/editor/image/asset/ImageAsset.kt | 4 +-- .../base/editor/map/asset/GameMapAsset.kt | 4 +-- .../base/editor/map/canvas/MapCanvas.kt | 11 +++++++- .../editor/map/controller/MapController.kt | 5 ++-- .../editor/map/view/editor/MapFragment.kt | 11 +++++++- .../editor/map/view/editor/MapToolbarView.kt | 4 ++- .../base/editor/map/view/editor/MapView.kt | 11 ++++++++ .../editor/map/viewmodel/EditorStateVM.kt | 3 ++ .../project/context/DefaultProjectContext.kt | 9 ++++-- .../editor/project/context/ProjectContext.kt | 2 +- .../base/editor/tileset/asset/TileSetAsset.kt | 4 +-- .../base/editor/util/fx/ImageUtil.kt | 28 +++++++++++++++++++ 14 files changed, 113 insertions(+), 14 deletions(-) create mode 100644 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/asset/model/GraphicAsset.kt create mode 100644 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/util/fx/ImageUtil.kt diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/asset/model/Asset.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/asset/model/Asset.kt index 0e63926f..48114882 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/asset/model/Asset.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/asset/model/Asset.kt @@ -15,4 +15,8 @@ abstract class Asset(directory: ObjectProperty, val uid: String, val sourc val file by fileProperty override fun toString() = "${this.javaClass.simpleName}[name=$name, uid=$uid]" + + open fun delete() { + file.delete() + } } \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/asset/model/GraphicAsset.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/asset/model/GraphicAsset.kt new file mode 100644 index 00000000..534efb93 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/asset/model/GraphicAsset.kt @@ -0,0 +1,27 @@ +package com.bartlomiejpluta.base.editor.asset.model + +import javafx.beans.binding.Bindings.createObjectBinding +import javafx.beans.property.ObjectProperty +import tornadofx.getValue +import java.io.File + +abstract class GraphicAsset( + assetDirectory: ObjectProperty, + graphicDirectory: ObjectProperty, + uid: String, + assetSource: String, + val graphicSource: String, + name: String +) : Asset(assetDirectory, uid, assetSource, name) { + + constructor(directory: ObjectProperty, uid: String, source: String, name: String) + : this(directory, directory, uid, source, source, name) + + val graphicFileProperty = createObjectBinding({ File(graphicDirectory.value, graphicSource) }, graphicDirectory) + val graphicFile by graphicFileProperty + + override fun delete() { + super.delete() + graphicFile.delete() + } +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/image/asset/ImageAsset.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/image/asset/ImageAsset.kt index 1354d501..d8e6b251 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/image/asset/ImageAsset.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/image/asset/ImageAsset.kt @@ -1,7 +1,7 @@ package com.bartlomiejpluta.base.editor.image.asset -import com.bartlomiejpluta.base.editor.asset.model.Asset +import com.bartlomiejpluta.base.editor.asset.model.GraphicAsset import com.bartlomiejpluta.base.editor.project.model.Project class ImageAsset(project: Project, uid: String, source: String, name: String) : - Asset(project.imagesDirectoryProperty, uid, source, name) \ No newline at end of file + GraphicAsset(project.imagesDirectoryProperty, uid, source, name) \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/asset/GameMapAsset.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/asset/GameMapAsset.kt index 59b15954..d88d5754 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/asset/GameMapAsset.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/asset/GameMapAsset.kt @@ -1,7 +1,7 @@ package com.bartlomiejpluta.base.editor.map.asset -import com.bartlomiejpluta.base.editor.asset.model.Asset +import com.bartlomiejpluta.base.editor.asset.model.GraphicAsset import com.bartlomiejpluta.base.editor.project.model.Project class GameMapAsset(project: Project, uid: String, name: String) : - Asset(project.mapsDirectoryProperty, uid, "$uid.dat", name) \ No newline at end of file + GraphicAsset(project.mapsDirectoryProperty, project.mapsDirectoryProperty, uid, "$uid.dat", "$uid.png", name) \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapCanvas.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapCanvas.kt index e04f5bd9..66ff2e0c 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapCanvas.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapCanvas.kt @@ -70,6 +70,11 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr override fun render(gc: GraphicsContext) { gc.clearRect(0.0, 0.0, gc.canvas.width, gc.canvas.height) + if (editorStateVM.takingSnapshot) { + renderForPhoto(gc) + return + } + renderBackground(gc) renderUnderlyingLayers(gc) renderCover(gc) @@ -78,12 +83,16 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr painter.render(gc) } + private fun renderForPhoto(gc: GraphicsContext) { + map.layers.forEach { dispatchLayerRender(gc, it) } + } + private fun renderSelectedLayer(gc: GraphicsContext) { map.layers.getOrNull(editorStateVM.selectedLayerIndex)?.let { dispatchLayerRender(gc, it) } } private fun renderCover(gc: GraphicsContext) { - if(!editorStateVM.coverUnderlyingLayers) { + if (!editorStateVM.coverUnderlyingLayers) { return } diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/controller/MapController.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/controller/MapController.kt index edd3e95c..3a2496b2 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/controller/MapController.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/controller/MapController.kt @@ -2,6 +2,7 @@ package com.bartlomiejpluta.base.editor.map.controller import com.bartlomiejpluta.base.editor.map.model.map.GameMap import com.bartlomiejpluta.base.editor.project.context.ProjectContext +import javafx.scene.image.Image import org.springframework.stereotype.Component import tornadofx.Controller @@ -9,7 +10,7 @@ import tornadofx.Controller class MapController : Controller() { private val projectContext: ProjectContext by di() - fun saveMap(map: GameMap) { - projectContext.saveMap(map) + fun saveMap(map: GameMap, image: Image) { + projectContext.saveMap(map, image) } } \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapFragment.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapFragment.kt index e06ee45c..235a85b9 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapFragment.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapFragment.kt @@ -1,8 +1,10 @@ package com.bartlomiejpluta.base.editor.map.view.editor import com.bartlomiejpluta.base.editor.command.context.UndoableScope +import com.bartlomiejpluta.base.editor.map.controller.MapController import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM +import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.tileset.view.editor.TileSetView import javafx.beans.binding.Bindings import tornadofx.* @@ -11,12 +13,15 @@ import tornadofx.* class MapFragment : Fragment() { override val scope = super.scope as UndoableScope + private val mapController: MapController by di() + private val editorStateVM = find() + private val mapVM = find() private val mapView = find() private val layersView = find() private val tileSetView = find() - private val toolbarView = find() + private val toolbarView = find(MapToolbarView::onSaveMap to this::saveMap) private val statusBarView = find() private val layerParameters = find() @@ -25,6 +30,10 @@ class MapFragment : Fragment() { editorStateVM.selectedLayerProperty ) + private fun saveMap() { + mapController.saveMap(mapVM.item, mapView.snapshot()) + } + override val root = borderpane { top = toolbarView.root diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapToolbarView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapToolbarView.kt index e7fda69e..ed72f249 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapToolbarView.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapToolbarView.kt @@ -19,6 +19,8 @@ class MapToolbarView : View() { private val undoRedoService: UndoRedoService by di() private val mapController: MapController by di() + val onSaveMap: () -> Unit by param() + override val scope = super.scope as UndoableScope private val mapVM = find() @@ -45,7 +47,7 @@ class MapToolbarView : View() { button(graphic = FontIcon("fa-floppy-o")) { shortcut("Ctrl+S") action { - mapController.saveMap(mapVM.item) + onSaveMap() } } diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapView.kt index 8e4b86e5..a08c9213 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapView.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapView.kt @@ -7,6 +7,7 @@ import com.bartlomiejpluta.base.editor.map.component.MapPane 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 javafx.scene.image.Image import javafx.scene.input.MouseButton import javafx.scene.input.MouseEvent import javafx.scene.transform.Scale @@ -41,6 +42,16 @@ class MapView : View() { subscribe { mapPane.render() } } + fun snapshot(): Image { + editorStateVM.takingSnapshot = true + mapPane.render() + + return mapPane.snapshot(null, null).also { + editorStateVM.takingSnapshot = false + mapPane.render() + } + } + override val root = scrollpane { prefWidth = 640.0 prefHeight = 480.0 diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/viewmodel/EditorStateVM.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/viewmodel/EditorStateVM.kt index 9cd79882..ba7386ce 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/viewmodel/EditorStateVM.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/viewmodel/EditorStateVM.kt @@ -30,4 +30,7 @@ class EditorStateVM : ViewModel() { val cursorColumnProperty = SimpleIntegerProperty(-1) val cursorColumn by cursorColumnProperty + + val takingSnapshotProperty = SimpleBooleanProperty(false) + var takingSnapshot by takingSnapshotProperty } \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/context/DefaultProjectContext.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/context/DefaultProjectContext.kt index 4234ff68..03b574ae 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/context/DefaultProjectContext.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/context/DefaultProjectContext.kt @@ -13,6 +13,7 @@ import com.bartlomiejpluta.base.editor.project.serial.ProjectSerializer import com.bartlomiejpluta.base.editor.tileset.asset.TileSetAsset import com.bartlomiejpluta.base.editor.tileset.asset.TileSetAssetData import com.bartlomiejpluta.base.editor.tileset.model.TileSet +import com.bartlomiejpluta.base.editor.util.fx.ImageUtil import com.bartlomiejpluta.base.editor.util.uid.UID import javafx.beans.property.ObjectProperty import javafx.beans.property.SimpleObjectProperty @@ -24,6 +25,7 @@ import tornadofx.setValue import java.io.File import java.io.FileInputStream import java.io.FileOutputStream +import kotlin.math.min @Component class DefaultProjectContext : ProjectContext { @@ -88,11 +90,14 @@ class DefaultProjectContext : ProjectContext { File(it.mapsDirectory, asset.source).inputStream().use { fis -> mapDeserializer.deserialize(fis) } } ?: throw IllegalStateException("There is no open project in the context") - override fun saveMap(map: GameMap) { + override fun saveMap(map: GameMap, image: Image) { project?.let { val asset = it.maps.firstOrNull { asset -> asset.uid == map.uid } ?: throw IllegalStateException("The map with uid [${map.uid}] does not exist ") + val thumbnail = ImageUtil.scale(image, min(200, map.width.toInt()), min(200, map.height.toInt()), true) + + ImageUtil.save(thumbnail, asset.graphicFile) File(it.mapsDirectory, asset.source).outputStream().use { fos -> mapSerializer.serialize(map, fos) } } } @@ -151,7 +156,7 @@ class DefaultProjectContext : ProjectContext { it.assetLists.firstOrNull { assets -> assets.remove(asset) } ?: throw IllegalStateException("The asset does not belong to any of the assets lists") - asset.file.delete() + asset.delete() } ?: throw IllegalStateException("There is no open project in the context") } } \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/context/ProjectContext.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/context/ProjectContext.kt index a213f333..f23ee9df 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/context/ProjectContext.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/context/ProjectContext.kt @@ -20,7 +20,7 @@ interface ProjectContext { fun importMap(name: String, map: GameMap) fun loadMap(uid: String): GameMap - fun saveMap(map: GameMap) + fun saveMap(map: GameMap, image: Image) fun importTileSet(data: TileSetAssetData) fun loadTileSet(uid: String): TileSet diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/tileset/asset/TileSetAsset.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/tileset/asset/TileSetAsset.kt index 31759808..2d33c1f0 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/tileset/asset/TileSetAsset.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/tileset/asset/TileSetAsset.kt @@ -1,7 +1,7 @@ package com.bartlomiejpluta.base.editor.tileset.asset -import com.bartlomiejpluta.base.editor.asset.model.Asset +import com.bartlomiejpluta.base.editor.asset.model.GraphicAsset import com.bartlomiejpluta.base.editor.project.model.Project class TileSetAsset(project: Project, uid: String, source: String, name: String, val rows: Int, val columns: Int) : - Asset(project.tileSetsDirectoryProperty, uid, source, name) + GraphicAsset(project.tileSetsDirectoryProperty, uid, source, name) diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/util/fx/ImageUtil.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/util/fx/ImageUtil.kt new file mode 100644 index 00000000..293ae331 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/util/fx/ImageUtil.kt @@ -0,0 +1,28 @@ +package com.bartlomiejpluta.base.editor.util.fx + +import javafx.scene.image.Image +import javafx.scene.image.ImageView +import java.awt.image.BufferedImage +import java.io.File +import javax.imageio.ImageIO + +object ImageUtil { + fun scale(source: Image, targetWidth: Int, targetHeight: Int, preserveRatio: Boolean) = ImageView(source).apply { + this.isSmooth = false + this.isPreserveRatio = preserveRatio + this.fitWidth = targetWidth.toDouble() + this.fitHeight = targetHeight.toDouble() + }.snapshot(null, null) + + fun save(image: Image, file: File) { + val buffered = BufferedImage(image.width.toInt(), image.height.toInt(), BufferedImage.TYPE_INT_ARGB) + val reader = image.pixelReader + for (x in 0 until image.width.toInt()) { + for (y in 0 until image.height.toInt()) { + buffered.setRGB(x, y, reader.getArgb(x, y)) + } + } + + ImageIO.write(buffered, file.extension, file) + } +} \ No newline at end of file