[Editor] Add support for Character Sets

This commit is contained in:
2021-03-01 23:05:07 +01:00
parent de23809f37
commit 5531cc2c66
15 changed files with 251 additions and 7 deletions

View File

@@ -2,6 +2,7 @@ package com.bartlomiejpluta.base.editor.asset.component
import com.bartlomiejpluta.base.editor.asset.model.Asset import com.bartlomiejpluta.base.editor.asset.model.Asset
import com.bartlomiejpluta.base.editor.asset.model.AssetCategory import com.bartlomiejpluta.base.editor.asset.model.AssetCategory
import com.bartlomiejpluta.base.editor.characterset.asset.CharacterSetAsset
import com.bartlomiejpluta.base.editor.image.asset.ImageAsset import com.bartlomiejpluta.base.editor.image.asset.ImageAsset
import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset
import com.bartlomiejpluta.base.editor.tileset.asset.TileSetAsset import com.bartlomiejpluta.base.editor.tileset.asset.TileSetAsset
@@ -62,6 +63,7 @@ class AssetTreeCell(renameAsset: (asset: Asset, name: String) -> Asset, deleteAs
is GameMapAsset -> FontIcon("fa-map") is GameMapAsset -> FontIcon("fa-map")
is TileSetAsset -> FontIcon("fa-th") is TileSetAsset -> FontIcon("fa-th")
is ImageAsset -> FontIcon("fa-image") is ImageAsset -> FontIcon("fa-image")
is CharacterSetAsset -> FontIcon("fa-male")
else -> null else -> null
} }
} }

View File

@@ -27,11 +27,16 @@ class AssetsListView : View() {
menuitem("Import Image...") { mainController.importImage() } menuitem("Import Image...") { mainController.importImage() }
} }
private val characterSets = AssetCategory("Character Sets").apply {
menuitem("Import Character Set...") { mainController.importCharacterSet() }
}
private val rootItem = AssetCategory( private val rootItem = AssetCategory(
name = "Project", items = observableListOf( name = "Project", items = observableListOf(
maps, maps,
tileSets, tileSets,
images images,
characterSets
) )
) )
@@ -42,6 +47,7 @@ class AssetsListView : View() {
Bindings.bindContent(maps.items, it.maps) Bindings.bindContent(maps.items, it.maps)
Bindings.bindContent(tileSets.items, it.tileSets) Bindings.bindContent(tileSets.items, it.tileSets)
Bindings.bindContent(images.items, it.images) Bindings.bindContent(images.items, it.images)
Bindings.bindContent(characterSets.items, it.characterSets)
root.root.expandAll() root.root.expandAll()
} }
} }

View File

@@ -0,0 +1,7 @@
package com.bartlomiejpluta.base.editor.characterset.asset
import com.bartlomiejpluta.base.editor.asset.model.Asset
import com.bartlomiejpluta.base.editor.project.model.Project
class CharacterSetAsset(project: Project, uid: String, source: String, name: String, val rows: Int, val columns: Int) :
Asset(project.characterSetsDirectoryProperty, uid, source, name)

View File

@@ -0,0 +1,22 @@
package com.bartlomiejpluta.base.editor.characterset.asset
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import tornadofx.getValue
import tornadofx.setValue
import java.io.File
class CharacterSetAssetData {
val nameProperty = SimpleStringProperty()
var name by nameProperty
val fileProperty = SimpleObjectProperty<File>()
var file by fileProperty
val rowsProperty = SimpleIntegerProperty()
var rows by rowsProperty
val columnsProperty = SimpleIntegerProperty()
var columns by columnsProperty
}

View File

@@ -0,0 +1,108 @@
package com.bartlomiejpluta.base.editor.characterset.view.importing
import com.bartlomiejpluta.base.editor.characterset.asset.CharacterSetAssetData
import com.bartlomiejpluta.base.editor.characterset.viewmodel.CharacterSetAssetDataVM
import com.bartlomiejpluta.base.editor.util.fx.TextFieldUtil
import javafx.beans.property.SimpleObjectProperty
import javafx.scene.Cursor
import javafx.scene.image.Image
import javafx.stage.FileChooser
import tornadofx.*
class ImportCharacterSetFragment : Fragment("Import Spite") {
private val dataVM = find<CharacterSetAssetDataVM>()
private val imagePreview = SimpleObjectProperty<Image?>()
private var onCompleteConsumer: ((CharacterSetAssetData) -> Unit)? = null
init {
dataVM.fileProperty.addListener { _, _, file ->
when (file) {
null -> imagePreview.value = null
else -> file.inputStream().use { imagePreview.value = Image(it) }
}
}
}
fun onComplete(consumer: (CharacterSetAssetData) -> Unit) {
this.onCompleteConsumer = consumer
}
override val root = form {
prefHeight = 480.0
fieldset("Import Character Set") {
hbox {
vbox {
scrollpane {
prefWidth = 300.0
prefHeightProperty().bind(this@form.heightProperty())
imageview(imagePreview)
tooltip = tooltip("Click to choose Character Set file")
cursor = Cursor.HAND
setOnMouseClicked {
dataVM.file = chooseFile(
title = "Select Character Set",
filters = arrayOf(FileChooser.ExtensionFilter("PNG Images (*.png)", "*.png"))
).getOrNull(0)
}
dataVM.validationContext.addValidator(this@vbox, dataVM.fileProperty) {
when {
it == null -> error("This field is required")
!it.exists() -> error("The file must exist")
else -> null
}
}
}
}
vbox {
paddingLeft = 20.0
field("Character Set Name") {
textfield(dataVM.nameProperty) {
required()
trimWhitespace()
}
}
field("Character Set Rows") {
spinner(min = 1, max = Integer.MAX_VALUE, property = dataVM.rowsProperty, editable = true) {
required()
editor.textFormatter = TextFieldUtil.integerFormatter(dataVM.rows)
}
}
field("Character Set Columns") {
spinner(min = 1, max = Integer.MAX_VALUE, property = dataVM.columnsProperty, editable = true) {
required()
editor.textFormatter = TextFieldUtil.integerFormatter(dataVM.columns)
}
}
}
}
}
buttonbar {
button("Import") {
action {
if (dataVM.commit()) {
onCompleteConsumer?.let { it(dataVM.item) }
close()
}
}
}
button("Reset") {
action { dataVM.rollback() }
}
button("Cancel") {
action { close() }
}
}
}
}

View File

@@ -0,0 +1,20 @@
package com.bartlomiejpluta.base.editor.characterset.viewmodel
import com.bartlomiejpluta.base.editor.characterset.asset.CharacterSetAssetData
import tornadofx.ItemViewModel
import tornadofx.getValue
import tornadofx.setValue
class CharacterSetAssetDataVM : ItemViewModel<CharacterSetAssetData>(CharacterSetAssetData()) {
val nameProperty = bind(CharacterSetAssetData::nameProperty)
var name by nameProperty
val fileProperty = bind(CharacterSetAssetData::fileProperty)
var file by fileProperty
val rowsProperty = bind(CharacterSetAssetData::rowsProperty)
var rows by rowsProperty
val columnsProperty = bind(CharacterSetAssetData::columnsProperty)
var columns by columnsProperty
}

View File

@@ -1,6 +1,8 @@
package com.bartlomiejpluta.base.editor.main.controller package com.bartlomiejpluta.base.editor.main.controller
import com.bartlomiejpluta.base.editor.asset.model.Asset import com.bartlomiejpluta.base.editor.asset.model.Asset
import com.bartlomiejpluta.base.editor.characterset.view.importing.ImportCharacterSetFragment
import com.bartlomiejpluta.base.editor.characterset.viewmodel.CharacterSetAssetDataVM
import com.bartlomiejpluta.base.editor.code.model.Code import com.bartlomiejpluta.base.editor.code.model.Code
import com.bartlomiejpluta.base.editor.code.model.CodeScope import com.bartlomiejpluta.base.editor.code.model.CodeScope
import com.bartlomiejpluta.base.editor.code.viewmodel.CodeVM import com.bartlomiejpluta.base.editor.code.viewmodel.CodeVM
@@ -151,6 +153,20 @@ class MainController : Controller() {
} }
} }
fun importCharacterSet() {
val vm = CharacterSetAssetDataVM()
val scope = Scope()
setInScope(vm, scope)
find<ImportCharacterSetFragment>(scope).apply {
onComplete {
projectContext.importCharacterSet(it)
}
openModal(block = true, resizable = false)
}
}
fun closeScript(fsNode: FileNode) { fun closeScript(fsNode: FileNode) {
openItems.entries.firstOrNull { (_, item) -> item is Code && item.fileNode.absolutePath == fsNode.absolutePath }?.key?.let { openItems.entries.firstOrNull { (_, item) -> item is Code && item.fileNode.absolutePath == fsNode.absolutePath }?.key?.let {
openItems.remove(it) openItems.remove(it)

View File

@@ -50,6 +50,12 @@ class MainMenuView : View() {
mainController.importImage() mainController.importImage()
} }
} }
item("Character Set...") {
action {
mainController.importCharacterSet()
}
}
} }
} }

View File

@@ -1,6 +1,8 @@
package com.bartlomiejpluta.base.editor.project.context package com.bartlomiejpluta.base.editor.project.context
import com.bartlomiejpluta.base.editor.asset.model.Asset import com.bartlomiejpluta.base.editor.asset.model.Asset
import com.bartlomiejpluta.base.editor.characterset.asset.CharacterSetAsset
import com.bartlomiejpluta.base.editor.characterset.asset.CharacterSetAssetData
import com.bartlomiejpluta.base.editor.code.model.Code import com.bartlomiejpluta.base.editor.code.model.Code
import com.bartlomiejpluta.base.editor.code.model.CodeType import com.bartlomiejpluta.base.editor.code.model.CodeType
import com.bartlomiejpluta.base.editor.code.service.JavaClassService import com.bartlomiejpluta.base.editor.code.service.JavaClassService
@@ -162,6 +164,19 @@ class DefaultProjectContext : ProjectContext {
File(it.imagesDirectory, asset.source).inputStream().use { fis -> Image(fis) } File(it.imagesDirectory, asset.source).inputStream().use { fis -> Image(fis) }
} ?: throw IllegalStateException("There is no open project in the context") } ?: throw IllegalStateException("There is no open project in the context")
override fun importCharacterSet(data: CharacterSetAssetData) {
project?.let {
UID.next(it.characterSets.map(Asset::uid)).let { uid ->
val source = "$uid.${data.file.extension}"
val targetFile = File(it.characterSetsDirectory, source)
data.file.copyTo(targetFile)
it.characterSets += CharacterSetAsset(it, uid, source, data.name, data.rows, data.columns)
save()
}
}
}
override fun deleteAsset(asset: Asset) { override fun deleteAsset(asset: Asset) {
project?.let { project?.let {
it.assetLists.firstOrNull { assets -> assets.remove(asset) } it.assetLists.firstOrNull { assets -> assets.remove(asset) }

View File

@@ -1,6 +1,7 @@
package com.bartlomiejpluta.base.editor.project.context package com.bartlomiejpluta.base.editor.project.context
import com.bartlomiejpluta.base.editor.asset.model.Asset import com.bartlomiejpluta.base.editor.asset.model.Asset
import com.bartlomiejpluta.base.editor.characterset.asset.CharacterSetAssetData
import com.bartlomiejpluta.base.editor.code.model.Code import com.bartlomiejpluta.base.editor.code.model.Code
import com.bartlomiejpluta.base.editor.file.model.FileNode import com.bartlomiejpluta.base.editor.file.model.FileNode
import com.bartlomiejpluta.base.editor.image.asset.ImageAsset import com.bartlomiejpluta.base.editor.image.asset.ImageAsset
@@ -32,8 +33,9 @@ interface ProjectContext {
fun findImageAsset(uid: String): ImageAsset fun findImageAsset(uid: String): ImageAsset
fun loadImage(uid: String): Image fun loadImage(uid: String): Image
fun deleteAsset(asset: Asset) fun importCharacterSet(data: CharacterSetAssetData)
fun deleteAsset(asset: Asset)
fun loadScript(fileNode: FileNode): Code fun loadScript(fileNode: FileNode): Code
fun saveScript(code: Code) fun saveScript(code: Code)
} }

View File

@@ -1,5 +1,6 @@
package com.bartlomiejpluta.base.editor.project.model package com.bartlomiejpluta.base.editor.project.model
import com.bartlomiejpluta.base.editor.characterset.asset.CharacterSetAsset
import com.bartlomiejpluta.base.editor.file.model.FileSystemNode import com.bartlomiejpluta.base.editor.file.model.FileSystemNode
import com.bartlomiejpluta.base.editor.image.asset.ImageAsset import com.bartlomiejpluta.base.editor.image.asset.ImageAsset
import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset
@@ -28,8 +29,9 @@ class Project {
val maps = observableListOf<GameMapAsset>() val maps = observableListOf<GameMapAsset>()
val tileSets = observableListOf<TileSetAsset>() val tileSets = observableListOf<TileSetAsset>()
val images = observableListOf<ImageAsset>() val images = observableListOf<ImageAsset>()
val characterSets = observableListOf<CharacterSetAsset>()
val assetLists = listOf(maps, tileSets, images) val assetLists = listOf(maps, tileSets, images, characterSets)
val mapsDirectoryProperty = SimpleObjectProperty<File>() val mapsDirectoryProperty = SimpleObjectProperty<File>()
var mapsDirectory by mapsDirectoryProperty var mapsDirectory by mapsDirectoryProperty
@@ -43,6 +45,10 @@ class Project {
var imagesDirectory by imagesDirectoryProperty var imagesDirectory by imagesDirectoryProperty
private set private set
val characterSetsDirectoryProperty = SimpleObjectProperty<File>()
var characterSetsDirectory by characterSetsDirectoryProperty
private set
val codeDirectoryProperty = SimpleObjectProperty<File>() val codeDirectoryProperty = SimpleObjectProperty<File>()
var codeDirectory by codeDirectoryProperty var codeDirectory by codeDirectoryProperty
private set private set
@@ -70,8 +76,9 @@ class Project {
sourceDirectoryProperty.addListener { _, _, dir -> sourceDirectoryProperty.addListener { _, _, dir ->
dir?.let { dir?.let {
mapsDirectory = File(it, MAPS_DIR) mapsDirectory = File(it, MAPS_DIR)
tileSetsDirectory = File(it, TILESETS_DIR) tileSetsDirectory = File(it, TILE_SETS_DIR)
imagesDirectory = File(it, IMAGES_DIR) imagesDirectory = File(it, IMAGES_DIR)
characterSetsDirectory = File(it, CHARACTER_SETS_DIR)
codeDirectory = File(it, CODE_DIR) codeDirectory = File(it, CODE_DIR)
buildDirectory = File(it, BUILD_DIR) buildDirectory = File(it, BUILD_DIR)
buildClassesDirectory = File(it, BUILD_CLASSES_DIR) buildClassesDirectory = File(it, BUILD_CLASSES_DIR)
@@ -85,6 +92,7 @@ class Project {
mapsDirectory?.mkdirs() mapsDirectory?.mkdirs()
tileSetsDirectory?.mkdirs() tileSetsDirectory?.mkdirs()
imagesDirectory?.mkdirs() imagesDirectory?.mkdirs()
characterSetsDirectory?.mkdirs()
codeDirectory?.mkdirs() codeDirectory?.mkdirs()
} }
@@ -93,8 +101,9 @@ class Project {
const val PROJECT_OUTPUT_JAR_FILE = "game.jar" const val PROJECT_OUTPUT_JAR_FILE = "game.jar"
const val MAPS_DIR = "maps" const val MAPS_DIR = "maps"
const val TILESETS_DIR = "tilesets" const val TILE_SETS_DIR = "tilesets"
const val IMAGES_DIR = "images" const val IMAGES_DIR = "images"
const val CHARACTER_SETS_DIR = "charsets"
const val CODE_DIR = "code" const val CODE_DIR = "code"
const val BUILD_DIR = "build" const val BUILD_DIR = "build"
const val BUILD_CLASSES_DIR = "$BUILD_DIR/classes" const val BUILD_CLASSES_DIR = "$BUILD_DIR/classes"

View File

@@ -1,5 +1,6 @@
package com.bartlomiejpluta.base.editor.project.serial package com.bartlomiejpluta.base.editor.project.serial
import com.bartlomiejpluta.base.editor.characterset.asset.CharacterSetAsset
import com.bartlomiejpluta.base.editor.image.asset.ImageAsset import com.bartlomiejpluta.base.editor.image.asset.ImageAsset
import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset
import com.bartlomiejpluta.base.editor.project.model.Project import com.bartlomiejpluta.base.editor.project.model.Project
@@ -19,6 +20,7 @@ class ProtobufProjectDeserializer : ProjectDeserializer {
project.maps.addAll(proto.mapsList.map { deserializeMap(project, it) }) project.maps.addAll(proto.mapsList.map { deserializeMap(project, it) })
project.tileSets.addAll(proto.tileSetsList.map { deserializeTileSet(project, it) }) project.tileSets.addAll(proto.tileSetsList.map { deserializeTileSet(project, it) })
project.images.addAll(proto.imagesList.map { deserializeImage(project, it) }) project.images.addAll(proto.imagesList.map { deserializeImage(project, it) })
project.characterSets.addAll(proto.characterSetsList.map { deserializeCharacterSet(project, it) })
return project return project
} }
@@ -44,4 +46,14 @@ class ProtobufProjectDeserializer : ProjectDeserializer {
source = image.source, source = image.source,
name = image.name name = image.name
) )
private fun deserializeCharacterSet(project: Project, characterSetAsset: ProjectProto.CharacterSetAsset) =
CharacterSetAsset(
project = project,
uid = characterSetAsset.uid,
source = characterSetAsset.source,
name = characterSetAsset.name,
rows = characterSetAsset.rows,
columns = characterSetAsset.columns
)
} }

View File

@@ -1,5 +1,6 @@
package com.bartlomiejpluta.base.editor.project.serial package com.bartlomiejpluta.base.editor.project.serial
import com.bartlomiejpluta.base.editor.characterset.asset.CharacterSetAsset
import com.bartlomiejpluta.base.editor.image.asset.ImageAsset import com.bartlomiejpluta.base.editor.image.asset.ImageAsset
import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset
import com.bartlomiejpluta.base.editor.project.model.Project import com.bartlomiejpluta.base.editor.project.model.Project
@@ -18,6 +19,7 @@ class ProtobufProjectSerializer : ProjectSerializer {
proto.addAllMaps(item.maps.map(this::serializeMap)) proto.addAllMaps(item.maps.map(this::serializeMap))
proto.addAllTileSets(item.tileSets.map(this::serializeTileSet)) proto.addAllTileSets(item.tileSets.map(this::serializeTileSet))
proto.addAllImages(item.images.map(this::serializeImage)) proto.addAllImages(item.images.map(this::serializeImage))
proto.addAllCharacterSets(item.characterSets.map(this::serializeCharacterSet))
proto.build().writeTo(output) proto.build().writeTo(output)
} }
@@ -40,4 +42,12 @@ class ProtobufProjectSerializer : ProjectSerializer {
.setSource(image.source) .setSource(image.source)
.setName(image.name) .setName(image.name)
.build() .build()
private fun serializeCharacterSet(characterSet: CharacterSetAsset) = ProjectProto.CharacterSetAsset.newBuilder()
.setUid(characterSet.uid)
.setSource(characterSet.source)
.setName(characterSet.name)
.setRows(characterSet.rows)
.setColumns(characterSet.columns)
.build()
} }

View File

@@ -38,12 +38,12 @@ class ImportTileSetFragment : Fragment("Import Tile Set") {
prefWidth = 300.0 prefWidth = 300.0
prefHeightProperty().bind(this@form.heightProperty()) prefHeightProperty().bind(this@form.heightProperty())
imageview(imagePreview) imageview(imagePreview)
tooltip = tooltip("Click to choose sprite file") tooltip = tooltip("Click to choose Tile Set file")
cursor = Cursor.HAND cursor = Cursor.HAND
setOnMouseClicked { setOnMouseClicked {
dataVM.file = chooseFile( dataVM.file = chooseFile(
title = "Select Sprite", title = "Select Tile Set",
filters = arrayOf(FileChooser.ExtensionFilter("PNG Images (*.png)", "*.png")) filters = arrayOf(FileChooser.ExtensionFilter("PNG Images (*.png)", "*.png"))
).getOrNull(0) ).getOrNull(0)
} }

View File

@@ -9,6 +9,7 @@ message Project {
repeated GameMapAsset maps = 3; repeated GameMapAsset maps = 3;
repeated TileSetAsset tileSets = 4; repeated TileSetAsset tileSets = 4;
repeated ImageAsset images = 5; repeated ImageAsset images = 5;
repeated CharacterSetAsset characterSets = 6;
} }
message GameMapAsset { message GameMapAsset {
@@ -29,4 +30,12 @@ message ImageAsset {
required string uid = 1; required string uid = 1;
required string source = 2; required string source = 2;
required string name = 3; required string name = 3;
}
message CharacterSetAsset {
required string uid = 1;
required string source = 2;
required string name = 3;
required uint32 rows = 4;
required uint32 columns = 5;
} }