[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.asset.model.Asset
import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset 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.map.serial.MapDeserializer
import com.bartlomiejpluta.base.editor.project.context.ProjectContext import com.bartlomiejpluta.base.editor.project.context.ProjectContext
import com.bartlomiejpluta.base.editor.project.model.Project import com.bartlomiejpluta.base.editor.project.model.Project
import com.squareup.javapoet.* import com.squareup.javapoet.*
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.time.Instant
import java.time.format.DateTimeFormatter
import java.util.* import java.util.*
import javax.annotation.processing.Generated
import javax.lang.model.element.Modifier import javax.lang.model.element.Modifier
@Component @Component
@@ -46,10 +50,17 @@ class AssetMapCodeGenerator : CodeGenerator {
} }
private fun generateAssetClass(name: String, assets: List<Asset>): TypeSpec { 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) val className = ClassName.get("A", name)
return TypeSpec return TypeSpec
.classBuilder(className) .classBuilder(className)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addAnnotation(generatedAnnotation)
.addField( .addField(
FieldSpec.builder( FieldSpec.builder(
ParameterizedTypeName.get( ParameterizedTypeName.get(
@@ -69,13 +80,13 @@ class AssetMapCodeGenerator : CodeGenerator {
} }
.build() .build()
) )
.addField(String::class.java, "uid", Modifier.PUBLIC, Modifier.FINAL) .addField(String::class.java, "$", Modifier.PUBLIC, Modifier.FINAL)
.addMethod( .addMethod(
MethodSpec MethodSpec
.constructorBuilder() .constructorBuilder()
.addModifiers(Modifier.PRIVATE) .addModifiers(Modifier.PRIVATE)
.addParameter(TypeName.get(String::class.java), "uid") .addParameter(TypeName.get(String::class.java), "uid")
.addStatement("this.uid = uid") .addStatement("this.\$\$ = uid")
.build() .build()
) )
.addMethod( .addMethod(
@@ -101,15 +112,22 @@ class AssetMapCodeGenerator : CodeGenerator {
} }
private fun generateMapAssetClass(name: String, assets: List<GameMapAsset>): TypeSpec { 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 className = ClassName.get("A", name)
val mapLayers = assets val mapLayers = assets
.map { asset -> asset to mapDeserializer.deserialize(asset.file.inputStream()) } .map { asset -> asset to mapDeserializer.deserialize(asset.file.inputStream()) }
.associate { (asset, map) -> asset to map.layers } .associate { (asset, map) -> asset to map.layers }
val abstractAssetClassName = ClassName.get("", "GameMapAsset") val abstractAssetClassName = ClassName.get("", "GameMapAsset")
val abstractLayerClassName = ClassName.get("", "GameMapAssetLayer")
return TypeSpec return TypeSpec
.classBuilder(className) .classBuilder(className)
.addAnnotation(generatedAnnotation)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addField( .addField(
FieldSpec.builder( FieldSpec.builder(
@@ -122,67 +140,33 @@ class AssetMapCodeGenerator : CodeGenerator {
.initializer("new \$T<>()", java.util.HashMap::class.java) .initializer("new \$T<>()", java.util.HashMap::class.java)
.build() .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() .addStaticBlock(CodeBlock.builder()
.apply { .apply {
assets.forEach { assets.forEach {
addStatement("_maps.put(\"${it.name}\", ${getAssetName(it)})") 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() .build()
) )
.addMethod( .addMethod(
MethodSpec MethodSpec
.methodBuilder("get") .methodBuilder("byName")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC) .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
.returns(abstractAssetClassName) .returns(abstractAssetClassName)
.addParameter(TypeName.get(String::class.java), "name") .addParameter(TypeName.get(String::class.java), "name")
.addStatement("return _maps.get(name)") .addStatement("return _maps.get(name)")
.build() .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( .addType(
TypeSpec.classBuilder(abstractAssetClassName) TypeSpec.classBuilder(abstractAssetClassName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.ABSTRACT) .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( .addField(
FieldSpec.builder( FieldSpec.builder(
ParameterizedTypeName.get( ParameterizedTypeName.get(
ClassName.get(java.util.Map::class.java), ClassName.get(java.util.Map::class.java),
ClassName.get(String::class.java), ClassName.get(String::class.java),
ClassName.get(Integer::class.java) abstractLayerClassName
), "_layers", Modifier.PROTECTED, Modifier.FINAL ), "_layers", Modifier.PROTECTED, Modifier.FINAL
) )
.initializer("new \$T<>()", java.util.HashMap::class.java) .initializer("new \$T<>()", java.util.HashMap::class.java)
@@ -191,23 +175,52 @@ class AssetMapCodeGenerator : CodeGenerator {
.addMethod( .addMethod(
MethodSpec.constructorBuilder() MethodSpec.constructorBuilder()
.addParameter(ClassName.get(String::class.java), "uid") .addParameter(ClassName.get(String::class.java), "uid")
.addStatement("this.uid = uid") .addStatement("this.\$\$ = uid")
.build() .build()
) )
.addMethod( .addMethod(
MethodSpec.methodBuilder("get") MethodSpec.methodBuilder("layer")
.addModifiers(Modifier.PUBLIC) .addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.get(String::class.java), "name") .addParameter(ClassName.get(String::class.java), "name")
.returns(ClassName.get(Integer::class.java)) .returns(abstractLayerClassName)
.addStatement("return this._layers.get(name)") .addStatement("return this._layers.get(name)")
.build() .build()
) )
.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 { .apply {
mapLayers.forEach { (asset, layers) -> mapLayers.forEach { (asset, layers) ->
val assetClassName = ClassName.get("", "GameMapAsset_${getCapitalizedAssetName(asset)}") val assetClassName = ClassName.get("", getCapitalizedAssetName(asset))
val layersClassName = ClassName.get("", "GameMapAsset_Layers_${getCapitalizedAssetName(asset)}")
addField( addField(
FieldSpec FieldSpec
@@ -220,36 +233,62 @@ class AssetMapCodeGenerator : CodeGenerator {
TypeSpec.classBuilder(assetClassName) TypeSpec.classBuilder(assetClassName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.superclass(abstractAssetClassName) .superclass(abstractAssetClassName)
.addField(
FieldSpec.builder(layersClassName, "layers", Modifier.PUBLIC, Modifier.FINAL)
.initializer("new \$T()", layersClassName)
.build()
)
.addMethod( .addMethod(
MethodSpec.constructorBuilder() MethodSpec.constructorBuilder()
.addStatement("super(\"${asset.uid}\")") .addStatement("super(\"${asset.uid}\")")
.apply { .apply {
layers.forEach { layer -> 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()
) )
.build()
)
addType(
TypeSpec.classBuilder(layersClassName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.apply { .apply {
layers.forEachIndexed { index, layer -> 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( addField(
FieldSpec.builder( FieldSpec.builder(
TypeName.INT, layerClass,
getAssetName(layer.name), getAssetName(layer.name),
Modifier.PUBLIC, Modifier.PUBLIC,
Modifier.FINAL Modifier.FINAL
) )
.initializer(index.toString()) .initializer("new \$T()", layerClass)
.build() .build()
) )
} }
@@ -272,4 +311,9 @@ class AssetMapCodeGenerator : CodeGenerator {
private fun getCapitalizedAssetName(name: String) = name private fun getCapitalizedAssetName(name: String) = name
.split("\\s+".toRegex()) .split("\\s+".toRegex())
.joinToString("") { part -> part.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } } .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) .addStatement("var list = new \$T<\$T>()", LinkedList::class.java, model)
.beginControlFlow("\$T.INSTANCE.getContext().withDatabase(db ->", CONTEXT_HOLDER) .beginControlFlow("\$T.INSTANCE.getContext().withDatabase(db ->", CONTEXT_HOLDER)
.addStatement( .addStatement(
"var filter = filters.stream().map(f -> String.format(\"`%s` %s ?\", f.column.column, f.op.getOp())).collect(\$T.joining(\" AND \"))", "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
COLLECTORS_CLASS
) )
.addStatement("var order = ordering.isEmpty() ? \"\" : \" ORDER BY \" + ordering.stream().collect(\$T.joining(\", \"))", COLLECTORS_CLASS) .addStatement("var order = ordering.isEmpty() ? \"\" : \" ORDER BY \" + ordering.stream().collect(\$T.joining(\", \"))", COLLECTORS_CLASS)
.apply { .apply {
val sql = "SELECT * FROM `${table.name}` WHERE " val sql = "SELECT * FROM `${table.name}` "
addStatement("var statement = db.prepareStatement(\"$sql\" + filter + order)") addStatement("var statement = db.prepareStatement(\"$sql\" + filter + order)")
} }
.addStatement("var i = 1") .addStatement("var i = 1")
@@ -407,6 +406,39 @@ class DataAccessObjectCodeGenerator : CodeGenerator {
.addStatement("return list") .addStatement("return list")
.build() .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()
) )
.build() .build()

View File

@@ -37,9 +37,11 @@ class MapObjectsCodeGenerator : CodeGenerator {
private fun generateMapObjects(asset: GameMapAsset, map: GameMap, project: Project) { private fun generateMapObjects(asset: GameMapAsset, map: GameMap, project: Project) {
val runner = className(project.runner) val runner = className(project.runner)
map.layers map.layers.forEachIndexed { index, layer ->
.mapNotNull { it as? ObjectLayer } if (layer is ObjectLayer) {
.forEach { generateLayerClass(project.buildGeneratedCodeDirectory, asset, map, it, runner) } generateLayerClass(project.buildGeneratedCodeDirectory, asset, map, layer, index, runner)
}
}
} }
private fun generateLayerClass( private fun generateLayerClass(
@@ -47,6 +49,7 @@ class MapObjectsCodeGenerator : CodeGenerator {
asset: GameMapAsset, asset: GameMapAsset,
map: GameMap, map: GameMap,
layer: ObjectLayer, layer: ObjectLayer,
layerIndex: Int,
runner: ClassName runner: ClassName
) { ) {
val packageName = "com.bartlomiejpluta.base.generated.map" val packageName = "com.bartlomiejpluta.base.generated.map"
@@ -77,6 +80,7 @@ class MapObjectsCodeGenerator : CodeGenerator {
.addParameter(EngineObjectLayer::class.java, "layer", Modifier.FINAL) .addParameter(EngineObjectLayer::class.java, "layer", Modifier.FINAL)
.addParameter(TypeName.INT, "x", Modifier.FINAL) .addParameter(TypeName.INT, "x", Modifier.FINAL)
.addParameter(TypeName.INT, "y", Modifier.FINAL) .addParameter(TypeName.INT, "y", Modifier.FINAL)
.addParameter(MAP_PIN_TYPE, "here", Modifier.FINAL)
.addCode(it.code) .addCode(it.code)
.build() .build()
.let(generatedClass::addMethod) .let(generatedClass::addMethod)
@@ -93,7 +97,7 @@ class MapObjectsCodeGenerator : CodeGenerator {
.addStatement("var customHandler = (\$T) handler", handler) .addStatement("var customHandler = (\$T) handler", handler)
layer.objects.forEach { 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 generatedClass
@@ -132,5 +136,6 @@ class MapObjectsCodeGenerator : CodeGenerator {
companion object { companion object {
private val GENERATOR_NAME = MapObjectsCodeGenerator::class.java.canonicalName 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.EditorStateVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import com.bartlomiejpluta.base.editor.render.model.Renderable import com.bartlomiejpluta.base.editor.render.model.Renderable
import javafx.geometry.VPos
import javafx.scene.canvas.GraphicsContext import javafx.scene.canvas.GraphicsContext
import javafx.scene.image.WritableImage import javafx.scene.image.WritableImage
import javafx.scene.paint.Color 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) : 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) renderObjects(gc, objectLayer)
renderLabels(gc, objectLayer)
} }
private fun renderObjectPassageMap(gc: GraphicsContext, objectLayer: 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) { private fun renderColorLayer(gc: GraphicsContext, colorLayer: ColorLayer) {
val alpha = gc.globalAlpha val alpha = gc.globalAlpha
val color = gc.fill 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 BACKGROUND_COLOR2 = Color.color(0.8, 0.8, 0.8, 1.0)
private val OBJECT_COLOR = Color.WHITE 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_FILL_ALPHA = 0.3
private const val OBJECT_MARGIN = 4 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 AutoTileLayer -> AutoTilePaintingTrace(mapVM, "Paint trace")
is ObjectLayer -> when (brushVM.tool) { is ObjectLayer -> when (brushVM.tool) {
BrushTool.DEFAULT -> ObjectPaintingTrace(projectContext, mapVM, "Update object") BrushTool.DEFAULT -> ObjectPaintingTrace(projectContext, mapVM, "Update object")
BrushTool.LABEL -> LabelPaintingTrace(projectContext, mapVM, "Update label")
else -> PassageAbilityPaintingTrace(mapVM, "Toggle passage") else -> PassageAbilityPaintingTrace(mapVM, "Toggle passage")
} }
is ImageLayer -> ImagePositionPaintingTrace(mapVM, "Move Image Layer") is ImageLayer -> ImagePositionPaintingTrace(mapVM, "Move Image Layer")

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
package com.bartlomiejpluta.base.editor.map.model.layer package com.bartlomiejpluta.base.editor.map.model.layer
import com.bartlomiejpluta.base.editor.map.model.enumeration.PassageAbility 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 com.bartlomiejpluta.base.editor.map.model.obj.MapObject
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import tornadofx.asObservable import tornadofx.asObservable
@@ -14,13 +15,16 @@ class ObjectLayer(
columns: Int, columns: Int,
objects: List<MapObject> = mutableListOf(), objects: List<MapObject> = mutableListOf(),
javaImports: String = "", 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 { ) : Layer {
var passageMap = passageMap var passageMap = passageMap
private set private set
val objects = objects.asObservable() val objects = objects.asObservable()
val labels = labels.asObservable()
override val nameProperty = SimpleStringProperty(name) override val nameProperty = SimpleStringProperty(name)
val javaImportsProperty = javaImports.toProperty() 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.enumeration.PassageAbility
import com.bartlomiejpluta.base.editor.map.model.layer.* import com.bartlomiejpluta.base.editor.map.model.layer.*
import com.bartlomiejpluta.base.editor.map.model.map.GameMap 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.map.model.obj.MapObject
import com.bartlomiejpluta.base.editor.project.context.ProjectContext import com.bartlomiejpluta.base.editor.project.context.ProjectContext
import com.bartlomiejpluta.base.editor.tileset.model.Tile import com.bartlomiejpluta.base.editor.tileset.model.Tile
@@ -121,7 +122,11 @@ class ProtobufMapDeserializer : MapDeserializer {
MapObject(it.x, it.y, it.code) 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 { 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) .setJavaImports(layer.javaImports)
.build() .build()
.let { GameMapProto.Layer.newBuilder().setName(layer.name).setObjectLayer(it).build() } .let { GameMapProto.Layer.newBuilder().setName(layer.name).setObjectLayer(it).build() }

View File

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