diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/map/layer/object/MapPin.java b/api/src/main/java/com/bartlomiejpluta/base/api/map/layer/object/MapPin.java new file mode 100644 index 00000000..f1359b45 --- /dev/null +++ b/api/src/main/java/com/bartlomiejpluta/base/api/map/layer/object/MapPin.java @@ -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; +} diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/generator/AssetMapCodeGenerator.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/generator/AssetMapCodeGenerator.kt index 8f52dfca..3ca1093d 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/generator/AssetMapCodeGenerator.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/generator/AssetMapCodeGenerator.kt @@ -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): 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): 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 + } } \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/generator/DataAccessObjectCodeGenerator.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/generator/DataAccessObjectCodeGenerator.kt index b795fa7d..0132fc3d 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/generator/DataAccessObjectCodeGenerator.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/generator/DataAccessObjectCodeGenerator.kt @@ -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() diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/generator/MapObjectsCodeGenerator.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/generator/MapObjectsCodeGenerator.kt index bde8a754..ac2ae0ec 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/generator/MapObjectsCodeGenerator.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/generator/MapObjectsCodeGenerator.kt @@ -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") } } \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/common/view/StringInputFragment.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/common/view/StringInputFragment.kt new file mode 100644 index 00000000..e019f2cf --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/common/view/StringInputFragment.kt @@ -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() + } + } + } + } +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/common/viewmodel/StringVM.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/common/viewmodel/StringVM.kt new file mode 100644 index 00000000..640f2a08 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/common/viewmodel/StringVM.kt @@ -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 +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/LabelPaintingTrace.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/LabelPaintingTrace.kt new file mode 100644 index 00000000..0dfbea8f --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/LabelPaintingTrace.kt @@ -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 + 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(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) +} \ 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 df7f87d5..44e40dd8 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 @@ -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 } } \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapPainter.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapPainter.kt index cb7ffe6a..37b43746 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapPainter.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapPainter.kt @@ -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") diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/ObjectPaintingTrace.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/ObjectPaintingTrace.kt index 41bd07db..b9a67032 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/ObjectPaintingTrace.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/ObjectPaintingTrace.kt @@ -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 diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/brush/BrushTool.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/brush/BrushTool.kt index f5540c78..41412a05 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/brush/BrushTool.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/brush/BrushTool.kt @@ -4,5 +4,6 @@ enum class BrushTool { DEFAULT, // Object Layer - PASSAGE + PASSAGE, + LABEL } \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/layer/ObjectLayer.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/layer/ObjectLayer.kt index 3250d1b1..01af4611 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/layer/ObjectLayer.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/layer/ObjectLayer.kt @@ -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 = mutableListOf(), javaImports: String = "", - passageMap: Array> = Array(rows) { Array(columns) { PassageAbility.ALLOW } } + passageMap: Array> = Array(rows) { Array(columns) { PassageAbility.ALLOW } }, + labels: List = mutableListOf() ) : Layer { var passageMap = passageMap private set val objects = objects.asObservable() + val labels = labels.asObservable() + override val nameProperty = SimpleStringProperty(name) val javaImportsProperty = javaImports.toProperty() diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/obj/MapLabel.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/obj/MapLabel.kt new file mode 100644 index 00000000..afd01f97 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/obj/MapLabel.kt @@ -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 +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/serial/ProtobufMapDeserializer.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/serial/ProtobufMapDeserializer.kt index 89abcd22..16ef5df4 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/serial/ProtobufMapDeserializer.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/serial/ProtobufMapDeserializer.kt @@ -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 { diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/serial/ProtobufMapSerializer.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/serial/ProtobufMapSerializer.kt index 50a92c6e..9e671764 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/serial/ProtobufMapSerializer.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/serial/ProtobufMapSerializer.kt @@ -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() } 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 9b079f78..a22ff27d 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 @@ -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() + } + } } } \ No newline at end of file