[Editor] Enable full support for map labels

From now on, the editor is capable to put map labels in the canavs as well as generate the map labels in output map serialized file.
This commit is contained in:
2023-11-01 16:49:53 +01:00
parent 3d6e064b5d
commit 0470a7f24a
16 changed files with 462 additions and 71 deletions

View File

@@ -0,0 +1,14 @@
package com.bartlomiejpluta.base.api.map.layer.object;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import lombok.Value;
@Value
@Builder
public class MapPin {
String map;
int layer;
int x;
int y;
}

View File

@@ -2,13 +2,17 @@ package com.bartlomiejpluta.base.editor.code.build.generator
import com.bartlomiejpluta.base.editor.asset.model.Asset
import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset
import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer
import com.bartlomiejpluta.base.editor.map.serial.MapDeserializer
import com.bartlomiejpluta.base.editor.project.context.ProjectContext
import com.bartlomiejpluta.base.editor.project.model.Project
import com.squareup.javapoet.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import java.time.Instant
import java.time.format.DateTimeFormatter
import java.util.*
import javax.annotation.processing.Generated
import javax.lang.model.element.Modifier
@Component
@@ -46,10 +50,17 @@ class AssetMapCodeGenerator : CodeGenerator {
}
private fun generateAssetClass(name: String, assets: List<Asset>): TypeSpec {
val generatedAnnotation = AnnotationSpec.builder(Generated::class.java).addMember("value", "\$S", GENERATOR_NAME)
.addMember("date", "\$S", DateTimeFormatter.ISO_INSTANT.format(Instant.now()))
.addMember("comments", "\$S", "Utility class for $name assets")
.build()
val className = ClassName.get("A", name)
return TypeSpec
.classBuilder(className)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addAnnotation(generatedAnnotation)
.addField(
FieldSpec.builder(
ParameterizedTypeName.get(
@@ -69,13 +80,13 @@ class AssetMapCodeGenerator : CodeGenerator {
}
.build()
)
.addField(String::class.java, "uid", Modifier.PUBLIC, Modifier.FINAL)
.addField(String::class.java, "$", Modifier.PUBLIC, Modifier.FINAL)
.addMethod(
MethodSpec
.constructorBuilder()
.addModifiers(Modifier.PRIVATE)
.addParameter(TypeName.get(String::class.java), "uid")
.addStatement("this.uid = uid")
.addStatement("this.\$\$ = uid")
.build()
)
.addMethod(
@@ -101,15 +112,22 @@ class AssetMapCodeGenerator : CodeGenerator {
}
private fun generateMapAssetClass(name: String, assets: List<GameMapAsset>): TypeSpec {
val generatedAnnotation = AnnotationSpec.builder(Generated::class.java).addMember("value", "\$S", GENERATOR_NAME)
.addMember("date", "\$S", DateTimeFormatter.ISO_INSTANT.format(Instant.now()))
.addMember("comments", "\$S", "Utility class for $name assets")
.build()
val className = ClassName.get("A", name)
val mapLayers = assets
.map { asset -> asset to mapDeserializer.deserialize(asset.file.inputStream()) }
.associate { (asset, map) -> asset to map.layers }
val abstractAssetClassName = ClassName.get("", "GameMapAsset")
val abstractLayerClassName = ClassName.get("", "GameMapAssetLayer")
return TypeSpec
.classBuilder(className)
.addAnnotation(generatedAnnotation)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addField(
FieldSpec.builder(
@@ -122,67 +140,33 @@ class AssetMapCodeGenerator : CodeGenerator {
.initializer("new \$T<>()", java.util.HashMap::class.java)
.build()
)
.addField(
FieldSpec.builder(
ParameterizedTypeName.get(
ClassName.get(java.util.Map::class.java),
ClassName.get(String::class.java),
ClassName.get(Integer::class.java)
), "_layers", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL
)
.initializer("new \$T<>()", java.util.HashMap::class.java)
.build()
)
.addStaticBlock(CodeBlock.builder()
.apply {
assets.forEach {
addStatement("_maps.put(\"${it.name}\", ${getAssetName(it)})")
}
}
.apply {
mapLayers.forEach { asset, layers ->
layers.forEach { layer ->
addStatement(
"_layers.put(\"${asset.name}::${layer.name}\", ${getAssetName(asset)}.layers.${
getAssetName(
layer.name
)
})"
)
}
}
}
.build()
)
.addMethod(
MethodSpec
.methodBuilder("get")
.methodBuilder("byName")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
.returns(abstractAssetClassName)
.addParameter(TypeName.get(String::class.java), "name")
.addStatement("return _maps.get(name)")
.build()
)
.addMethod(
MethodSpec
.methodBuilder("getLayer")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
.returns(ClassName.get(Integer::class.java))
.addParameter(TypeName.get(String::class.java), "mapName")
.addParameter(TypeName.get(String::class.java), "layerName")
.addStatement("return _layers.get(mapName + \"::\" + layerName)")
.build()
)
.addType(
TypeSpec.classBuilder(abstractAssetClassName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.ABSTRACT)
.addField(String::class.java, "uid", Modifier.PUBLIC, Modifier.FINAL)
.addField(String::class.java, "$", Modifier.PUBLIC, Modifier.FINAL)
.addField(
FieldSpec.builder(
ParameterizedTypeName.get(
ClassName.get(java.util.Map::class.java),
ClassName.get(String::class.java),
ClassName.get(Integer::class.java)
abstractLayerClassName
), "_layers", Modifier.PROTECTED, Modifier.FINAL
)
.initializer("new \$T<>()", java.util.HashMap::class.java)
@@ -191,23 +175,52 @@ class AssetMapCodeGenerator : CodeGenerator {
.addMethod(
MethodSpec.constructorBuilder()
.addParameter(ClassName.get(String::class.java), "uid")
.addStatement("this.uid = uid")
.addStatement("this.\$\$ = uid")
.build()
)
.addMethod(
MethodSpec.methodBuilder("get")
MethodSpec.methodBuilder("layer")
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.get(String::class.java), "name")
.returns(ClassName.get(Integer::class.java))
.returns(abstractLayerClassName)
.addStatement("return this._layers.get(name)")
.build()
)
.build()
).addType(
TypeSpec.classBuilder(abstractLayerClassName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.ABSTRACT)
.addField(TypeName.INT, "$", Modifier.PUBLIC, Modifier.FINAL)
.addField(
FieldSpec.builder(
ParameterizedTypeName.get(
ClassName.get(java.util.Map::class.java),
ClassName.get(String::class.java),
MAP_PIN_TYPE
), "_labels", Modifier.PROTECTED, Modifier.FINAL
)
.initializer("new \$T<>()", java.util.HashMap::class.java)
.build()
)
.addMethod(
MethodSpec.constructorBuilder()
.addParameter(TypeName.INT, "index")
.addStatement("this.\$\$ = index")
.build()
)
.addMethod(
MethodSpec.methodBuilder("label")
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.get(String::class.java), "label")
.returns(MAP_PIN_TYPE)
.addStatement("return this._labels.get(label)")
.build()
)
.build()
)
.apply {
mapLayers.forEach { (asset, layers) ->
val assetClassName = ClassName.get("", "GameMapAsset_${getCapitalizedAssetName(asset)}")
val layersClassName = ClassName.get("", "GameMapAsset_Layers_${getCapitalizedAssetName(asset)}")
val assetClassName = ClassName.get("", getCapitalizedAssetName(asset))
addField(
FieldSpec
@@ -220,36 +233,62 @@ class AssetMapCodeGenerator : CodeGenerator {
TypeSpec.classBuilder(assetClassName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.superclass(abstractAssetClassName)
.addField(
FieldSpec.builder(layersClassName, "layers", Modifier.PUBLIC, Modifier.FINAL)
.initializer("new \$T()", layersClassName)
.build()
)
.addMethod(
MethodSpec.constructorBuilder()
.addStatement("super(\"${asset.uid}\")")
.apply {
layers.forEach { layer ->
addStatement("this._layers.put(\"${layer.name}\", layers.${getAssetName(layer.name)})")
addStatement("this._layers.put(\"${layer.name}\", this.${getAssetName(layer.name)})")
}
}
.build()
)
.build()
)
addType(
TypeSpec.classBuilder(layersClassName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.apply {
layers.forEachIndexed { index, layer ->
val layerClass = ClassName.get("", getCapitalizedAssetName(layer.name))
val type = TypeSpec.classBuilder(layerClass)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.superclass(abstractLayerClassName)
.addMethod(
MethodSpec
.constructorBuilder()
.addStatement("super($index)")
.apply {
if (layer is ObjectLayer) {
layer.labels.forEach { label ->
addStatement("this._labels.put(\"${label.label}\", this.${getAssetName(label.label)})")
}
}
}
.build()
)
//.addField(FieldSpec.builder(TypeName.INT, "$", Modifier.PUBLIC, Modifier.FINAL).initializer(index.toString()).build())
.apply {
if (layer is ObjectLayer) {
layer.labels.forEach { label ->
addField(FieldSpec.builder(MAP_PIN_TYPE, getAssetName(label.label), Modifier.FINAL, Modifier.PUBLIC)
.initializer("\$T.builder()" +
".map(\"${asset.uid}\")" +
".layer(${index})" +
".x(${label.x})" +
".y(${label.y})" +
".build()", MAP_PIN_TYPE)
.build())
}
}
}
.build()
addType(type)
addField(
FieldSpec.builder(
TypeName.INT,
layerClass,
getAssetName(layer.name),
Modifier.PUBLIC,
Modifier.FINAL
)
.initializer(index.toString())
.initializer("new \$T()", layerClass)
.build()
)
}
@@ -272,4 +311,9 @@ class AssetMapCodeGenerator : CodeGenerator {
private fun getCapitalizedAssetName(name: String) = name
.split("\\s+".toRegex())
.joinToString("") { part -> part.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } }
companion object {
val MAP_PIN_TYPE = ClassName.get("com.bartlomiejpluta.base.api.map.layer.object", "MapPin")
val GENERATOR_NAME = AssetMapCodeGenerator::class.java.canonicalName
}
}

View File

@@ -381,12 +381,11 @@ class DataAccessObjectCodeGenerator : CodeGenerator {
.addStatement("var list = new \$T<\$T>()", LinkedList::class.java, model)
.beginControlFlow("\$T.INSTANCE.getContext().withDatabase(db ->", CONTEXT_HOLDER)
.addStatement(
"var filter = filters.stream().map(f -> String.format(\"`%s` %s ?\", f.column.column, f.op.getOp())).collect(\$T.joining(\" AND \"))",
COLLECTORS_CLASS
"var filter = filters.isEmpty() ? \"\" : \" WHERE \" + filters.stream().map(f -> String.format(\"`%s` %s ?\", f.column.column, f.op.getOp())).collect(\$T.joining(\" AND \"))", COLLECTORS_CLASS
)
.addStatement("var order = ordering.isEmpty() ? \"\" : \" ORDER BY \" + ordering.stream().collect(\$T.joining(\", \"))", COLLECTORS_CLASS)
.apply {
val sql = "SELECT * FROM `${table.name}` WHERE "
val sql = "SELECT * FROM `${table.name}` "
addStatement("var statement = db.prepareStatement(\"$sql\" + filter + order)")
}
.addStatement("var i = 1")
@@ -407,6 +406,39 @@ class DataAccessObjectCodeGenerator : CodeGenerator {
.addStatement("return list")
.build()
)
.addMethod(
MethodSpec.methodBuilder("first")
.addModifiers(Modifier.PUBLIC)
.returns(model)
.addStatement("var list = new \$T<\$T>()", LinkedList::class.java, model)
.beginControlFlow("return \$T.INSTANCE.getContext().withDatabase(db ->", CONTEXT_HOLDER)
.addStatement(
"var filter = filters.isEmpty() ? \"\" : \" WHERE \" + filters.stream().map(f -> String.format(\"`%s` %s ?\", f.column.column, f.op.getOp())).collect(\$T.joining(\" AND \"))",
COLLECTORS_CLASS
)
.addStatement("var order = ordering.isEmpty() ? \"\" : \" ORDER BY \" + ordering.stream().collect(\$T.joining(\", \"))", COLLECTORS_CLASS)
.apply {
val sql = "SELECT * FROM `${table.name}` "
addStatement("var statement = db.prepareStatement(\"$sql\" + filter + order + \" FETCH FIRST ROW ONLY\")")
}
.addStatement("var i = 1")
.beginControlFlow("for (var f : filters)")
.addStatement("statement.setObject(i++, f.value)")
.endControlFlow()
.addStatement("var result = statement.executeQuery()")
.beginControlFlow("if(result.next())")
.addStatement("var model = ${model.simpleName()}.builder()")
.apply {
table.columns.forEach { column ->
addStatement("model.${snakeToCamelCase(column.name)}(result.${dbToGetMethod(column)}(\"${column.name}\"))")
}
}
.addStatement("return model.build()")
.endControlFlow()
.addStatement("return null")
.endControlFlow(")")
.build()
)
.build()
)
.build()

View File

@@ -37,9 +37,11 @@ class MapObjectsCodeGenerator : CodeGenerator {
private fun generateMapObjects(asset: GameMapAsset, map: GameMap, project: Project) {
val runner = className(project.runner)
map.layers
.mapNotNull { it as? ObjectLayer }
.forEach { generateLayerClass(project.buildGeneratedCodeDirectory, asset, map, it, runner) }
map.layers.forEachIndexed { index, layer ->
if (layer is ObjectLayer) {
generateLayerClass(project.buildGeneratedCodeDirectory, asset, map, layer, index, runner)
}
}
}
private fun generateLayerClass(
@@ -47,6 +49,7 @@ class MapObjectsCodeGenerator : CodeGenerator {
asset: GameMapAsset,
map: GameMap,
layer: ObjectLayer,
layerIndex: Int,
runner: ClassName
) {
val packageName = "com.bartlomiejpluta.base.generated.map"
@@ -77,6 +80,7 @@ class MapObjectsCodeGenerator : CodeGenerator {
.addParameter(EngineObjectLayer::class.java, "layer", Modifier.FINAL)
.addParameter(TypeName.INT, "x", Modifier.FINAL)
.addParameter(TypeName.INT, "y", Modifier.FINAL)
.addParameter(MAP_PIN_TYPE, "here", Modifier.FINAL)
.addCode(it.code)
.build()
.let(generatedClass::addMethod)
@@ -93,7 +97,7 @@ class MapObjectsCodeGenerator : CodeGenerator {
.addStatement("var customHandler = (\$T) handler", handler)
layer.objects.forEach {
runMethod.addStatement("_${it.x}x${it.y}(context, customRunner, customHandler, map, layer, ${it.x}, ${it.y})")
runMethod.addStatement("_${it.x}x${it.y}(context, customRunner, customHandler, map, layer, ${it.x}, ${it.y}, \$T.builder().map(\"${map.uid}\").layer($layerIndex).x(${it.x}).y(${it.y}).build())", MAP_PIN_TYPE)
}
generatedClass
@@ -132,5 +136,6 @@ class MapObjectsCodeGenerator : CodeGenerator {
companion object {
private val GENERATOR_NAME = MapObjectsCodeGenerator::class.java.canonicalName
val MAP_PIN_TYPE = ClassName.get("com.bartlomiejpluta.base.api.map.layer.object", "MapPin")
}
}

View File

@@ -0,0 +1,37 @@
package com.bartlomiejpluta.base.editor.common.view
import javafx.beans.property.SimpleStringProperty
import tornadofx.*
class StringInputFragment : Fragment("Enter value") {
val valueProperty = SimpleStringProperty()
var value by valueProperty
private var onCompleteConsumer: ((String) -> Unit)? = null
fun onComplete(consumer: (String) -> Unit) {
this.onCompleteConsumer = consumer
}
override val root = borderpane {
center = textfield(valueProperty) {
whenDocked { requestFocus() }
}
bottom = buttonbar {
button("Apply") {
action {
onCompleteConsumer?.let { it(value) }
close()
}
}
button("Cancel") {
action {
close()
}
}
}
}
}

View File

@@ -0,0 +1,8 @@
package com.bartlomiejpluta.base.editor.common.viewmodel
import tornadofx.*
class StringVM(value: String = "") : ViewModel() {
val valueProperty = value.toProperty()
val value by valueProperty
}

View File

@@ -0,0 +1,140 @@
package com.bartlomiejpluta.base.editor.map.canvas
import com.bartlomiejpluta.base.editor.common.view.StringInputFragment
import com.bartlomiejpluta.base.editor.common.viewmodel.StringVM
import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode
import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer
import com.bartlomiejpluta.base.editor.map.model.obj.MapLabel
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.project.context.ProjectContext
import com.bartlomiejpluta.base.editor.render.input.MapMouseEvent
import javafx.collections.ObservableList
import javafx.scene.input.MouseButton
import tornadofx.Scope
import tornadofx.find
import tornadofx.setInScope
class LabelPaintingTrace(
private val projectContext: ProjectContext,
private val map: GameMapVM,
override val commandName: String
) : PaintingTrace {
private lateinit var labels: ObservableList<MapLabel>
private lateinit var event: MapMouseEvent
private var newLabel: MapLabel? = null
private var formerLabel: MapLabel? = 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
}
labels = (editorStateVM.selectedLayer as ObjectLayer).labels
formerLabel = labels.firstOrNull { it.x == x && it.y == y }
event = mouseEvent
}
private fun createOrUpdateLabel() {
showCodeDialog(formerLabel?.label ?: "")?.let {
newLabel = MapLabel(x, y, it)
labels.remove(formerLabel)
labels.add(newLabel)
executed = true
}
}
private fun showCodeDialog(initialContent: String): String? {
val scope = Scope()
val vm = StringVM(initialContent)
setInScope(vm, scope)
var content: String? = null
find<StringInputFragment>(scope).apply {
title = "Set label"
onComplete {
content = it
}
openModal(block = true)
}
return content
}
private fun moveLabel(newX: Int, newY: Int) {
if (newY >= map.rows || newX >= map.columns || newY < 0 || newX < 0) {
return
}
formerLabel?.let {
newLabel = MapLabel(newX, newY, it.label)
labels.remove(formerLabel)
labels.add(newLabel)
executed = true
}
}
private fun removeLabel() {
labels.remove(formerLabel)
executed = true
}
override fun proceedTrace(editorStateVM: EditorStateVM, brushVM: BrushVM, mouseEvent: MapMouseEvent) {
}
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)) {
moveLabel(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 -> createOrUpdateLabel()
MouseButton.SECONDARY -> removeLabel()
else -> {
}
}
return
}
// Removing
if (brushVM.mode == BrushMode.ERASING_MODE) {
removeLabel()
}
}
override fun undo() {
labels.remove(newLabel)
formerLabel?.let(labels::add)
}
override fun redo() {
labels.remove(formerLabel)
newLabel?.let(labels::add)
}
override val supportedButtons = arrayOf(MouseButton.PRIMARY, MouseButton.SECONDARY)
}

View File

@@ -6,9 +6,12 @@ import com.bartlomiejpluta.base.editor.map.model.layer.*
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import com.bartlomiejpluta.base.editor.render.model.Renderable
import javafx.geometry.VPos
import javafx.scene.canvas.GraphicsContext
import javafx.scene.image.WritableImage
import javafx.scene.paint.Color
import javafx.scene.text.Font
import javafx.scene.text.TextAlignment
class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, private val painter: MapPainter) :
@@ -157,6 +160,7 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr
}
renderObjects(gc, objectLayer)
renderLabels(gc, objectLayer)
}
private fun renderObjectPassageMap(gc: GraphicsContext, objectLayer: ObjectLayer) {
@@ -196,6 +200,60 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr
}
}
private fun renderLabels(gc: GraphicsContext, objectLayer: ObjectLayer) {
val alpha = gc.globalAlpha
val fill = gc.fill
val width = gc.lineWidth
val align = gc.textAlign
val baseLine = gc.textBaseline
val font = gc.font
// gc.font = LABEL_FONT
for (mapLabel in objectLayer.labels) {
// gc.globalAlpha = OBJECT_FILL_ALPHA
// gc.fill = OBJECT_COLOR
//
// gc.fillRect(
// mapObject.x * tileWidth + OBJECT_MARGIN,
// mapObject.y * tileHeight + OBJECT_MARGIN,
// tileWidth - 2 * OBJECT_MARGIN,
// tileHeight - 2 * OBJECT_MARGIN
// )
gc.globalAlpha = 1.0
gc.stroke = LABEL_COLOR
gc.fill = LABEL_COLOR
gc.lineWidth = LABEL_FONT_WIDTH
gc.textAlign = TextAlignment.CENTER
gc.textBaseline = VPos.CENTER
gc.fillText(
"${mapLabel.label[0]}.${mapLabel.label[mapLabel.label.lastIndex]}",
mapLabel.x * tileWidth + tileWidth / 2,
mapLabel.y * tileHeight + tileHeight / 2
)
gc.lineWidth = LABEL_WIDTH
gc.strokeRect(
mapLabel.x * tileWidth + LABEL_MARGIN + LABEL_WIDTH/2,
mapLabel.y * tileHeight + LABEL_MARGIN + LABEL_WIDTH/2,
tileWidth - 2 * LABEL_MARGIN - LABEL_WIDTH/2,
tileHeight - 2 * LABEL_MARGIN - LABEL_WIDTH/2
)
}
gc.globalAlpha = alpha
gc.fill = fill
gc.lineWidth = width
gc.textAlign = align
gc.textBaseline = baseLine
gc.font = font
}
private fun renderColorLayer(gc: GraphicsContext, colorLayer: ColorLayer) {
val alpha = gc.globalAlpha
val color = gc.fill
@@ -238,7 +296,11 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr
private val BACKGROUND_COLOR2 = Color.color(0.8, 0.8, 0.8, 1.0)
private val OBJECT_COLOR = Color.WHITE
private val LABEL_COLOR = Color.CYAN
private val LABEL_WIDTH = 2.0
private val LABEL_FONT_WIDTH = 4.0
private const val OBJECT_FILL_ALPHA = 0.3
private const val OBJECT_MARGIN = 4
private const val LABEL_MARGIN = 1
}
}

View File

@@ -72,6 +72,7 @@ class MapPainter(
is AutoTileLayer -> AutoTilePaintingTrace(mapVM, "Paint trace")
is ObjectLayer -> when (brushVM.tool) {
BrushTool.DEFAULT -> ObjectPaintingTrace(projectContext, mapVM, "Update object")
BrushTool.LABEL -> LabelPaintingTrace(projectContext, mapVM, "Update label")
else -> PassageAbilityPaintingTrace(mapVM, "Toggle passage")
}
is ImageLayer -> ImagePositionPaintingTrace(mapVM, "Move Image Layer")

View File

@@ -84,10 +84,12 @@ class ObjectPaintingTrace(
get() = """
/*
* Following final parameters are available to use:
* x: int - the x coordinate of tile the object has been created on
* y: int - the y coordinate of tile the object has been created on
* layer: ObjectLayer - current object layer
* map: GameMap - current map
* here: MapPin - the composite object containing current map UID,
* layer's index and x,y coordinates of the current tile
* x: int - the x coordinate of the current tile
* y: int - the y coordinate of the current tile
* layer: ObjectLayer - current object layer's index
* map: GameMap - current map
* handler: ${className(map.handler)} - current map handler
* runner: ${className(projectContext.project?.runner)} - the game runner of the project
* context: Context - the game context

View File

@@ -4,5 +4,6 @@ enum class BrushTool {
DEFAULT,
// Object Layer
PASSAGE
PASSAGE,
LABEL
}

View File

@@ -1,6 +1,7 @@
package com.bartlomiejpluta.base.editor.map.model.layer
import com.bartlomiejpluta.base.editor.map.model.enumeration.PassageAbility
import com.bartlomiejpluta.base.editor.map.model.obj.MapLabel
import com.bartlomiejpluta.base.editor.map.model.obj.MapObject
import javafx.beans.property.SimpleStringProperty
import tornadofx.asObservable
@@ -14,13 +15,16 @@ class ObjectLayer(
columns: Int,
objects: List<MapObject> = mutableListOf(),
javaImports: String = "",
passageMap: Array<Array<PassageAbility>> = Array(rows) { Array(columns) { PassageAbility.ALLOW } }
passageMap: Array<Array<PassageAbility>> = Array(rows) { Array(columns) { PassageAbility.ALLOW } },
labels: List<MapLabel> = mutableListOf()
) : Layer {
var passageMap = passageMap
private set
val objects = objects.asObservable()
val labels = labels.asObservable()
override val nameProperty = SimpleStringProperty(name)
val javaImportsProperty = javaImports.toProperty()

View File

@@ -0,0 +1,16 @@
package com.bartlomiejpluta.base.editor.map.model.obj
import tornadofx.getValue
import tornadofx.setValue
import tornadofx.toProperty
class MapLabel(x: Int, y: Int, label: String) {
val xProperty = x.toProperty()
var x by xProperty
val yProperty = y.toProperty()
var y by yProperty
val labelProperty = label.toProperty()
var label by labelProperty
}

View File

@@ -4,6 +4,7 @@ import com.bartlomiejpluta.base.editor.map.model.enumeration.ImageLayerMode
import com.bartlomiejpluta.base.editor.map.model.enumeration.PassageAbility
import com.bartlomiejpluta.base.editor.map.model.layer.*
import com.bartlomiejpluta.base.editor.map.model.map.GameMap
import com.bartlomiejpluta.base.editor.map.model.obj.MapLabel
import com.bartlomiejpluta.base.editor.map.model.obj.MapObject
import com.bartlomiejpluta.base.editor.project.context.ProjectContext
import com.bartlomiejpluta.base.editor.tileset.model.Tile
@@ -121,7 +122,11 @@ class ProtobufMapDeserializer : MapDeserializer {
MapObject(it.x, it.y, it.code)
}
return ObjectLayer(proto.name, rows, columns, objects, proto.objectLayer.javaImports, passageMap)
val labels = proto.objectLayer.labelsList.map {
MapLabel(it.x, it.y, it.label)
}
return ObjectLayer(proto.name, rows, columns, objects, proto.objectLayer.javaImports, passageMap, labels)
}
private fun deserializeColorLayer(proto: GameMapProto.Layer): Layer {

View File

@@ -60,6 +60,15 @@ class ProtobufMapSerializer : MapSerializer {
})
}
}
.also { proto ->
layer.labels.map {
proto.addLabels(GameMapProto.MapLabel.newBuilder().apply {
x = it.x
y = it.y
label = it.label
})
}
}
.setJavaImports(layer.javaImports)
.build()
.let { GameMapProto.Layer.newBuilder().setName(layer.name).setObjectLayer(it).build() }

View File

@@ -165,5 +165,16 @@ class MapToolbarView : View() {
brushVM.commit()
}
}
togglebutton(value = BrushTool.LABEL, group = objectLayerTool) {
graphic = FontIcon("fa-star")
visibleWhen(isObjectLayerSelected)
action {
brushVM.tool = BrushTool.LABEL
brushVM.commit()
}
}
}
}