From 153c4f4756f1cdea11e3f7abcef0c103c0a01a00 Mon Sep 17 00:00:00 2001 From: Max A Date: Fri, 12 Mar 2021 11:35:49 -0800 Subject: [PATCH] Introduce titleAlignment for box decorator, plus box tests. Closes #331. --- .../zircon/api/ComponentDecorations.kt | 7 +- .../renderer/ComponentDecorationRenderer.kt | 34 +++++++ .../decoration/BoxDecorationRenderer.kt | 26 ++++-- .../decoration/BoxDecorationRendererTest.kt | 90 +++++++++++++++++++ 4 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 zircon.core/src/jvmTest/kotlin/org/hexworks/zircon/internal/component/renderer/decoration/BoxDecorationRendererTest.kt diff --git a/zircon.core/src/commonMain/kotlin/org/hexworks/zircon/api/ComponentDecorations.kt b/zircon.core/src/commonMain/kotlin/org/hexworks/zircon/api/ComponentDecorations.kt index bbbaca43e1..0469fcdffa 100644 --- a/zircon.core/src/commonMain/kotlin/org/hexworks/zircon/api/ComponentDecorations.kt +++ b/zircon.core/src/commonMain/kotlin/org/hexworks/zircon/api/ComponentDecorations.kt @@ -5,6 +5,7 @@ package org.hexworks.zircon.api import org.hexworks.cobalt.databinding.api.extension.createPropertyFrom import org.hexworks.zircon.api.component.Component import org.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer +import org.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer.Alignment import org.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer.RenderingMode import org.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer.RenderingMode.NON_INTERACTIVE import org.hexworks.zircon.api.graphics.BoxType @@ -42,11 +43,13 @@ object ComponentDecorations { fun box( boxType: BoxType = BoxType.SINGLE, title: String = "", - renderingMode: RenderingMode = NON_INTERACTIVE + renderingMode: RenderingMode = NON_INTERACTIVE, + titleAlignment: Alignment = Alignment.TOP_LEFT ): ComponentDecorationRenderer = BoxDecorationRenderer( boxType = boxType, titleProperty = createPropertyFrom(title), - renderingMode = renderingMode + renderingMode = renderingMode, + titleAlignment = titleAlignment ) /** diff --git a/zircon.core/src/commonMain/kotlin/org/hexworks/zircon/api/component/renderer/ComponentDecorationRenderer.kt b/zircon.core/src/commonMain/kotlin/org/hexworks/zircon/api/component/renderer/ComponentDecorationRenderer.kt index c2f53926cf..3d98f00fb2 100644 --- a/zircon.core/src/commonMain/kotlin/org/hexworks/zircon/api/component/renderer/ComponentDecorationRenderer.kt +++ b/zircon.core/src/commonMain/kotlin/org/hexworks/zircon/api/component/renderer/ComponentDecorationRenderer.kt @@ -1,6 +1,7 @@ package org.hexworks.zircon.api.component.renderer import org.hexworks.zircon.api.component.data.ComponentState +import org.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer.Alignment import org.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer.RenderingMode.INTERACTIVE import org.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer.RenderingMode.NON_INTERACTIVE import org.hexworks.zircon.api.data.Position @@ -33,4 +34,37 @@ interface ComponentDecorationRenderer : DecorationRenderer true + else -> false +} +internal fun Alignment.isRight() = when(this) { + Alignment.TOP_RIGHT, Alignment.BOTTOM_RIGHT -> true + else -> false +} +internal fun Alignment.isCenter() = when(this) { + Alignment.TOP_CENTER, Alignment.BOTTOM_CENTER -> true + else -> false +} +internal fun Alignment.isTop() = when (this) { + Alignment.TOP_LEFT, Alignment.TOP_RIGHT, Alignment.TOP_CENTER -> true + else -> false +} +internal fun Alignment.isBottom() = when (this) { + Alignment.BOTTOM_LEFT, Alignment.BOTTOM_RIGHT, Alignment.BOTTOM_CENTER -> true + else -> false } diff --git a/zircon.core/src/commonMain/kotlin/org/hexworks/zircon/internal/component/renderer/decoration/BoxDecorationRenderer.kt b/zircon.core/src/commonMain/kotlin/org/hexworks/zircon/internal/component/renderer/decoration/BoxDecorationRenderer.kt index cdf7eba5f7..b204b4236d 100644 --- a/zircon.core/src/commonMain/kotlin/org/hexworks/zircon/internal/component/renderer/decoration/BoxDecorationRenderer.kt +++ b/zircon.core/src/commonMain/kotlin/org/hexworks/zircon/internal/component/renderer/decoration/BoxDecorationRenderer.kt @@ -5,10 +5,12 @@ import org.hexworks.cobalt.databinding.api.property.Property import org.hexworks.zircon.api.behavior.TitleOverride import org.hexworks.zircon.api.builder.data.TileBuilder import org.hexworks.zircon.api.builder.graphics.BoxBuilder -import org.hexworks.zircon.api.component.renderer.ComponentDecorationRenderContext -import org.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer +import org.hexworks.zircon.api.component.renderer.* +import org.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer.Alignment import org.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer.RenderingMode import org.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer.RenderingMode.NON_INTERACTIVE +import org.hexworks.zircon.api.component.renderer.isRight +import org.hexworks.zircon.api.component.renderer.isTop import org.hexworks.zircon.api.data.Position import org.hexworks.zircon.api.data.Size import org.hexworks.zircon.api.graphics.BoxType @@ -17,7 +19,8 @@ import org.hexworks.zircon.api.graphics.TileGraphics data class BoxDecorationRenderer( val boxType: BoxType = BoxType.SINGLE, private val titleProperty: Property = createPropertyFrom(""), - private val renderingMode: RenderingMode = NON_INTERACTIVE + private val renderingMode: RenderingMode = NON_INTERACTIVE, + private val titleAlignment: Alignment = Alignment.TOP_LEFT ) : ComponentDecorationRenderer { override val offset = Position.offset1x1() @@ -47,13 +50,24 @@ data class BoxDecorationRenderer( } else { finalTitle } + val titleOffsetX = when { + titleAlignment.isLeft() -> 0 + titleAlignment.isRight() -> size.width - cleanText.length - 4 + titleAlignment.isCenter() -> (size.width - cleanText.length - 4) / 2 + else -> throw IllegalStateException("unreachable") + } + val titleOffsetY = when { + titleAlignment.isTop() -> 0 + titleAlignment.isBottom() -> size.height - 1 + else -> throw IllegalStateException("unreachable") + } tileGraphics.draw( TileBuilder.newBuilder() .withStyleSet(style) .withCharacter(boxType.connectorLeft) - .build(), Position.create(1, 0) + .build(), Position.create(1 + titleOffsetX, titleOffsetY) ) - val pos = Position.create(2, 0) + val pos = Position.create(2 + titleOffsetX, titleOffsetY) (cleanText.indices).forEach { idx -> tileGraphics.draw( tile = TileBuilder.newBuilder() @@ -68,7 +82,7 @@ data class BoxDecorationRenderer( .withStyleSet(style) .withCharacter(boxType.connectorRight) .build(), - drawPosition = Position.create(2 + cleanText.length, 0) + drawPosition = Position.create(2 + titleOffsetX + cleanText.length, titleOffsetY) ) } } diff --git a/zircon.core/src/jvmTest/kotlin/org/hexworks/zircon/internal/component/renderer/decoration/BoxDecorationRendererTest.kt b/zircon.core/src/jvmTest/kotlin/org/hexworks/zircon/internal/component/renderer/decoration/BoxDecorationRendererTest.kt new file mode 100644 index 0000000000..4822e16c95 --- /dev/null +++ b/zircon.core/src/jvmTest/kotlin/org/hexworks/zircon/internal/component/renderer/decoration/BoxDecorationRendererTest.kt @@ -0,0 +1,90 @@ +package org.hexworks.zircon.internal.component.renderer.decoration + +import org.assertj.core.api.Assertions.assertThat +import org.hexworks.zircon.api.CP437TilesetResources +import org.hexworks.zircon.api.ComponentDecorations.box +import org.hexworks.zircon.api.Components +import org.hexworks.zircon.api.DrawSurfaces +import org.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer +import org.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer.Alignment.* +import org.hexworks.zircon.api.data.Size +import org.hexworks.zircon.convertCharacterTilesToString +import org.hexworks.zircon.internal.graphics.Renderable +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +class BoxDecorationRendererTest( + @Suppress("unused") private val testTitle: String, // used by Parameterized for display in IntelliJ/terminal + private val decorator: ComponentDecorationRenderer, + private val expected: String +) { + @Test + fun renderTest() { + val target = Components.panel() + .withTileset(CP437TilesetResources.rexPaint20x20()) + .withDecorations(decorator) + .build() + + val graphics = DrawSurfaces.tileGraphicsBuilder().withSize(Size.create(12, 4)).build() + (target as Renderable).render(graphics) + assertThat(graphics.convertCharacterTilesToString()).isEqualTo(expected.trimIndent()) + } + + companion object { + @Parameterized.Parameters(name = "{index}: {0}") + @JvmStatic + fun data() = listOf( + arrayOf("Default parameters", box(), """ + ┌──────────┐ + │ │ + │ │ + └──────────┘ + """), + arrayOf("With title", box(title = "Foo"), """ + ┌┤Foo├─────┐ + │ │ + │ │ + └──────────┘ + """), + arrayOf("With title, right-aligned", box(title = "Foo", titleAlignment = TOP_RIGHT), """ + ┌─────┤Foo├┐ + │ │ + │ │ + └──────────┘ + """), + // Titlebar is even-sized width, but title is odd-sized, so we're off by half, rounding to the left. + arrayOf("With title, center-aligned, approx center", box(title = "Foo", titleAlignment = TOP_CENTER), """ + ┌──┤Foo├───┐ + │ │ + │ │ + └──────────┘ + """), + arrayOf("With title, center-aligned, exact center", box(title = "Food", titleAlignment = TOP_CENTER), """ + ┌──┤Food├──┐ + │ │ + │ │ + └──────────┘ + """), + arrayOf("With title, bottom left-aligned", box(title = "Foo", titleAlignment = BOTTOM_LEFT), """ + ┌──────────┐ + │ │ + │ │ + └┤Foo├─────┘ + """), + arrayOf("With title, bottom right-aligned", box(title = "Foo", titleAlignment = BOTTOM_RIGHT), """ + ┌──────────┐ + │ │ + │ │ + └─────┤Foo├┘ + """), + arrayOf("With title, bottom center-aligned", box(title = "Foo", titleAlignment = BOTTOM_CENTER), """ + ┌──────────┐ + │ │ + │ │ + └──┤Foo├───┘ + """) + ) + } +}