diff --git a/velocity/src/main/kotlin/gg/grounds/proxy/velocity/tab/TabBadge.kt b/velocity/src/main/kotlin/gg/grounds/proxy/velocity/tab/TabBadge.kt index daa4077..7e1cf71 100644 --- a/velocity/src/main/kotlin/gg/grounds/proxy/velocity/tab/TabBadge.kt +++ b/velocity/src/main/kotlin/gg/grounds/proxy/velocity/tab/TabBadge.kt @@ -6,13 +6,19 @@ import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextColor object TabBadge { - fun chip(label: String, fill: TextColor): Component { + fun width(label: String): Int { val textWidth = VanillaAdvances.width(label) val pad = 4 val inner = max(textWidth + pad, TabGlyphs.LEFT_PX + TabGlyphs.RIGHT_PX + TabGlyphs.MIDDLE_PX) val middles = inner - TabGlyphs.LEFT_PX - TabGlyphs.RIGHT_PX - val badgeWidth = TabGlyphs.LEFT_PX + middles * TabGlyphs.MIDDLE_PX + TabGlyphs.RIGHT_PX + return TabGlyphs.LEFT_PX + middles * TabGlyphs.MIDDLE_PX + TabGlyphs.RIGHT_PX + } + + fun chip(label: String, fill: TextColor): Component { + val textWidth = VanillaAdvances.width(label) + val badgeWidth = width(label) + val middles = badgeWidth - TabGlyphs.LEFT_PX - TabGlyphs.RIGHT_PX val padLeft = (badgeWidth - textWidth) / 2 val padRight = badgeWidth - textWidth - padLeft val gap = TabSpaces.of(-1) diff --git a/velocity/src/main/kotlin/gg/grounds/proxy/velocity/tab/TabGlyphs.kt b/velocity/src/main/kotlin/gg/grounds/proxy/velocity/tab/TabGlyphs.kt index 9353c21..1bee696 100644 --- a/velocity/src/main/kotlin/gg/grounds/proxy/velocity/tab/TabGlyphs.kt +++ b/velocity/src/main/kotlin/gg/grounds/proxy/velocity/tab/TabGlyphs.kt @@ -11,4 +11,11 @@ object TabGlyphs { const val LEFT_PX = 3 const val MIDDLE_PX = 1 const val RIGHT_PX = 3 + /** Matches `TabFont` bitmap height and the committed `tab/logo.png`. */ + const val LOGO_HEIGHT = 32 + const val LOGO_TEXTURE_WIDTH = 256 + const val LOGO_TEXTURE_HEIGHT = 52 + const val LOGO_ADVANCE = LOGO_TEXTURE_WIDTH * LOGO_HEIGHT / LOGO_TEXTURE_HEIGHT + const val LOGO_PAD = 16 + const val HEADER_WIDTH = LOGO_PAD + LOGO_ADVANCE + LOGO_PAD } diff --git a/velocity/src/main/kotlin/gg/grounds/proxy/velocity/tab/TabName.kt b/velocity/src/main/kotlin/gg/grounds/proxy/velocity/tab/TabName.kt index f9400b8..a8446b8 100644 --- a/velocity/src/main/kotlin/gg/grounds/proxy/velocity/tab/TabName.kt +++ b/velocity/src/main/kotlin/gg/grounds/proxy/velocity/tab/TabName.kt @@ -7,6 +7,8 @@ import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.TextColor object TabName { + private const val GAP = 2 + fun format(name: String, locale: Locale?, role: PlayerRole?): Component { val colour = role?.colour?.let(TextColor::fromHexString) ?: Palette.TEXT val row = Component.text() @@ -15,16 +17,33 @@ object TabName { ?.takeIf { it.isNotBlank() } ?.let { language -> row.append(TabBadge.chip(language.uppercase(Locale.ROOT), Palette.TEXT_FAINT)) - row.append(Component.text(TabSpaces.of(2)).font(TabGlyphs.FONT)) + row.append(Component.text(TabSpaces.of(GAP)).font(TabGlyphs.FONT)) } role ?.name ?.takeIf { it.isNotBlank() } ?.let { rank -> row.append(TabBadge.chip(rank.uppercase(Locale.ROOT), colour)) - row.append(Component.text(TabSpaces.of(2)).font(TabGlyphs.FONT)) + row.append(Component.text(TabSpaces.of(GAP)).font(TabGlyphs.FONT)) } row.append(Component.text(name, colour)) + val pad = TabGlyphs.HEADER_WIDTH - rowWidth(name, locale, role) + if (pad > 0) { + row.append(Component.text(TabSpaces.of(pad)).font(TabGlyphs.FONT)) + } return row.build() } + + private fun rowWidth(name: String, locale: Locale?, role: PlayerRole?): Int { + var width = VanillaAdvances.width(name) + locale + ?.language + ?.takeIf { it.isNotBlank() } + ?.let { width += TabBadge.width(it.uppercase(Locale.ROOT)) + GAP } + role + ?.name + ?.takeIf { it.isNotBlank() } + ?.let { width += TabBadge.width(it.uppercase(Locale.ROOT)) + GAP } + return width + } } diff --git a/velocity/src/main/resources/gg/grounds/proxy/messages.properties b/velocity/src/main/resources/gg/grounds/proxy/messages.properties index 49278bf..9735175 100644 --- a/velocity/src/main/resources/gg/grounds/proxy/messages.properties +++ b/velocity/src/main/resources/gg/grounds/proxy/messages.properties @@ -1,4 +1,5 @@ -tab.header=\n\uE000\n +# Newlines pad the wordmark vertically; U+E018 is TabSpaces.of(16) on each side. +tab.header=\n\n\n\n\uE018\uE000\uE018\n\n tab.footer=\n Ping \ngrounds.gg tab.server.lobby=Lobby tab.server.game=Game diff --git a/velocity/src/test/kotlin/gg/grounds/proxy/velocity/tab/TabListTest.kt b/velocity/src/test/kotlin/gg/grounds/proxy/velocity/tab/TabListTest.kt index 5e41b28..9911aa8 100644 --- a/velocity/src/test/kotlin/gg/grounds/proxy/velocity/tab/TabListTest.kt +++ b/velocity/src/test/kotlin/gg/grounds/proxy/velocity/tab/TabListTest.kt @@ -49,6 +49,17 @@ class TabListTest { assertFalse(header.contains("Grounds Network"), header) } + @Test + fun `the header pads the wordmark on every side`() { + val header = plain(messages.render(ProxyMessage.TAB_HEADER, Locale.ENGLISH)) + val side = TabSpaces.of(TabGlyphs.LOGO_PAD) + assertTrue(header.contains("$side${TabGlyphs.LOGO}$side"), header) + val lines = header.lines() + assertTrue(lines.size >= 6, header) + assertTrue(lines.first().isEmpty(), header) + assertTrue(lines.last().isEmpty(), header) + } + @Test fun `the header is not prefixed - the wordmark already says whose network this is`() { val header = plain(messages.render(ProxyMessage.TAB_HEADER, Locale.ENGLISH)) diff --git a/velocity/src/test/kotlin/gg/grounds/proxy/velocity/tab/TabNameTest.kt b/velocity/src/test/kotlin/gg/grounds/proxy/velocity/tab/TabNameTest.kt index e9211e2..e5d51ca 100644 --- a/velocity/src/test/kotlin/gg/grounds/proxy/velocity/tab/TabNameTest.kt +++ b/velocity/src/test/kotlin/gg/grounds/proxy/velocity/tab/TabNameTest.kt @@ -3,6 +3,7 @@ package gg.grounds.proxy.velocity.tab import gg.grounds.proxy.api.PlayerRole import java.util.Locale import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @@ -30,4 +31,22 @@ class TabNameTest { assertFalse(text.contains("DE"), text) assertTrue(text.contains("Steve"), text) } + + @Test + fun `a short name is padded to the wordmark width`() { + val name = "A" + val text = + PlainTextComponentSerializer.plainText().serialize(TabName.format(name, null, null)) + val pad = TabGlyphs.HEADER_WIDTH - VanillaAdvances.width(name) + assertTrue(pad > 0) + assertTrue(text.contains(TabSpaces.of(pad)), text) + } + + @Test + fun `a name that is already wider than the wordmark is not padded`() { + val name = "A".repeat(40) + val text = + PlainTextComponentSerializer.plainText().serialize(TabName.format(name, null, null)) + assertEquals(name, text) + } }