Compare commits

2 Commits

Author SHA1 Message Date
eb89753ed9 [Editor] Enable graphic assets preview in Project Structure panel 2021-02-21 20:28:07 +01:00
b2cda5fd20 [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).
2021-02-21 20:27:00 +01:00
15 changed files with 163 additions and 37 deletions

View File

@@ -15,4 +15,8 @@ abstract class Asset(directory: ObjectProperty<File>, val uid: String, val sourc
val file by fileProperty
override fun toString() = "${this.javaClass.simpleName}[name=$name, uid=$uid]"
open fun delete() {
file.delete()
}
}

View File

@@ -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<File>,
graphicDirectory: ObjectProperty<File>,
uid: String,
assetSource: String,
val graphicSource: String,
name: String
) : Asset(assetDirectory, uid, assetSource, name) {
constructor(directory: ObjectProperty<File>, 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()
}
}

View File

@@ -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)
GraphicAsset(project.imagesDirectoryProperty, uid, source, name)

View File

@@ -1,20 +1,22 @@
package com.bartlomiejpluta.base.editor.main.view
import com.bartlomiejpluta.base.editor.asset.model.Asset
import com.bartlomiejpluta.base.editor.asset.model.GraphicAsset
import com.bartlomiejpluta.base.editor.image.asset.ImageAsset
import com.bartlomiejpluta.base.editor.main.controller.MainController
import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset
import com.bartlomiejpluta.base.editor.project.context.ProjectContext
import com.bartlomiejpluta.base.editor.tileset.asset.TileSetAsset
import javafx.beans.binding.Bindings
import javafx.beans.binding.Bindings.createObjectBinding
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import javafx.collections.ObservableList
import javafx.scene.Node
import javafx.scene.control.ContextMenu
import javafx.scene.control.MenuItem
import javafx.scene.control.TreeCell
import javafx.scene.control.TreeItem
import javafx.scene.control.*
import javafx.scene.control.cell.TextFieldTreeCell
import javafx.scene.image.Image
import javafx.scene.layout.Priority
import javafx.util.StringConverter
import org.kordamp.ikonli.javafx.FontIcon
import tornadofx.*
@@ -44,6 +46,17 @@ class ProjectStructureView : View() {
)
)
private var projectStructure: TreeView<Any> by singleAssign()
private val selectedItem = SimpleObjectProperty<Any>()
private val graphicAssetPreview = createObjectBinding({
when (val item = selectedItem.value) {
is GraphicAsset -> item.graphicFile.inputStream().use { Image(it) }
else -> null
}
}, selectedItem).apply { addListener { _, _, v -> println(v) } }
init {
projectContext.projectProperty.addListener { _, _, project ->
project?.let {
@@ -51,13 +64,16 @@ class ProjectStructureView : View() {
Bindings.bindContent(structureMaps.items, it.maps)
Bindings.bindContent(structureTileSets.items, it.tileSets)
Bindings.bindContent(structureImages.items, it.images)
root.root.expandAll()
root.refresh()
projectStructure.root.expandAll()
projectStructure.refresh()
}
}
}
override val root = treeview<Any> {
override val root = vbox {
projectStructure = treeview {
vgrow = Priority.ALWAYS
root = TreeItem(structureRoot)
populate {
@@ -80,6 +96,17 @@ class ProjectStructureView : View() {
event.consume()
}
bindSelected(selectedItem)
}
scrollpane {
vgrow = Priority.SOMETIMES
prefWidth = 200.0
prefHeight = 200.0
removeWhen(graphicAssetPreview.isNull)
imageview(graphicAssetPreview)
}
}
private fun renameAsset(asset: Asset, name: String) = asset.apply {

View File

@@ -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)
GraphicAsset(project.mapsDirectoryProperty, project.mapsDirectoryProperty, uid, "$uid.dat", "$uid.png", name)

View File

@@ -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,6 +83,10 @@ 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) }
}

View File

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

View File

@@ -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<EditorStateVM>()
private val mapVM = find<GameMapVM>()
private val mapView = find<MapView>()
private val layersView = find<MapLayersView>()
private val tileSetView = find<TileSetView>()
private val toolbarView = find<MapToolbarView>()
private val toolbarView = find<MapToolbarView>(MapToolbarView::onSaveMap to this::saveMap)
private val statusBarView = find<MapStatusBarView>()
private val layerParameters = find<MapLayerParameters>()
@@ -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

View File

@@ -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<GameMapVM>()
@@ -45,7 +47,7 @@ class MapToolbarView : View() {
button(graphic = FontIcon("fa-floppy-o")) {
shortcut("Ctrl+S")
action {
mapController.saveMap(mapVM.item)
onSaveMap()
}
}

View File

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

View File

@@ -30,4 +30,7 @@ class EditorStateVM : ViewModel() {
val cursorColumnProperty = SimpleIntegerProperty(-1)
val cursorColumn by cursorColumnProperty
val takingSnapshotProperty = SimpleBooleanProperty(false)
var takingSnapshot by takingSnapshotProperty
}

View File

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

View File

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

View File

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

View File

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