diff --git a/.gitignore b/.gitignore index 75d852ee6..da638ba6e 100644 --- a/.gitignore +++ b/.gitignore @@ -126,6 +126,7 @@ modules.xml logs/ .claude + # ProjectAtlas runtime state /.projectatlas/* !/.projectatlas/config.toml diff --git a/gradle.properties b/gradle.properties index 5eb19632c..21d820269 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled javaVersion=25 mcVersion=26.2 group=dev.slne.surf.api -version=3.42.0 +version=3.43.0 relocationPrefix=dev.slne.surf.api.libs snapshot=false diff --git a/surf-api-core/surf-api-core/api/surf-api-core.api b/surf-api-core/surf-api-core/api/surf-api-core.api index bf35222c9..cbd84ba23 100644 --- a/surf-api-core/surf-api-core/api/surf-api-core.api +++ b/surf-api-core/surf-api-core/api/surf-api-core.api @@ -7233,6 +7233,33 @@ public final class dev/slne/surf/api/core/generated/VanillaAdvancementKeys$Story public static final field UPGRADE_TOOLS Lnet/kyori/adventure/key/Key; } +public final class dev/slne/surf/api/core/inventory/framework/internal/DefaultFontWidths { + public static final field BOLD_EXTRA_ADVANCE I + public static final field DEFAULT_ADVANCE I + public static final field INSTANCE Ldev/slne/surf/api/core/inventory/framework/internal/DefaultFontWidths; + public static final field SPACE_ADVANCE I + public final fun charWidth (IZLit/unimi/dsi/fastutil/ints/Int2IntMap;)I + public static synthetic fun charWidth$default (Ldev/slne/surf/api/core/inventory/framework/internal/DefaultFontWidths;IZLit/unimi/dsi/fastutil/ints/Int2IntMap;ILjava/lang/Object;)I + public final fun componentWidth (Lnet/kyori/adventure/text/Component;ZLit/unimi/dsi/fastutil/ints/Int2IntMap;)I + public static synthetic fun componentWidth$default (Ldev/slne/surf/api/core/inventory/framework/internal/DefaultFontWidths;Lnet/kyori/adventure/text/Component;ZLit/unimi/dsi/fastutil/ints/Int2IntMap;ILjava/lang/Object;)I + public final fun getAdvances ()Lit/unimi/dsi/fastutil/ints/Int2IntMap; + public final fun textWidth (Ljava/lang/String;ZLit/unimi/dsi/fastutil/ints/Int2IntMap;)I + public static synthetic fun textWidth$default (Ldev/slne/surf/api/core/inventory/framework/internal/DefaultFontWidths;Ljava/lang/String;ZLit/unimi/dsi/fastutil/ints/Int2IntMap;ILjava/lang/Object;)I +} + +public final class dev/slne/surf/api/core/inventory/framework/internal/LaidOutViewText { + public fun (Lnet/kyori/adventure/text/Component;I)V + public final fun component1 ()Lnet/kyori/adventure/text/Component; + public final fun component2 ()I + public final fun copy (Lnet/kyori/adventure/text/Component;I)Ldev/slne/surf/api/core/inventory/framework/internal/LaidOutViewText; + public static synthetic fun copy$default (Ldev/slne/surf/api/core/inventory/framework/internal/LaidOutViewText;Lnet/kyori/adventure/text/Component;IILjava/lang/Object;)Ldev/slne/surf/api/core/inventory/framework/internal/LaidOutViewText; + public fun equals (Ljava/lang/Object;)Z + public final fun getComponent ()Lnet/kyori/adventure/text/Component; + public final fun getWidth ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public abstract class dev/slne/surf/api/core/inventory/framework/internal/PaginationButtonGlyphs : java/lang/Enum { public static final field DISABLED Ldev/slne/surf/api/core/inventory/framework/internal/PaginationButtonGlyphs; public static final field DISABLED_LEFT Ldev/slne/surf/api/core/inventory/framework/internal/PaginationButtonGlyphs; @@ -7244,19 +7271,6 @@ public abstract class dev/slne/surf/api/core/inventory/framework/internal/Pagina public static fun values ()[Ldev/slne/surf/api/core/inventory/framework/internal/PaginationButtonGlyphs; } -public final class dev/slne/surf/api/core/inventory/framework/internal/RenderedViewTitle { - public fun (Ljava/lang/String;I)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()I - public final fun copy (Ljava/lang/String;I)Ldev/slne/surf/api/core/inventory/framework/internal/RenderedViewTitle; - public static synthetic fun copy$default (Ldev/slne/surf/api/core/inventory/framework/internal/RenderedViewTitle;Ljava/lang/String;IILjava/lang/Object;)Ldev/slne/surf/api/core/inventory/framework/internal/RenderedViewTitle; - public fun equals (Ljava/lang/Object;)Z - public final fun getText ()Ljava/lang/String; - public final fun getWidth ()I - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - public final class dev/slne/surf/api/core/inventory/framework/internal/ShiftGlyphs { public static final field INSTANCE Ldev/slne/surf/api/core/inventory/framework/internal/ShiftGlyphs; public final fun renderShift (I)Ljava/lang/String; @@ -7286,8 +7300,28 @@ public final class dev/slne/surf/api/core/inventory/framework/internal/ViewButto public final class dev/slne/surf/api/core/inventory/framework/internal/ViewContainerRenderingKt { public static final fun appendShiftedComponent (Ldev/slne/surf/api/core/messages/builder/SurfComponentBuilder;IILkotlin/jvm/functions/Function1;)V - public static final fun renderViewTitle (Ljava/lang/String;IIZLit/unimi/dsi/fastutil/ints/Int2IntMap;)Ldev/slne/surf/api/core/inventory/framework/internal/RenderedViewTitle; - public static synthetic fun renderViewTitle$default (Ljava/lang/String;IIZLit/unimi/dsi/fastutil/ints/Int2IntMap;ILjava/lang/Object;)Ldev/slne/surf/api/core/inventory/framework/internal/RenderedViewTitle; +} + +public final class dev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry { + public static final field COLUMNS I + public static final field INSTANCE Ldev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry; + public static final field MAX_ROWS I + public static final field SLOT_SIZE I + public final fun columnOf (II)I + public static synthetic fun columnOf$default (Ldev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry;IIILjava/lang/Object;)I + public final fun columnShift (III)I + public static synthetic fun columnShift$default (Ldev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry;IIIILjava/lang/Object;)I + public final fun rowOf (II)I + public static synthetic fun rowOf$default (Ldev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry;IIILjava/lang/Object;)I + public final fun slotOf (III)I + public static synthetic fun slotOf$default (Ldev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry;IIIILjava/lang/Object;)I + public final fun spanWidth (II)I + public static synthetic fun spanWidth$default (Ldev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry;IIILjava/lang/Object;)I +} + +public final class dev/slne/surf/api/core/inventory/framework/internal/ViewTextLayoutKt { + public static final fun layoutViewText (Lnet/kyori/adventure/text/Component;IZLjava/lang/Integer;ZLkotlin/jvm/functions/Function2;)Ldev/slne/surf/api/core/inventory/framework/internal/LaidOutViewText; + public static synthetic fun layoutViewText$default (Lnet/kyori/adventure/text/Component;IZLjava/lang/Integer;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ldev/slne/surf/api/core/inventory/framework/internal/LaidOutViewText; } public final class dev/slne/surf/api/core/invoker/HiddenInvokerUtil { diff --git a/surf-api-core/surf-api-core/build.gradle.kts b/surf-api-core/surf-api-core/build.gradle.kts index 29b6817af..c2020eab9 100644 --- a/surf-api-core/surf-api-core/build.gradle.kts +++ b/surf-api-core/surf-api-core/build.gradle.kts @@ -6,6 +6,9 @@ plugins { dependencies { api(projects.surfApiShared.surfApiSharedPublic) api(libs.adventure.nbt) + // Exposed transitively by surf-api-shared-public as well; declared here so the test convention + // (testImplementation extendsFrom compileOnlyApi) puts Adventure on the test classpath. + compileOnlyApi(libs.adventure.api) compileOnlyApi(libs.luckperms) compileOnlyApi(libs.packetevents.api) compileOnlyApi(libs.dazzleconf) diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/DefaultFontWidths.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/DefaultFontWidths.kt new file mode 100644 index 000000000..3272df09a --- /dev/null +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/DefaultFontWidths.kt @@ -0,0 +1,148 @@ +package dev.slne.surf.api.core.inventory.framework.internal + +import dev.slne.surf.api.core.util.freeze +import dev.slne.surf.api.shared.api.util.InternalSurfApi +import it.unimi.dsi.fastutil.ints.Int2IntMap +import it.unimi.dsi.fastutil.ints.Int2IntMaps +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextDecoration + +/** + * Advance widths of the vanilla `minecraft:default` font, used to lay out header text that is + * rendered in the client's own font instead of a fixed-width resource-pack font. + * + * An *advance* is the number of pixels the render cursor moves on for one glyph — the glyph's own + * width plus the one pixel of spacing the font renderer inserts after it. Every code point not + * listed in [advances] advances by [DEFAULT_ADVANCE]; bold text adds [BOLD_EXTRA_ADVANCE] on top of + * every glyph. + * + * The table is measured off the three bitmap sheets of the vanilla font (`ascii.png`, + * `accented.png`, `nonlatin_european.png`), so it covers Latin, Latin Extended, Greek and Cyrillic + * exactly. Glyphs outside those sheets — CJK from the Unifont fallback, custom resource-pack glyphs + * — are measured as [DEFAULT_ADVANCE], so pass an override map for them via the `charWidths` + * parameter of the platform header components if they must be measured exactly. + * + * This is internal infrastructure — use the platform `TextAlignment` / header DSL instead. + */ +@InternalSurfApi +object DefaultFontWidths { + + /** Advance of a glyph the vanilla font renders at its regular width (5px glyph + 1px spacing). */ + const val DEFAULT_ADVANCE = 6 + + /** Extra advance the font renderer adds to every glyph of bold text. */ + const val BOLD_EXTRA_ADVANCE = 1 + + /** Advance of a space, which the vanilla font takes from its space provider. */ + const val SPACE_ADVANCE = 4 + + /** + * Every glyph of the vanilla font sheets whose advance is not [DEFAULT_ADVANCE], grouped by + * that advance. Generated by measuring the sheets, see [advances]. + */ + private val ADVANCES_BY_WIDTH = mapOf( + 2 to "!',.:;i|¡¦·ıǀǃʻˌ˙;·і׃ויןᛁᛌᛧ᛬ᴉị⁏ⁱ⏽⸵⹁יִ𐌹", + 3 to "`l´ìíįĺļɩɭ̧ίιՙ՚՛՝լ׳᛫ᛵᶅᶖḷἱί‘’‚•′‵⁽⁾₍₎ꞁוֹ", + 4 to "\"()*I[]t{}¨­¹ÌÍÎÏîïĪīĮİľŀţŧſƖƗƚƫƭǁǏǐțȴɨɪʇʈΙΪϊІЇїӀ՜՞׆זנװױײ჻ჼᚨᚭᚮᚲᚽᚿᛂᛆᛊᛍᛐᛓᛙᛚḮḯḹḻḽṫṭṯṱẗẛỈỉỊἰὶῑῒΐῙ‐‑‹›‼⁎⁑⁒⁺⁻⁼₁₊₋₌∥⋆⏴⏵⏸◘◦♀♩♭♮ײַוּ", + 5 to "<>fkª°²³ºĨĩĬĭijķĸłťƙǩȊȋȶɟɬʞκгкѓќҕӄӷԼՒէւג״ჯᚠᚡᚦᚧᚩᚪᚫᚱᚳᚴᚵᚹᛈᛑᛒᛔᛕᛛᛩᛴᶂḟḬḭḱḳḵẜẝỽἶἷῐῖῗῘ“”„″‶⁊⁰⁴⁵⁶⁷⁸⁹ⁿ₀₂₃₄₅₆₇₈₉∁⌡▌●ꝛכּ", + 7 to "@~«¶»ÐĐđħŁƀƄƅƇƈƉƑƓƝƬƳǤǥȽɃɇɈɉɊɋɌɍɖɗɠɲɳɻʄʆʐʛʠʮσϔЊЋДЦЪдщъыђњћѢѣѴѵѶѷҊҋҌҍҒҗҚҞҠҢңҤҥҨҩҲҳҵҶҷӅӆӉӊӍӎӹӺԆԇԎԏԤԥԦԧԨԩԬԭԮԯԳԴԹԺԽՂՄՌգդզժխղմնվքևֈႫႯჱᛃᛟᶁᶃᶆᶇᶍᶎᶏᶐᶑᶒᶓᶕᶗᶙỻỿἅἸἾἿῚΊ–‴‷⁋₣₫€₭₺₽₾⅟⇵√∝≈≡≢⏩⏪⏯▶▷◀◁⛄✔✘❌ⱥⴁⴂⴆⴎⴏⴝꚀꚁꚈꚉꚊꚋꚎꚒꚓꚗꜩꞕꞨꟆfl🔥🪓", + 8 to "¤©®¼½¾ďĦʼnƁƊƠơƢƣƤƯưƴljȡȵɄɎɏɚɝɷʉʧʪʫʯΆΈΉΌΎΏωώЂЉЖФШЫЮюљҴҼҽҾҿӁӜӸԄԅտփႡႢႥႦႧႪႭႮႰႳႻჀჅლᛠᛢᛪᛳᵬᵭᵯᵰᵱᵲᵳᵴỚớỜờỞởỠỡỢợỨứỪừỬửỮữỰựỺἻἽὠὡὢὣὤὥὦὧὼώᾠᾡᾢᾣᾤᾥᾦᾧῲῳῴῶῷ…‰※⁂⁈⁉₦₩₪₱₳₴₷₻℗⅙⅚←→⇄∀∅∛∜∞≔⌀⌂⌘⌚⌛⌠⏭⏮⏳⏻⏼║╖╗╜╝╢╣░◎☂☃☄★☆☈☔☠☻♫♬⚀⚁⚂⚃⚄⚅⚐⚑⚓⚔⚗⛨✂✉✎❄❤⧈⭐⭘⯪⯫ⱢⱱꜨff�🔔🛡🧪🪣", + 9 to "ƕǶʦʨЩҖҦҧԂԃԈԉԊԋԔԕԖԗԠԡԢԣᵺἈἉἎἏἘἙἨἩἮἯἺἼὈὉὙὟὨὩὮᾈᾉᾎᾏᾘᾙᾞᾟᾨᾩᾮᾺΆῈΈῊΉῪΎῬῸΌῺΏ—‱⁗™⅐⅑⅓⅔⅕⅖⅗⅛⅜⅝⅞↉↔⇏⇒⇔─┌└├┬┴┼═╒╓╔╘╙╚╞╟╠╤╥╦╧╨╩╪╫╬▀▄█▐▒▓☀☁☽⛈⛏ꚅꚖꜹꜻꜽffi🌊🌧🍖🎣🏹🔱🗡", + 10 to "ÆæŒœDŽDždžLJLjNJNjnjǢǣDZDzdzǼǽȸȹɶʣʤʥʩϓӔӕԘԙԪԫᴂᴔᵫἊἋἌἍἚἛἜἝἪἫἬἭἹὊὋὌὍὛὝὪὫὬὭὯᾊᾋᾌᾍᾚᾛᾜᾝᾪᾫᾬᾭᾯ⁇₧₨₯№ℹ⅒⅘①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪☐☑☒☜☞☮☯⚠ꚄꜲꜳꜴꜵꜶꜷꜸꜺꜼꝎꝏꭣfflstﬓﬔﬕﬖﬗ", + ) + + /** + * Per-code-point advance overrides for every glyph of the vanilla font sheets that is not + * [DEFAULT_ADVANCE] pixels wide. + * + * Measured off `ascii.png`, `accented.png` and `nonlatin_european.png` themselves, so these are + * the advances the client actually uses — Latin, Latin Extended, Greek and Cyrillic included. + * + * Suitable as the `charWidths` argument of [TextAlignmentMath.textWidth] when [DEFAULT_ADVANCE] + * is passed as its `charSize` and `0` as its `charSpacing`. + */ + val advances: Int2IntMap = Int2IntOpenHashMap(1100).apply { + // Measured off the vanilla font sheets (ascii.png, accented.png, nonlatin_european.png): + // advance = width of the drawn pixels plus the one pixel the renderer adds after a glyph. + // The space is the exception - it carries no pixels and comes from the space provider. + put(' '.code, SPACE_ADVANCE) + + for ((advance, glyphs) in ADVANCES_BY_WIDTH) { + for (glyph in glyphs) put(glyph.code, advance) + } + }.freeze() + + /** + * Returns the advance of [codePoint] in the vanilla font. + * + * [overrides] wins over [advances], which in turn wins over [DEFAULT_ADVANCE]. + * + * @param codePoint the Unicode code point to measure + * @param bold whether the glyph is rendered bold + * @param overrides per-code-point advance overrides for glyphs this table does not cover + */ + fun charWidth( + codePoint: Int, + bold: Boolean = false, + overrides: Int2IntMap = Int2IntMaps.EMPTY_MAP, + ): Int { + val advance = when { + overrides.containsKey(codePoint) -> overrides.get(codePoint) + advances.containsKey(codePoint) -> advances.get(codePoint) + else -> DEFAULT_ADVANCE + } + + return advance + if (bold) BOLD_EXTRA_ADVANCE else 0 + } + + /** + * Returns the total number of pixels [text] advances the render cursor by when rendered in the + * vanilla font. + * + * Walks [text] per Unicode **code point**, so a surrogate pair counts as the single glyph it + * renders as. Returns `0` for an empty string. + * + * @param text the string to measure, exactly as it is rendered + * @param bold whether the text is rendered bold + * @param overrides per-code-point advance overrides for glyphs this table does not cover + */ + fun textWidth( + text: String, + bold: Boolean = false, + overrides: Int2IntMap = Int2IntMaps.EMPTY_MAP, + ): Int { + var width = 0 + var index = 0 + while (index < text.length) { + val codePoint = text.codePointAt(index) + width += charWidth(codePoint, bold, overrides) + index += Character.charCount(codePoint) + } + + return width + } + + /** + * Returns the total number of pixels [component] advances the render cursor by when rendered in + * the vanilla font. + * + * Convenience wrapper around [layoutViewText] with the metrics of the vanilla font: no extra + * spacing and no casing transformation, so the component is measured exactly as it is. + * + * @param component the component to measure, exactly as it is rendered + * @param bold the [TextDecoration.BOLD] state inherited from the surrounding style + * @param overrides per-code-point advance overrides for glyphs this table does not cover + */ + fun componentWidth( + component: Component, + bold: Boolean = false, + overrides: Int2IntMap = Int2IntMaps.EMPTY_MAP, + ): Int = layoutViewText( + component = component, + charSpacing = 0, + uppercase = false, + spaceShift = null, + bold = bold, + ) { codePoint, glyphBold -> charWidth(codePoint, glyphBold, overrides) }.width +} diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/TextAlignmentMath.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/TextAlignmentMath.kt index ab0c4e384..d88ebff38 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/TextAlignmentMath.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/TextAlignmentMath.kt @@ -92,7 +92,7 @@ object TextAlignmentMath { val usableWidth = containerWidth - (padding * 2) val freeSpace = usableWidth - textWidth - return leftShift + freeSpace + 1 + padding + return leftShift + freeSpace + padding } /** @@ -100,7 +100,8 @@ object TextAlignmentMath { * * The free space is halved with [Math.floorDiv] so that a title wider than the container (which * yields a negative free space) overflows evenly instead of being biased to one side by - * truncation towards zero. + * truncation towards zero. An odd free space leaves the extra pixel on the right, the way the + * client centres text itself. * * @param textWidth the rendered pixel width of the text, as returned by [textWidth] * @param leftShift the base pixel offset of the container area @@ -116,6 +117,6 @@ object TextAlignmentMath { val usableWidth = containerWidth - (padding * 2) val freeSpace = usableWidth - textWidth - return leftShift + Math.floorDiv(freeSpace, 2) + 1 + padding + return leftShift + Math.floorDiv(freeSpace, 2) + padding } } diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewContainerRendering.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewContainerRendering.kt index 42d9722af..da2c2e476 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewContainerRendering.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewContainerRendering.kt @@ -2,8 +2,6 @@ package dev.slne.surf.api.core.inventory.framework.internal import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder import dev.slne.surf.api.shared.api.util.InternalSurfApi -import it.unimi.dsi.fastutil.ints.Int2IntMap -import it.unimi.dsi.fastutil.ints.Int2IntMaps /** * Appends a single container component to this builder, surrounded by the shift glyphs that @@ -42,63 +40,3 @@ fun SurfComponentBuilder.appendShiftedComponent( append { text(ShiftGlyphs.renderShift(resetShift)) } } } - -/** - * An inventory title that has been laid out for rendering. - * - * @property text the glyph-interleaved string to emit - * @property width the exact number of pixels [text] advances the render cursor by - */ -@InternalSurfApi -data class RenderedViewTitle(val text: String, val width: Int) - -/** - * Lays out the rendered inventory title: every character is uppercased and inter-character - * spacing glyphs are inserted between them. Unless the title is right-aligned, a leading spacing - * glyph is prepended as well. - * - * The returned [RenderedViewTitle.width] is measured on the string that is actually emitted, so it - * accounts for the leading spacing glyph and for uppercasing that changes the glyph count (`ß` - * uppercases to `SS`). Text is walked per Unicode **code point**, so surrogate pairs are neither - * split apart nor counted twice. - * - * Shared by the per-platform `ViewContainerTitleComponent` implementations. This is internal - * infrastructure — use the platform DSL instead. - * - * @param title the plain-text inventory title - * @param charSize the pixel width of a single character in the title font - * @param charSpacing the pixel spacing to insert between each character - * @param alignRight `true` when the title is right-aligned - * @param charWidths per-code-point width overrides for glyphs that are not [charSize] wide - * @return the string to render together with its exact pixel width - */ -@InternalSurfApi -fun renderViewTitle( - title: String, - charSize: Int, - charSpacing: Int, - alignRight: Boolean, - charWidths: Int2IntMap = Int2IntMaps.EMPTY_MAP, -): RenderedViewTitle { - val uppercased = title.uppercase() - if (uppercased.isEmpty()) return RenderedViewTitle("", 0) - - val spacing = ShiftGlyphs.renderShift(charSpacing) - val text = buildString { - if (!alignRight) append(spacing) - - var index = 0 - while (index < uppercased.length) { - if (index > 0) append(spacing) - - val codePoint = uppercased.codePointAt(index) - appendCodePoint(codePoint) - index += Character.charCount(codePoint) - } - } - - val width = TextAlignmentMath.textWidth(uppercased, charSize, charSpacing, charWidths) + - if (alignRight) 0 else charSpacing - - return RenderedViewTitle(text, width) -} diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry.kt new file mode 100644 index 000000000..72bbcda57 --- /dev/null +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry.kt @@ -0,0 +1,74 @@ +package dev.slne.surf.api.core.inventory.framework.internal + +import dev.slne.surf.api.shared.api.util.InternalSurfApi + +/** + * Shared pixel math for addressing the inventory's slot grid from the header. + * + * Everything the header draws is a glyph inside the inventory title, so the only thing that can be + * computed at runtime is the **horizontal** offset of a glyph: how far right of the title origin a + * given slot column starts. The **vertical** position of a glyph is baked into the `ascent` of its + * font provider in the resource pack and cannot be changed by the server, which is why a texture + * that has to appear in a specific slot row needs its own glyph (see the platform + * `ViewBlockCellComponent`) and text that has to appear centred in a specific slot row needs its + * own font (see the platform `SurfViewSettingsDefaults.rowFont`). + * + * This is internal infrastructure — use the platform header DSL instead. + */ +@InternalSurfApi +object ViewSlotGeometry { + + /** Number of slot columns in a chest-type inventory. */ + const val COLUMNS = 9 + + /** Maximum number of slot rows in a chest-type inventory. */ + const val MAX_ROWS = 6 + + /** Horizontal pitch of the slot grid in pixels: one slot plus its one-pixel border. */ + const val SLOT_SIZE = 18 + + /** + * Returns the pixel offset of the left edge of [column] relative to the title origin. + * + * @param column the zero-based column index + * @param originX the pixel offset of column `0` + * @param slotSize the horizontal pitch of the slot grid + */ + fun columnShift(column: Int, originX: Int, slotSize: Int = SLOT_SIZE): Int = + originX + (column * slotSize) + + /** + * Returns the pixel width spanned by [columns] adjacent slot columns. + * + * @param columns how many columns the span covers + * @param slotSize the horizontal pitch of the slot grid + */ + fun spanWidth(columns: Int, slotSize: Int = SLOT_SIZE): Int = columns * slotSize + + /** + * Returns the zero-based column index of the slot at [slot]. + * + * @param slot the zero-based slot index, counted left-to-right then top-to-bottom + * @param columns the number of columns per row + */ + fun columnOf(slot: Int, columns: Int = COLUMNS): Int = Math.floorMod(slot, columns) + + /** + * Returns the **one-based** row index of the slot at [slot], matching the row numbering the + * per-row block glyphs and row fonts use. + * + * @param slot the zero-based slot index, counted left-to-right then top-to-bottom + * @param columns the number of columns per row + */ + fun rowOf(slot: Int, columns: Int = COLUMNS): Int = Math.floorDiv(slot, columns) + 1 + + /** + * Returns the zero-based slot index of the cell at [column] and one-based [row]. + * + * @param column the zero-based column index + * @param row the one-based row index + * @param columns the number of columns per row + */ + fun slotOf(column: Int, row: Int, columns: Int = COLUMNS): Int = + ((row - 1) * columns) + column +} diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewTextLayout.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewTextLayout.kt new file mode 100644 index 000000000..a5fb9ae35 --- /dev/null +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewTextLayout.kt @@ -0,0 +1,173 @@ +package dev.slne.surf.api.core.inventory.framework.internal + +import dev.slne.surf.api.shared.api.util.InternalSurfApi +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.KeybindComponent +import net.kyori.adventure.text.TextComponent +import net.kyori.adventure.text.TranslatableComponent +import net.kyori.adventure.text.format.TextDecoration + +/** + * A header text that has been laid out for rendering. + * + * @property component the component to emit, with any casing and spacing transformation applied + * @property width the exact number of pixels [component] advances the render cursor by + */ +@InternalSurfApi +data class LaidOutViewText(val component: Component, val width: Int) + +/** + * Lays out a header text for the font it is rendered in and measures the result. + * + * Both the transformation and the measurement happen per Unicode **code point** and per component + * part, so a surrogate pair counts as the single glyph it renders as and a bold word inside an + * otherwise regular line is measured bold. The [advance] of every glyph is asked for individually, + * which is what makes this work for the proportional vanilla font (see [DefaultFontWidths]) as well + * as for a fixed-width resource-pack font. + * + * Three transformations are applied to the text of every part: + * - [uppercase] uppercases it, for a font that only draws capitals. Note that uppercasing can + * change the glyph count (`ß` becomes `SS`), which is why the width is measured on the result. + * - [charSpacing] inserts a shift glyph of that many pixels in front of every glyph, tightening + * (negative) or loosening (positive) the run. + * - [spaceShift] replaces every space with a shift glyph of that many pixels. A resource-pack font + * built from a glyph sheet has no space glyph of its own — the sheet cell is empty, and an empty + * cell advances a single pixel — so the client would fall back to the missing-glyph box, which + * draws at its own baseline and therefore lands on the title line. Rendering the space as a shift + * avoids relying on the font providing one at all. Pass `null` for a font that does provide a + * space, such as the client font. + * + * The shift glyphs are emitted **inside** the part, so they render in the same font as the text — + * that font has to provide them. + * + * Parts that are not plain text keep their content: a [TranslatableComponent] is measured on its + * fallback (or its key) and a [KeybindComponent] on its keybind name, because what the client + * renders for them depends on its language file, and none of the transformations can be applied + * without breaking the translation. + * + * Shared by the per-platform header components. This is internal infrastructure — use the platform + * header DSL instead. + * + * @param component the component to lay out + * @param charSpacing pixels of spacing inserted in front of every glyph; `0` emits the text as-is + * @param uppercase whether the text is uppercased + * @param spaceShift pixels a space advances when rendered as a shift glyph, or `null` to emit the + * space itself + * @param bold the [TextDecoration.BOLD] state inherited from the surrounding style + * @param advance returns the pixel advance of one code point, given its bold state + * @return the component to render together with its exact pixel width + */ +@InternalSurfApi +fun layoutViewText( + component: Component, + charSpacing: Int, + uppercase: Boolean, + spaceShift: Int? = null, + bold: Boolean = false, + advance: (codePoint: Int, bold: Boolean) -> Int, +): LaidOutViewText { + val effectiveBold = when (component.decoration(TextDecoration.BOLD)) { + TextDecoration.State.TRUE -> true + TextDecoration.State.FALSE -> false + TextDecoration.State.NOT_SET -> bold + } + + val text = ownText(component) + val laidOutText = if (component is TextComponent) { + layoutRun( + text = if (uppercase) text.uppercase() else text, + charSpacing = charSpacing, + spaceShift = spaceShift, + bold = effectiveBold, + advance = advance + ) + } else { + // Not plain text: emit it untouched, but still measure what the client will draw for it. + LaidOutRun(text, measure(text, effectiveBold, advance)) + } + + val children = component.children().map { child -> + layoutViewText(child, charSpacing, uppercase, spaceShift, effectiveBold, advance) + } + + val width = laidOutText.width + children.sumOf { it.width } + val laidOut = children.map { it.component } + val result = if (component is TextComponent) { + component.content(laidOutText.text).children(laidOut) + } else { + component.children(laidOut) + } + + return LaidOutViewText(result, width) +} + +/** One component part after transformation, with the pixels it advances the cursor by. */ +private data class LaidOutRun(val text: String, val width: Int) + +/** + * Emits and measures a single run of text in one pass, applying [charSpacing] and [spaceShift]. + */ +private fun layoutRun( + text: String, + charSpacing: Int, + spaceShift: Int?, + bold: Boolean, + advance: (codePoint: Int, bold: Boolean) -> Int, +): LaidOutRun { + if (text.isEmpty()) return LaidOutRun(text, 0) + + val spacing = if (charSpacing != 0) ShiftGlyphs.renderShift(charSpacing) else "" + val space = if (spaceShift != null) ShiftGlyphs.renderShift(spaceShift) else "" + + var width = 0 + val emitted = StringBuilder(text.length) + var index = 0 + while (index < text.length) { + val codePoint = text.codePointAt(index) + index += Character.charCount(codePoint) + + if (charSpacing != 0) { + emitted.append(spacing) + width += charSpacing + } + + if (spaceShift != null && codePoint == ' '.code) { + emitted.append(space) + width += spaceShift + } else { + emitted.appendCodePoint(codePoint) + width += advance(codePoint, bold) + } + } + + return LaidOutRun(emitted.toString(), width) +} + +/** + * Returns the text [component] itself contributes, ignoring its children. + */ +private fun ownText(component: Component): String = when (component) { + is TextComponent -> component.content() + is TranslatableComponent -> component.fallback() ?: component.key() + is KeybindComponent -> component.keybind() + else -> "" +} + +/** + * Sums the [advance] of every code point in [text]. + */ +private inline fun measure( + text: String, + bold: Boolean, + advance: (codePoint: Int, bold: Boolean) -> Int, +): Int { + var width = 0 + var index = 0 + while (index < text.length) { + val codePoint = text.codePointAt(index) + width += advance(codePoint, bold) + index += Character.charCount(codePoint) + } + + return width +} diff --git a/surf-api-core/surf-api-core/src/test/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewHeaderLayoutTest.kt b/surf-api-core/surf-api-core/src/test/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewHeaderLayoutTest.kt new file mode 100644 index 000000000..a7bd5c2c1 --- /dev/null +++ b/surf-api-core/surf-api-core/src/test/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewHeaderLayoutTest.kt @@ -0,0 +1,376 @@ +package dev.slne.surf.api.core.inventory.framework.internal + +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.TextComponent +import net.kyori.adventure.text.TranslatableComponent +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.TextDecoration +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * Guards the invariants the header layout rests on. + * + * `ViewContainer` resets the render cursor with `-(textureWidth + positionalShift)` after every + * component, so a width that does not match the pixels a component actually advances the cursor by + * shifts everything rendered after it. For text drawn in the vanilla font that width comes from + * [DefaultFontWidths]; the shift that centers it comes from [TextAlignmentMath]; the shift that + * puts a texture or a text run on a specific slot column comes from [ViewSlotGeometry]. + */ +class ViewHeaderLayoutTest { + + private val titles = listOf( + "", + "A", + "Shop", + "Spieler-Statistiken", + "iiiiiiiii", + "WWWWWWWWW", + "Straße", + "Menü 1/4", + "!!!...:::", + "🎉 Event 🎉", + ) + + @Test + fun `vanilla widths are measured per code point`() { + // Two code points, one of which is a surrogate pair — four UTF-16 chars in total. + assertEquals(2 * DefaultFontWidths.DEFAULT_ADVANCE, DefaultFontWidths.textWidth("A🎉")) + } + + @Test + fun `narrow glyphs are measured narrower than regular ones`() { + assertEquals(2, DefaultFontWidths.charWidth('i'.code)) + assertEquals(3, DefaultFontWidths.charWidth('l'.code)) + assertEquals(4, DefaultFontWidths.charWidth(' '.code)) + assertEquals(7, DefaultFontWidths.charWidth('@'.code)) + assertEquals(DefaultFontWidths.DEFAULT_ADVANCE, DefaultFontWidths.charWidth('W'.code)) + assertEquals(DefaultFontWidths.DEFAULT_ADVANCE, DefaultFontWidths.charWidth('ß'.code)) + } + + @Test + fun `bold adds one pixel per glyph`() { + val text = "Shop" + assertEquals( + DefaultFontWidths.textWidth(text) + text.length, + DefaultFontWidths.textWidth(text, bold = true) + ) + } + + @Test + fun `overrides win over the built-in table`() { + val overrides = Int2IntOpenHashMap(1).apply { put('i'.code, 9) } + + assertEquals(9, DefaultFontWidths.charWidth('i'.code, overrides = overrides)) + assertEquals(18, DefaultFontWidths.textWidth("ii", overrides = overrides)) + } + + @Test + fun `text width equals the sum of the glyph advances`() { + for (title in titles) { + var expected = 0 + var index = 0 + while (index < title.length) { + val codePoint = title.codePointAt(index) + expected += DefaultFontWidths.charWidth(codePoint) + index += Character.charCount(codePoint) + } + + assertEquals(expected, DefaultFontWidths.textWidth(title), "width of '$title'") + } + } + + @Test + fun `centered titles stay centred whatever they contain`() { + // Geometry of the vanilla chest inventory, see the platform ViewHeaderGeometry defaults. + val leftShift = -8 + val padding = 0 + val containerWidth = 176 + + for (title in titles) { + val width = DefaultFontWidths.textWidth(title) + val shift = + TextAlignmentMath.centerAlignedShift(width, leftShift, padding, containerWidth) + + // Doubled to compare half-pixel centres in integer math. + val textCentre = (2 * shift) + width + val containerCentre = (2 * leftShift) + containerWidth + + assertTrue( + (textCentre - containerCentre) in -2..2, + "'$title' is off centre by ${(textCentre - containerCentre) / 2.0}px" + ) + } + } + + @Test + fun `right aligned titles end at the same pixel`() { + val leftShift = -8 + val padding = 0 + val containerWidth = 176 + + val ends = titles.map { title -> + val width = DefaultFontWidths.textWidth(title) + TextAlignmentMath.rightAlignedShift( + width, + leftShift, + padding, + containerWidth + ) + width + } + + assertEquals(1, ends.distinct().size, "right aligned titles end at $ends") + } + + @Test + fun `a component is measured across its whole tree`() { + val component = Component.text("Guthaben: ").append(Component.text("1.250")) + + assertEquals(DefaultFontWidths.textWidth("Guthaben: 1.250"), DefaultFontWidths.componentWidth(component)) + } + + @Test + fun `colour does not change a measured width`() { + val plain = Component.text("Shop") + val coloured = Component.text("Shop").color(NamedTextColor.GOLD) + + assertEquals( + DefaultFontWidths.componentWidth(plain), + DefaultFontWidths.componentWidth(coloured) + ) + } + + @Test + fun `bold is measured per part and inherited by children`() { + val boldChild = Component.text("A") + .append(Component.text("B").decorate(TextDecoration.BOLD)) + val boldParent = Component.text("A").decorate(TextDecoration.BOLD) + .append(Component.text("B")) + val boldParentPlainChild = Component.text("A").decorate(TextDecoration.BOLD) + .append(Component.text("B").decoration(TextDecoration.BOLD, false)) + + val plain = DefaultFontWidths.charWidth('A'.code) + + assertEquals((2 * plain) + 1, DefaultFontWidths.componentWidth(boldChild)) + assertEquals((2 * plain) + 2, DefaultFontWidths.componentWidth(boldParent)) + assertEquals((2 * plain) + 1, DefaultFontWidths.componentWidth(boldParentPlainChild)) + } + + @Test + fun `translatables are measured on their fallback`() { + val withFallback = Component.translatable("surf.menu.title", "Shop") + val withoutFallback = Component.translatable("shop") + + assertEquals(DefaultFontWidths.textWidth("Shop"), DefaultFontWidths.componentWidth(withFallback)) + assertEquals(DefaultFontWidths.textWidth("shop"), DefaultFontWidths.componentWidth(withoutFallback)) + } + + /** Reverse of [ShiftGlyphs.renderShift]: maps each single-glyph shift back to its pixel amount. */ + private val shiftAmounts: Map = buildMap { + for (exponent in 0..9) { + val amount = 1 shl exponent + for (signed in intArrayOf(amount, -amount)) { + val glyph = ShiftGlyphs.renderShift(signed) + check(glyph.length == 1) { "expected a single glyph for $signed, got '$glyph'" } + put(glyph[0], signed) + } + } + } + + /** + * Independently measures how far a laid-out component advances the cursor: shift glyphs + * contribute their own amount, every other code point the advance of the font it is measured in. + */ + private fun advanceOf(component: Component, charSize: Int, bold: Boolean = false): Int { + val effectiveBold = when (component.decoration(TextDecoration.BOLD)) { + TextDecoration.State.TRUE -> true + TextDecoration.State.FALSE -> false + TextDecoration.State.NOT_SET -> bold + } + val glyphSize = charSize + if (effectiveBold) 1 else 0 + + var advance = 0 + val text = (component as? TextComponent)?.content() ?: "" + var index = 0 + while (index < text.length) { + val codePoint = text.codePointAt(index) + val shift = if (Character.charCount(codePoint) == 1) { + shiftAmounts[codePoint.toChar()] + } else { + null + } + + advance += shift ?: glyphSize + index += Character.charCount(codePoint) + } + + for (child in component.children()) { + advance += advanceOf(child, charSize, effectiveBold) + } + + return advance + } + + /** Metrics of the fixed-width, capitals-only resource-pack menu font. */ + private fun menuLayout(component: Component) = layoutViewText( + component = component, + charSpacing = -1, + uppercase = true, + spaceShift = 4, + ) { _, bold -> if (bold) 10 else 9 } + + @Test + fun `a fixed width font reports the advance it actually renders`() { + val components = listOf( + Component.text("Shop"), + Component.text("Menu 1/4"), + Component.text("A").append(Component.text("BC").decorate(TextDecoration.BOLD)), + Component.text("Guthaben: ").append(Component.text("1250")), + Component.text(""), + ) + + for (component in components) { + val laidOut = menuLayout(component) + + assertEquals( + advanceOf(laidOut.component, charSize = 9), + laidOut.width, + "reported width of '" + plain(component) + "'" + ) + } + } + + @Test + fun `a font without a space glyph renders spaces as shifts`() { + val text = "AB CD" + val laidOut = layoutViewText( + component = Component.text(text), + charSpacing = 0, + uppercase = false, + spaceShift = 4, + ) { _, _ -> 9 } + + // Four glyphs at 9px plus a 4px shift, and the space itself must be gone: it would render + // as the missing-glyph box of the font, on the title line instead of in the row. + assertEquals((4 * 9) + 4, laidOut.width) + assertEquals(laidOut.width, advanceOf(laidOut.component, charSize = 9)) + assertTrue( + ' ' !in (laidOut.component as TextComponent).content(), + "the space was emitted verbatim: '${(laidOut.component as TextComponent).content()}'" + ) + } + + @Test + fun `a font with a space glyph keeps the space`() { + val laidOut = layoutViewText( + component = Component.text("AB CD"), + charSpacing = 0, + uppercase = false, + spaceShift = null, + ) { _, _ -> 9 } + + assertEquals(5 * 9, laidOut.width) + assertEquals("AB CD", (laidOut.component as TextComponent).content()) + } + + @Test + fun `the menu font renders eight pixels per glyph`() { + // 9px cell tightened by 1px of spacing in front of every glyph. + assertEquals(4 * 8, menuLayout(Component.text("Shop")).width) + } + + @Test + fun `a capitals only font uppercases every part`() { + val laidOut = menuLayout(Component.text("Shop ").append(Component.text("Seite"))) + + // Shift glyphs filtered out, which for these metrics includes the space: it is rendered as + // a shift because the font draws no space glyph. + assertEquals("SHOPSEITE", plain(laidOut.component).filter { it !in shiftAmounts }) + } + + @Test + fun `uppercasing that changes the glyph count is measured on the result`() { + // 'ß' uppercases to 'SS', so the run is one glyph wider than the input. + assertEquals(2 * 8, menuLayout(Component.text("ß")).width) + } + + @Test + fun `translatable parts are neither uppercased nor spaced`() { + val laidOut = menuLayout(Component.translatable("surf.menu.title", "Shop")) + + assertEquals("Shop", plain(laidOut.component)) + assertEquals(4 * 9, laidOut.width) + } + + private fun plain(component: Component): String = buildString { + append((component as? TextComponent)?.content() ?: fallbackOf(component)) + for (child in component.children()) append(plain(child)) + } + + private fun fallbackOf(component: Component) = + (component as? TranslatableComponent)?.let { it.fallback() ?: it.key() } ?: "" + + @Test + fun `column shifts follow the slot pitch`() { + val originX = -1 + + assertEquals(-1, ViewSlotGeometry.columnShift(0, originX)) + assertEquals(17, ViewSlotGeometry.columnShift(1, originX)) + assertEquals(143, ViewSlotGeometry.columnShift(8, originX)) + assertEquals(162, ViewSlotGeometry.spanWidth(ViewSlotGeometry.COLUMNS)) + } + + @Test + fun `the vanilla slot grid is centred in the vanilla inventory`() { + // Both centres must coincide, otherwise a centred title would not sit above a centred row + // text. Doubled to compare half-pixel centres in integer math. + val inventoryCentre = (2 * -8) + 176 + val gridCentre = (2 * ViewSlotGeometry.columnShift(0, -1)) + + ViewSlotGeometry.spanWidth(ViewSlotGeometry.COLUMNS) + + assertEquals(inventoryCentre, gridCentre) + } + + @Test + fun `slot indices map to the column and one-based row they render in`() { + assertEquals(0, ViewSlotGeometry.columnOf(0)) + assertEquals(1, ViewSlotGeometry.rowOf(0)) + assertEquals(8, ViewSlotGeometry.columnOf(8)) + assertEquals(1, ViewSlotGeometry.rowOf(8)) + assertEquals(0, ViewSlotGeometry.columnOf(9)) + assertEquals(2, ViewSlotGeometry.rowOf(9)) + assertEquals(4, ViewSlotGeometry.columnOf(31)) + assertEquals(4, ViewSlotGeometry.rowOf(31)) + + for (slot in 0 until ViewSlotGeometry.COLUMNS * ViewSlotGeometry.MAX_ROWS) { + assertEquals( + slot, + ViewSlotGeometry.slotOf( + ViewSlotGeometry.columnOf(slot), + ViewSlotGeometry.rowOf(slot) + ), + "round-trip of slot $slot" + ) + } + } + + @Test + fun `a text run centred in a column span stays inside it`() { + val originX = -1 + val columns = 2..6 + val spanShift = ViewSlotGeometry.columnShift(columns.first, originX) + val spanWidth = ViewSlotGeometry.spanWidth(columns.count()) + + for (title in titles) { + val width = DefaultFontWidths.textWidth(title) + if (width > spanWidth) continue + + val shift = TextAlignmentMath.centerAlignedShift(width, spanShift, 0, spanWidth) + + assertTrue(shift >= spanShift, "'$title' starts left of the span") + assertTrue(shift + width <= spanShift + spanWidth + 1, "'$title' ends right of the span") + } + } +} diff --git a/surf-api-core/surf-api-core/src/test/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewTitleRenderingTest.kt b/surf-api-core/surf-api-core/src/test/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewTitleRenderingTest.kt deleted file mode 100644 index ae54ead74..000000000 --- a/surf-api-core/surf-api-core/src/test/kotlin/dev/slne/surf/api/core/inventory/framework/internal/ViewTitleRenderingTest.kt +++ /dev/null @@ -1,175 +0,0 @@ -package dev.slne.surf.api.core.inventory.framework.internal - -import it.unimi.dsi.fastutil.ints.Int2IntMap -import it.unimi.dsi.fastutil.ints.Int2IntMaps -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -/** - * Guards the invariant the whole header layout rests on: the width [renderViewTitle] reports must - * equal the number of pixels the string it returns actually advances the render cursor by. - * - * `ViewContainer` resets the cursor with `-(textureWidth + positionalShift)` after every component, - * so any mismatch here shifts every texture that follows the title. - */ -class ViewTitleRenderingTest { - - private val charSize = 9 - private val charSpacing = -1 - - /** Reverse of [ShiftGlyphs.renderShift]: maps each single-glyph shift back to its pixel amount. */ - private val shiftAmounts: Map = buildMap { - for (exponent in 0..9) { - val amount = 1 shl exponent - for (signed in intArrayOf(amount, -amount)) { - val glyph = ShiftGlyphs.renderShift(signed) - check(glyph.length == 1) { "expected a single glyph for $signed, got '$glyph'" } - put(glyph[0], signed) - } - } - } - - /** - * Independently measures how far [text] advances the cursor: shift glyphs contribute their own - * amount, every other code point contributes its glyph width. - */ - private fun advanceOf(text: String, charWidths: Int2IntMap = Int2IntMaps.EMPTY_MAP): Int { - var advance = 0 - var index = 0 - while (index < text.length) { - val codePoint = text.codePointAt(index) - val shift = if (Character.charCount(codePoint) == 1) { - shiftAmounts[codePoint.toChar()] - } else { - null - } - - advance += shift - ?: if (charWidths.containsKey(codePoint)) charWidths.get(codePoint) else charSize - index += Character.charCount(codePoint) - } - - return advance - } - - private val titles = listOf( - "", - "A", - "Shop", - "Straße", - "Grüße", - "Äpfel", - "Öl", - "Menü", - "Ein sehr langer Titel für das Menü", - "Werkzeug & Rüstung", - "Statistiken 🎉", - "ẞ", - ) - - @Test - fun `reported width matches the rendered advance`() { - for (title in titles) { - for (alignRight in listOf(false, true)) { - val rendered = renderViewTitle(title, charSize, charSpacing, alignRight) - - assertEquals( - advanceOf(rendered.text), - rendered.width, - "width mismatch for '$title' (alignRight=$alignRight)" - ) - } - } - } - - @Test - fun `reported width matches the rendered advance with per-glyph overrides`() { - val charWidths = Int2IntOpenHashMap().apply { - put('Ä'.code, 11) - put('Ü'.code, 11) - put('I'.code, 4) - put(' '.code, 4) - } - - for (title in titles) { - for (alignRight in listOf(false, true)) { - val rendered = renderViewTitle(title, charSize, charSpacing, alignRight, charWidths) - - assertEquals( - advanceOf(rendered.text, charWidths), - rendered.width, - "width mismatch for '$title' (alignRight=$alignRight)" - ) - } - } - } - - @Test - fun `eszett is measured as the two glyphs it uppercases to`() { - // "Straße" renders as STRASSE: 7 glyphs, 6 gaps, plus the leading spacing glyph. - val rendered = renderViewTitle("Straße", charSize, charSpacing, alignRight = false) - - assertEquals((7 * charSize) + (6 * charSpacing) + charSpacing, rendered.width) - } - - @Test - fun `right aligned titles omit the leading spacing glyph`() { - val left = renderViewTitle("Straße", charSize, charSpacing, alignRight = false) - val right = renderViewTitle("Straße", charSize, charSpacing, alignRight = true) - - assertEquals(left.width - charSpacing, right.width) - } - - @Test - fun `empty titles render nothing`() { - val rendered = renderViewTitle("", charSize, charSpacing, alignRight = false) - - assertEquals("", rendered.text) - assertEquals(0, rendered.width) - } - - @Test - fun `surrogate pairs count as a single glyph`() { - val rendered = renderViewTitle("🎉", charSize, charSpacing, alignRight = true) - - assertEquals(charSize, rendered.width) - } - - @Test - fun `text width is measured per code point`() { - // Two code points, one of which is a surrogate pair — four UTF-16 chars in total. - assertEquals( - (2 * charSize) + charSpacing, - TextAlignmentMath.textWidth("A🎉", charSize, charSpacing) - ) - } - - @Test - fun `centered titles stay centred whatever they contain`() { - // Geometry of the real header container, see ViewContainerTitleComponent. - val leftShift = 31 - val padding = 2 - val containerWidth = 100 - - for (title in titles) { - val rendered = renderViewTitle(title, charSize, charSpacing, alignRight = false) - val shift = TextAlignmentMath.centerAlignedShift( - rendered.width, - leftShift, - padding, - containerWidth - ) - - // Doubled to compare half-pixel centres in integer math. - val textCentre = (2 * shift) + rendered.width - val containerCentre = (2 * leftShift) + containerWidth - - assertTrue( - (textCentre - containerCentre) in -2..2, - "'$title' is off centre by ${(textCentre - containerCentre) / 2.0}px" - ) - } - } -} diff --git a/surf-api-minestom/api/surf-api-minestom.api b/surf-api-minestom/api/surf-api-minestom.api index 11c1141f5..449e4c59a 100644 --- a/surf-api-minestom/api/surf-api-minestom.api +++ b/surf-api-minestom/api/surf-api-minestom.api @@ -594,6 +594,10 @@ public abstract class dev/slne/surf/api/minestom/inventory/framework/view/Abstra protected fun onViewOpen (Lme/devnatan/inventoryframework/context/OpenContext;)V protected fun onViewRender (Lme/devnatan/inventoryframework/context/RenderContext;)V protected fun onViewUpdate (Lme/devnatan/inventoryframework/context/Context;)V + protected final fun updateHeader (Lme/devnatan/inventoryframework/context/Context;Ljava/lang/String;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;)V + protected final fun updateHeader (Lme/devnatan/inventoryframework/context/Context;Lnet/kyori/adventure/text/Component;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;)V + public static synthetic fun updateHeader$default (Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfView;Lme/devnatan/inventoryframework/context/Context;Ljava/lang/String;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;ILjava/lang/Object;)V + public static synthetic fun updateHeader$default (Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfView;Lme/devnatan/inventoryframework/context/Context;Lnet/kyori/adventure/text/Component;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;ILjava/lang/Object;)V } public abstract class dev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewContext { @@ -752,10 +756,12 @@ public final class dev/slne/surf/api/minestom/inventory/framework/view/container public fun ()V public final fun addChild (Ldev/slne/surf/api/minestom/inventory/framework/view/container/component/ViewContainerComponent;)V public final fun getChildren ()Ljava/util/List; + public final fun getLastRenderedTitle ()Lnet/kyori/adventure/text/Component; public final fun hasComponentOfType (Ljava/lang/Class;)Z public final fun removeChild (Ldev/slne/surf/api/minestom/inventory/framework/view/container/component/ViewContainerComponent;)V + public final fun removeChildren (Lkotlin/jvm/functions/Function1;)V public final fun removeChildrenOfType (Ljava/lang/Class;)V - public final fun render ()Lnet/kyori/adventure/text/TextComponent; + public final fun render ()Lnet/kyori/adventure/text/Component; } public abstract interface class dev/slne/surf/api/minestom/inventory/framework/view/container/component/ViewContainerComponent { @@ -772,7 +778,10 @@ public final class dev/slne/surf/api/minestom/inventory/framework/view/container } public final class dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewBlockCellComponent : dev/slne/surf/api/minestom/inventory/framework/view/container/component/ViewContainerComponent { - public fun (II)V + public static final field Companion Ldev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewBlockCellComponent$Companion; + public synthetic fun (II)V + public fun (IILdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;)V + public synthetic fun (IILdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z public final fun getColumn ()I public fun getHasExactWidth ()Z @@ -783,51 +792,96 @@ public final class dev/slne/surf/api/minestom/inventory/framework/view/container public fun renderComponent (Ldev/slne/surf/api/core/messages/builder/SurfComponentBuilder;)V } -public final class dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerBackHintComponent : dev/slne/surf/api/minestom/inventory/framework/view/container/component/ViewContainerComponent { - public static final field INSTANCE Ldev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerBackHintComponent; +public final class dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewBlockCellComponent$Companion { + public final fun forSlot (ILdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;)Ldev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewBlockCellComponent; + public static synthetic fun forSlot$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewBlockCellComponent$Companion;ILdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)Ldev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewBlockCellComponent; +} + +public final class dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerRowTextComponent : dev/slne/surf/api/minestom/inventory/framework/view/container/component/ViewContainerComponent { + public fun (ILjava/lang/String;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;)V + public synthetic fun (ILjava/lang/String;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ILnet/kyori/adventure/text/Component;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;)V + public synthetic fun (ILnet/kyori/adventure/text/Component;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z + public final fun getAlignment ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment; + public final fun getColumns ()Lkotlin/ranges/IntRange; public fun getHasExactWidth ()Z public fun getPositionalShift ()I + public final fun getRow ()I + public final fun getText ()Lnet/kyori/adventure/text/Component; public fun getTextureWidth ()I public fun hashCode ()I public fun renderComponent (Ldev/slne/surf/api/core/messages/builder/SurfComponentBuilder;)V - public fun toString ()Ljava/lang/String; } public final class dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerTitleComponent : dev/slne/surf/api/minestom/inventory/framework/view/container/component/ViewContainerComponent { - public static final field CHAR_SIZE I - public static final field CHAR_SPACING I - public static final field CONTAINER_WIDTH I - public static final field Companion Ldev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerTitleComponent$Companion; - public static final field LEFT_SHIFT I - public static final field PADDING I - public fun (Ljava/lang/String;Lnet/kyori/adventure/key/Key;ILdev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lit/unimi/dsi/fastutil/ints/Int2IntMap;)V - public synthetic fun (Ljava/lang/String;Lnet/kyori/adventure/key/Key;ILdev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lit/unimi/dsi/fastutil/ints/Int2IntMap;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;)V + public synthetic fun (Ljava/lang/String;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lnet/kyori/adventure/text/Component;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;)V + public synthetic fun (Lnet/kyori/adventure/text/Component;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z public fun getHasExactWidth ()Z public fun getPositionalShift ()I + public final fun getTextAlignment ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment; public fun getTextureWidth ()I + public final fun getTitle ()Lnet/kyori/adventure/text/Component; public fun hashCode ()I public fun renderComponent (Ldev/slne/surf/api/core/messages/builder/SurfComponentBuilder;)V } -public final class dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerTitleComponent$Companion { -} - public final class dev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerDSLKt { public static final fun addChild (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/container/component/ViewContainerComponent;)V - public static final fun backHint (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;)V - public static final fun blockCell (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;II)V - public static final fun blockColumn (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;)V - public static final fun blockColumn (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[I)V + public static final synthetic fun backHint (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;)V + public static final fun blockAllSlots (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;)V + public static final fun blockAllSlots (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows;Lit/unimi/dsi/fastutil/ints/IntCollection;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;)V + public static synthetic fun blockAllSlots$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V + public static synthetic fun blockAllSlots$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows;Lit/unimi/dsi/fastutil/ints/IntCollection;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V + public static final synthetic fun blockCell (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;II)V + public static final fun blockCell (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;IILdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;)V + public static synthetic fun blockCell$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;IILdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V + public static final synthetic fun blockColumn (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;)V + public static final fun blockColumn (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;ILdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;)V + public static final synthetic fun blockColumn (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[I)V + public static final fun blockColumn (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[IILdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;)V + public static synthetic fun blockColumn$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;ILdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V public static synthetic fun blockColumn$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;ILjava/lang/Object;)V - public static final fun blockRow (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;)V - public static final fun blockRow (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[I)V + public static synthetic fun blockColumn$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[IILdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V + public static final synthetic fun blockRow (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;)V + public static final fun blockRow (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;)V + public static final synthetic fun blockRow (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[I)V + public static final fun blockRow (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[ILdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;)V public static synthetic fun blockRow$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;ILjava/lang/Object;)V + public static synthetic fun blockRow$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V + public static synthetic fun blockRow$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[ILdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V + public static final fun blockSlot (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;)V + public static synthetic fun blockSlot$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V + public static final fun clearRowText (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;I)V + public static final fun clearRowTexts (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;)V public static final fun hasComponentOfType (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ljava/lang/Class;)Z - public static final fun header (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;Ljava/lang/String;)V + public static final fun header (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/jvm/functions/Function1;)V + public static final synthetic fun header (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;Ljava/lang/String;)V + public static final fun header (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;Ljava/lang/String;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;)V + public static final fun header (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;Lnet/kyori/adventure/text/Component;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;)V + public static synthetic fun header$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static synthetic fun header$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;Ljava/lang/String;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;ILjava/lang/Object;)V + public static synthetic fun header$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;Lnet/kyori/adventure/text/Component;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;ILjava/lang/Object;)V public static final fun removeChild (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/container/component/ViewContainerComponent;)V public static final fun removeChildrenOfType (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ljava/lang/Class;)V + public static final fun rowText (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILjava/lang/String;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;)V + public static final fun rowText (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILnet/kyori/adventure/text/Component;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;)V + public static final fun rowText (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;ILdev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Lkotlin/jvm/functions/Function1;)V + public static final fun rowText (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;ILjava/lang/String;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;)V + public static final fun rowText (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;ILnet/kyori/adventure/text/Component;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;)V + public static synthetic fun rowText$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILjava/lang/String;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;ILjava/lang/Object;)V + public static synthetic fun rowText$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILnet/kyori/adventure/text/Component;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;ILjava/lang/Object;)V + public static synthetic fun rowText$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;ILdev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static synthetic fun rowText$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;ILjava/lang/String;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;ILjava/lang/Object;)V + public static synthetic fun rowText$default (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;ILnet/kyori/adventure/text/Component;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;ILjava/lang/Object;)V + public static final fun rowTextAt (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;IILjava/lang/String;)V + public static final fun rowTextAt (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfViewRef;IILnet/kyori/adventure/text/Component;)V + public static final fun unblockAllSlots (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;)V + public static final fun unblockCell (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;II)V + public static final fun unblockSlot (Ldev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext;I)V } public final class dev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerModificationContext { @@ -950,6 +1004,15 @@ public final class dev/slne/surf/api/minestom/inventory/framework/view/paginatio public static final fun pagination (Ldev/slne/surf/api/minestom/inventory/framework/view/PaginatedSurfViewContext;Lkotlin/jvm/functions/Function1;)V } +public abstract interface class dev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator { + public static final field Companion Ldev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator$Companion; + public abstract fun render (II)Lnet/kyori/adventure/text/Component; +} + +public final class dev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator$Companion { + public final fun getDefault ()Ldev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator; +} + public abstract interface class dev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationSourceType { } @@ -980,31 +1043,51 @@ public final class dev/slne/surf/api/minestom/inventory/framework/view/paginatio public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginatedViewSettings : dev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettings { public fun ()V - public fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition;)V - public synthetic fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition;Ldev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator;Lnet/kyori/adventure/sound/Sound;)V + public synthetic fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition;Ldev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator;Lnet/kyori/adventure/sound/Sound;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lnet/kyori/adventure/key/Key; + public final fun component10 ()Z + public final fun component11 ()Z + public final fun component12 ()Z + public final fun component13 ()Z + public final fun component14 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows; + public final fun component15 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows; + public final fun component16 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition; + public final fun component17 ()Ldev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator; + public final fun component18 ()Lnet/kyori/adventure/sound/Sound; public final fun component2 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment; - public final fun component3 ()Z - public final fun component4 ()Z - public final fun component5 ()Z - public final fun component6 ()Z + public final fun component3 ()Lnet/kyori/adventure/text/format/TextColor; + public final fun component4 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public final fun component5 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public final fun component6 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry; public final fun component7 ()Z - public final fun component8 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows; - public final fun component9 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition; - public final fun copy (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginatedViewSettings; - public static synthetic fun copy$default (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginatedViewSettings;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition;ILjava/lang/Object;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginatedViewSettings; + public final fun component8 ()Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; + public final fun component9 ()Z + public final fun copy (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition;Ldev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator;Lnet/kyori/adventure/sound/Sound;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginatedViewSettings; + public static synthetic fun copy$default (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginatedViewSettings;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition;Ldev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator;Lnet/kyori/adventure/sound/Sound;ILjava/lang/Object;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginatedViewSettings; public fun equals (Ljava/lang/Object;)Z + public fun getBackgroundGlyph ()Z public fun getCancelOnClick ()Z public fun getCancelOnDrag ()Z public fun getCancelOnDrop ()Z public fun getCancelOnPickup ()Z public fun getFont ()Lnet/kyori/adventure/key/Key; + public fun getHeaderFontMetrics ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public fun getHeaderGeometry ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry; public fun getHeaderTextAlignment ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment; + public fun getHeaderTextColor ()Lnet/kyori/adventure/text/format/TextColor; public fun getNavigateBackOnOutsideClick ()Z public final fun getPaginationButtonPosition ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition; + public final fun getPaginationContentRowCount ()I + public final fun getPaginationEmptyRows ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows; + public final fun getPaginationPageIndicator ()Ldev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator; + public final fun getPaginationSwitchSound ()Lnet/kyori/adventure/sound/Sound; public final fun getPaginationViewRows ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows; + public fun getRowFontMetrics ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public fun getRowFonts ()Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; public fun getRows ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows; public fun hashCode ()I + public fun rowFont (I)Lnet/kyori/adventure/key/Key; public fun toString ()Ljava/lang/String; } @@ -1016,69 +1099,202 @@ public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/ public static fun values ()[Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition; } +public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows : java/lang/Enum { + public static final field Companion Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows$Companion; + public static final field NONE Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows; + public static final field ONE Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows; + public static final field THREE Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows; + public static final field TWO Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getRows ()I + public static fun valueOf (Ljava/lang/String;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows; + public static fun values ()[Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows; +} + +public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows$Companion { + public final fun byRows (I)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows; + public final fun byRowsOrNull (I)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows; +} + public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows : java/lang/Enum { + public static final field FIVE Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows; public static final field FOUR Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows; public static final field ONE Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows; public static final field THREE Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows; public static final field TWO Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows; public final fun getActualRows ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows; + public final fun getContentRows ()I public static fun getEntries ()Lkotlin/enums/EnumEntries; - public final fun getPaginationContentRows ()Lkotlin/ranges/IntRange; + public final synthetic fun getPaginationContentRows ()Lkotlin/ranges/IntRange; public static fun valueOf (Ljava/lang/String;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows; public static fun values ()[Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows; } public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/SimpleViewSettings : dev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettings { public fun ()V - public fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows;)V - public synthetic fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows;)V + public synthetic fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lnet/kyori/adventure/key/Key; + public final fun component10 ()Z + public final fun component11 ()Z + public final fun component12 ()Z + public final fun component13 ()Z + public final fun component14 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows; public final fun component2 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment; - public final fun component3 ()Z - public final fun component4 ()Z - public final fun component5 ()Z - public final fun component6 ()Z + public final fun component3 ()Lnet/kyori/adventure/text/format/TextColor; + public final fun component4 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public final fun component5 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public final fun component6 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry; public final fun component7 ()Z - public final fun component8 ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows; - public final fun copy (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/SimpleViewSettings; - public static synthetic fun copy$default (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/SimpleViewSettings;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows;ILjava/lang/Object;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/SimpleViewSettings; + public final fun component8 ()Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; + public final fun component9 ()Z + public final fun copy (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/SimpleViewSettings; + public static synthetic fun copy$default (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/SimpleViewSettings;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows;ILjava/lang/Object;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/SimpleViewSettings; public fun equals (Ljava/lang/Object;)Z + public fun getBackgroundGlyph ()Z public fun getCancelOnClick ()Z public fun getCancelOnDrag ()Z public fun getCancelOnDrop ()Z public fun getCancelOnPickup ()Z public fun getFont ()Lnet/kyori/adventure/key/Key; + public fun getHeaderFontMetrics ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public fun getHeaderGeometry ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry; public fun getHeaderTextAlignment ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment; + public fun getHeaderTextColor ()Lnet/kyori/adventure/text/format/TextColor; public fun getNavigateBackOnOutsideClick ()Z + public fun getRowFontMetrics ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public fun getRowFonts ()Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; public fun getRows ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows; public fun hashCode ()I + public fun rowFont (I)Lnet/kyori/adventure/key/Key; public fun toString ()Ljava/lang/String; } public abstract interface class dev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettings { + public abstract fun getBackgroundGlyph ()Z public abstract fun getCancelOnClick ()Z public abstract fun getCancelOnDrag ()Z public abstract fun getCancelOnDrop ()Z public abstract fun getCancelOnPickup ()Z public abstract fun getFont ()Lnet/kyori/adventure/key/Key; + public abstract fun getHeaderFontMetrics ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public abstract fun getHeaderGeometry ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry; public abstract fun getHeaderTextAlignment ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment; + public abstract fun getHeaderTextColor ()Lnet/kyori/adventure/text/format/TextColor; public abstract fun getNavigateBackOnOutsideClick ()Z + public abstract fun getRowFontMetrics ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public abstract fun getRowFonts ()Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; public abstract fun getRows ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows; + public fun rowFont (I)Lnet/kyori/adventure/key/Key; +} + +public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettings$DefaultImpls { + public static fun rowFont (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettings;I)Lnet/kyori/adventure/key/Key; } public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettingsDefaults { + public static final field DEFAULT_BACKGROUND_GLYPH Z public static final field DEFAULT_CANCEL_ON_CLICK Z public static final field DEFAULT_CANCEL_ON_DRAG Z public static final field DEFAULT_CANCEL_ON_DROP Z public static final field DEFAULT_CANCEL_ON_PICKUP Z public static final field DEFAULT_NAVIGATE_BACK_ON_CLOSE Z public static final field INSTANCE Ldev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettingsDefaults; + public final fun getASCENDED_HEADER_FONT ()Lnet/kyori/adventure/key/Key; public final fun getDEFAULT_HEADER_ALIGNMENT ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment; public final fun getDEFAULT_HEADER_FONT ()Lnet/kyori/adventure/key/Key; + public final fun getDEFAULT_HEADER_FONT_METRICS ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public final fun getDEFAULT_HEADER_GEOMETRY ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry; + public final fun getDEFAULT_HEADER_TEXT_COLOR ()Lnet/kyori/adventure/text/format/TextColor; public final fun getDEFAULT_MENU_FONT ()Lnet/kyori/adventure/key/Key; public final fun getDEFAULT_PAGINATION_BUTTON_POSITION ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition; + public final fun getDEFAULT_PAGINATION_EMPTY_ROWS ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows; + public final fun getDEFAULT_PAGINATION_PAGE_INDICATOR ()Ldev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator; + public final fun getDEFAULT_PAGINATION_SWITCH_SOUND ()Lnet/kyori/adventure/sound/Sound; public final fun getDEFAULT_PAGINATION_VIEW_ROWS ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows; + public final fun getDEFAULT_ROW_FONTS ()Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; + public final fun getDEFAULT_ROW_FONT_METRICS ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public final fun getDEFAULT_ROW_TEXT_ALIGNMENT ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment; + public final fun getDEFAULT_ROW_TEXT_COLUMNS ()Lkotlin/ranges/IntRange; public final fun getDEFAULT_VIEW_ROWS ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows; + public final fun rowFont (I)Lnet/kyori/adventure/key/Key; +} + +public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics { + public static final field Companion Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics$Companion; + public static final field MENU_CHAR_SIZE I + public static final field MENU_CHAR_SPACING I + public static final field MENU_SPACE_SIZE I + public fun ()V + public fun (IIZZLit/unimi/dsi/fastutil/ints/Int2IntMap;)V + public synthetic fun (IIZZLit/unimi/dsi/fastutil/ints/Int2IntMap;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun advance (IZ)I + public static synthetic fun advance$default (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;IZILjava/lang/Object;)I + public final fun component1 ()I + public final fun component2 ()I + public final fun component3 ()Z + public final fun component4 ()Z + public final fun component5 ()Lit/unimi/dsi/fastutil/ints/Int2IntMap; + public final fun copy (IIZZLit/unimi/dsi/fastutil/ints/Int2IntMap;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public static synthetic fun copy$default (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;IIZZLit/unimi/dsi/fastutil/ints/Int2IntMap;ILjava/lang/Object;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public fun equals (Ljava/lang/Object;)Z + public final fun getCharSize ()I + public final fun getCharSpacing ()I + public final fun getCharWidths ()Lit/unimi/dsi/fastutil/ints/Int2IntMap; + public final fun getSpaceAsShift ()Z + public final fun getSpaceShift ()Ljava/lang/Integer; + public final fun getUppercase ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics$Companion { + public final fun getSURF_MENU ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public final fun getVANILLA ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public final fun getVANILLA_ROW ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; +} + +public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry { + public static final field Companion Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry$Companion; + public static final field DEFAULT_ROW_TEXT_PADDING I + public static final field DEFAULT_SLOT_GLYPH_INSET I + public static final field DEFAULT_SLOT_ORIGIN_X I + public static final field DEFAULT_TITLE_LEFT_SHIFT I + public static final field DEFAULT_TITLE_PADDING I + public static final field DEFAULT_TITLE_WIDTH I + public fun ()V + public fun (IIIIIII)V + public synthetic fun (IIIIIIIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun columnShift (I)I + public final fun columnSpanOptions (Lkotlin/ranges/IntRange;Lit/unimi/dsi/fastutil/ints/Int2IntMap;II)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignmentOptions; + public static synthetic fun columnSpanOptions$default (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;Lkotlin/ranges/IntRange;Lit/unimi/dsi/fastutil/ints/Int2IntMap;IIILjava/lang/Object;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignmentOptions; + public final fun component1 ()I + public final fun component2 ()I + public final fun component3 ()I + public final fun component4 ()I + public final fun component5 ()I + public final fun component6 ()I + public final fun component7 ()I + public final fun copy (IIIIIII)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry; + public static synthetic fun copy$default (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;IIIIIIIILjava/lang/Object;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry; + public fun equals (Ljava/lang/Object;)Z + public final fun getRowTextPadding ()I + public final fun getSlotAreaWidth ()I + public final fun getSlotGlyphInset ()I + public final fun getSlotOriginX ()I + public final fun getSlotSize ()I + public final fun getTitleLeftShift ()I + public final fun getTitlePadding ()I + public final fun getTitleWidth ()I + public fun hashCode ()I + public final fun slotGlyphShift (I)I + public final fun titleOptions (Lit/unimi/dsi/fastutil/ints/Int2IntMap;II)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignmentOptions; + public static synthetic fun titleOptions$default (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;Lit/unimi/dsi/fastutil/ints/Int2IntMap;IIILjava/lang/Object;)Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignmentOptions; + public fun toString ()Ljava/lang/String; +} + +public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry$Companion { + public final fun getDEFAULT ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry; } public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/ViewRows : java/lang/Enum { @@ -1146,10 +1362,20 @@ public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/ public fun build ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginatedViewSettings; public synthetic fun build ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettings; public final fun getPaginationButtonPosition ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition; + public final fun getPaginationEmptyRows ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows; + public final fun getPaginationPageIndicator ()Ldev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator; + public final fun getPaginationSwitchSound ()Lnet/kyori/adventure/sound/Sound; public final fun getPaginationViewRows ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows; + public final fun noPaginationEmptyRows ()V + public final fun noPaginationPageIndicator ()V + public final fun noPaginationSwitchSound ()V public final fun paginationButtonPosition (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationButtonPosition;)V public final fun paginationButtonsAtBottom ()V public final fun paginationButtonsAtTop ()V + public final fun paginationEmptyRows (I)V + public final fun paginationEmptyRows (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows;)V + public final fun paginationPageIndicator (Ldev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator;)V + public final fun paginationSwitchSound (Lnet/kyori/adventure/sound/Sound;)V public final fun paginationViewRows (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows;)V } @@ -1171,6 +1397,8 @@ public final class dev/slne/surf/api/minestom/inventory/framework/view/settings/ } public abstract class dev/slne/surf/api/minestom/inventory/framework/view/settings/builder/SurfViewSettingsBuilder { + public final fun backgroundGlyph (Z)V + public static synthetic fun backgroundGlyph$default (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/builder/SurfViewSettingsBuilder;ZILjava/lang/Object;)V public abstract fun build ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettings; public final fun cancelAllInteractions ()V public final fun cancelOnClick (Z)V @@ -1182,16 +1410,29 @@ public abstract class dev/slne/surf/api/minestom/inventory/framework/view/settin public final fun cancelOnPickup (Z)V public static synthetic fun cancelOnPickup$default (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/builder/SurfViewSettingsBuilder;ZILjava/lang/Object;)V public final fun font (Lnet/kyori/adventure/key/Key;)V + public final fun getBackgroundGlyph ()Z public final fun getCancelOnClick ()Z public final fun getCancelOnDrag ()Z public final fun getCancelOnDrop ()Z public final fun getCancelOnPickup ()Z public final fun getFont ()Lnet/kyori/adventure/key/Key; + public final fun getHeaderFontMetrics ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public final fun getHeaderGeometry ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry; public final fun getHeaderTextAlignment ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment; + public final fun getHeaderTextColor ()Lnet/kyori/adventure/text/format/TextColor; public final fun getNavigateBackOnOutsideClick ()Z + public final fun getRowFontMetrics ()Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics; + public final fun getRowFonts ()Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; + public final fun headerFontMetrics (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;)V + public final fun headerGeometry (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry;)V + public final fun headerGeometry (Lkotlin/jvm/functions/Function1;)V public final fun headerTextAlignment (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/align/TextAlignment;)V + public final fun headerTextColor (Lnet/kyori/adventure/text/format/TextColor;)V public final fun navigateBackOnOutsideClick (Z)V public static synthetic fun navigateBackOnOutsideClick$default (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/builder/SurfViewSettingsBuilder;ZILjava/lang/Object;)V + public final fun rowFont (ILnet/kyori/adventure/key/Key;)V + public final fun rowFontMetrics (Ldev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics;)V + public final fun rowFonts (Lit/unimi/dsi/fastutil/ints/Int2ObjectMap;)V } public abstract interface class dev/slne/surf/api/minestom/inventory/framework/view/state/DeferredState { diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfView.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfView.kt index eb4014ffe..1ed9da031 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfView.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/AbstractSurfView.kt @@ -6,13 +6,15 @@ import dev.slne.surf.api.minestom.inventory.framework.view.container.component.c import dev.slne.surf.api.minestom.inventory.framework.view.container.component.components.ViewContainerTitleComponent import dev.slne.surf.api.minestom.inventory.framework.view.container.dsl.ViewContainerModificationContext import dev.slne.surf.api.minestom.inventory.framework.view.container.dsl.addChild -import dev.slne.surf.api.minestom.inventory.framework.view.container.dsl.backHint +import dev.slne.surf.api.minestom.inventory.framework.view.container.dsl.removeChildrenOfType import dev.slne.surf.api.minestom.inventory.framework.view.settings.SimpleViewSettings import dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettings +import dev.slne.surf.api.minestom.inventory.framework.view.settings.align.TextAlignment import me.devnatan.inventoryframework.View import me.devnatan.inventoryframework.ViewConfigBuilder import me.devnatan.inventoryframework.ViewType import me.devnatan.inventoryframework.context.* +import net.kyori.adventure.text.Component /** * Abstract base class for all Surf inventory views built on top of the inventory framework. @@ -24,10 +26,15 @@ import me.devnatan.inventoryframework.context.* * and settings (cancel behaviours, row count, type) are always applied before delegating * to the subclass hooks. * - * The view's title is composed from a [ViewContainer] that holds a glyph background component - * and an aligned title text component. The container can be modified at any time via + * The view's title is composed from a [ViewContainer] that holds the header components: the + * aligned title text plus whatever the view adds on top of it. The container can be modified at any time via * [modifyContainer]. * + * A title set on the view's config instead - `onInit { title(...) }`, or `modifyConfig { title(...) }` + * inside [onViewOpen] - is adopted as the container's header rather than replacing the rendered + * container, so the header texture, the alignment and every other header element survive it. From + * any other context use [updateHeader], which does the same. + * * You can directly create a subclass or use the dsl functions ([surfView] or [paginatedSurfView]) instead. * * ```kotlin @@ -136,26 +143,121 @@ abstract class AbstractSurfView( } } - private fun applyContainerDefaults(context: Context) { - modifyContainer(context) { - addChild(ViewContainerGlyphComponent(settings.rows)) - addChild( - ViewContainerTitleComponent( - title = defaultHeader, - font = settings.font, - charSpacing = ViewContainerTitleComponent.CHAR_SPACING, - textAlignment = settings.headerTextAlignment - ) + /** + * Replaces the header text of the view, keeping every other container component - the + * background glyph, row texts, the pagination overlay - in place. + * + * This is the API to change a view's title with: setting the title directly on the view's + * config (`modifyConfig { title(...) }`) would replace the whole rendered container, dropping + * the header texture along with the alignment shift, which leaves the text unaligned and every + * other header element gone. + * + * ```kotlin + * override fun onViewClick(click: SlotClickContext) { + * updateHeader(click, text("Seite 2", Colors.GOLD)) + * } + * ``` + * + * @param context the context used to propagate the title change to viewers + * @param header the title component to render + * @param alignment horizontal alignment of the title; defaults to the alignment the current + * title is rendered with, or to [SurfViewSettings.headerTextAlignment] if there is none + */ + protected fun updateHeader( + context: Context, + header: Component, + alignment: TextAlignment? = null, + ) = modifyContainer(context) { applyHeader(header, alignment) } + + /** + * Replaces the header text of the view with the plain, white string [header]. + * + * @param context the context used to propagate the title change to viewers + * @param header the plain-text title to render + * @param alignment horizontal alignment of the title; defaults to the alignment the current + * title is rendered with, or to [SurfViewSettings.headerTextAlignment] if there is none + * @see updateHeader + */ + protected fun updateHeader( + context: Context, + header: String, + alignment: TextAlignment? = null, + ) = updateHeader(context, Component.text(header), alignment) + + /** + * Swaps the container's [ViewContainerTitleComponent] for one rendering [header]. + * + * Only the title component is replaced, so every other component the container holds survives + * the title change. When [alignment] is `null` the alignment of the title currently rendered is + * kept, falling back to [SurfViewSettings.headerTextAlignment] if the container has no title + * yet. + */ + context(ctx: ViewContainerModificationContext) + internal fun applyHeader(header: Component, alignment: TextAlignment? = null) { + val currentAlignment = ctx.container.children + .filterIsInstance() + .firstOrNull() + ?.textAlignment + + removeChildrenOfType() + + addChild( + ViewContainerTitleComponent( + title = header, + font = settings.font, + textAlignment = alignment ?: currentAlignment ?: settings.headerTextAlignment, + geometry = settings.headerGeometry, + defaultColor = settings.headerTextColor, + metrics = settings.headerFontMetrics ) + ) + } - if (settings.navigateBackOnOutsideClick) { - backHint() + private fun applyContainerDefaults(context: OpenContext) { + // A title set on the config before the container exists - via `onInit { title(...) }` or by + // whoever opened the view - is the header the view was asked to show, so it wins over the + // one the view was constructed with. + val header = context.configuredTitle() ?: Component.text(defaultHeader) + + modifyContainer(context) { + if (settings.backgroundGlyph) { + addChild(ViewContainerGlyphComponent(settings.rows)) } + applyHeader(header) + containerDefaults() } } + /** + * Adopts a title that was set on the config from outside the container - `modifyConfig + * { title(...) }` inside [onViewOpen] being the usual case - as the container's header. + * + * Without this, that title would replace the rendered container as a whole: the header texture, + * the alignment shift and every row text would be gone, and the raw text would sit wherever the + * client happens to draw an inventory title. + */ + private fun adoptExternalHeader(open: OpenContext) { + val container = containerState.get(open) + val header = open.configuredTitle() ?: return + + // The container itself put its own render on the config, that one is not external. + if (header == container.lastRenderedTitle) return + + modifyContainer(open) { applyHeader(header) } + } + + /** + * Returns the title currently set on this context's config as a [Component], or `null` if no + * title is set. Non-component titles (e.g. plain strings) are wrapped as-is. + */ + private fun OpenContext.configuredTitle(): Component? = when (val title = config.title) { + null -> null + is Component -> title + else -> Component.text(title.toString()) + } + /** * Hook called within [applyContainerDefaults] to allow subclasses to add their own * container components after the standard glyph and title components have been added. @@ -184,6 +286,7 @@ abstract class AbstractSurfView( recordHistory(open) applyContainerDefaults(open) onViewOpen(open) + adoptExternalHeader(open) } private fun recordHistory(open: OpenContext) { diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/ViewContainer.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/ViewContainer.kt index f7707d6f7..12fc52a38 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/ViewContainer.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/ViewContainer.kt @@ -4,6 +4,7 @@ import dev.slne.surf.api.core.inventory.framework.internal.appendShiftedComponen import dev.slne.surf.api.core.messages.adventure.buildText import dev.slne.surf.api.minestom.inventory.framework.view.container.component.ViewContainerComponent import dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettingsDefaults +import net.kyori.adventure.text.Component import java.util.concurrent.CopyOnWriteArrayList /** @@ -28,6 +29,17 @@ internal class ViewContainer { val children: List field = CopyOnWriteArrayList() + /** + * The title [net.kyori.adventure.text.Component] the last [render] call produced, or `null` + * while the container has never been rendered. + * + * Kept so that a title arriving from outside the container - e.g. one set through + * `modifyConfig { title(...) }` - can be told apart from the one the container itself put on + * the view's config. + */ + var lastRenderedTitle: Component? = null + private set + fun addChild(component: ViewContainerComponent) { children.addIfAbsent(component) } @@ -48,11 +60,15 @@ internal class ViewContainer { children.removeIf { type.isInstance(it) } } + fun removeChildren(predicate: (ViewContainerComponent) -> Boolean) { + children.removeIf(predicate) + } + inline fun removeChildrenOfType() { removeChildrenOfType(T::class.java) } - fun render() = buildText { + fun render(): Component = buildText { // Components that only estimate their textureWidth leave the cursor slightly off the // container origin, so render them after every exact one instead of letting the following // components inherit that drift. partition() keeps the relative insertion order of both. @@ -67,5 +83,5 @@ internal class ViewContainer { } font(SurfViewSettingsDefaults.DEFAULT_MENU_FONT) - } + }.also { lastRenderedTitle = it } } \ No newline at end of file diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewBlockCellComponent.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewBlockCellComponent.kt index 7a16eb1ee..6714e83dd 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewBlockCellComponent.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewBlockCellComponent.kt @@ -1,31 +1,63 @@ package dev.slne.surf.api.minestom.inventory.framework.view.container.component.components +import dev.slne.surf.api.core.inventory.framework.internal.ViewSlotGeometry import dev.slne.surf.api.core.messages.Colors import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder import dev.slne.surf.api.core.util.freeze import dev.slne.surf.api.minestom.inventory.framework.view.container.component.ViewContainerComponent +import dev.slne.surf.api.minestom.inventory.framework.view.settings.ViewHeaderGeometry import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap /** * A [ViewContainerComponent] that renders a single inventory cell block overlay at the given * [column] and [row] position. * - * Each instance renders a 18×18 pixel glyph (plus 1 pixel spacing = 19 pixels wide) that - * visually "blocks" a specific cell in the inventory header texture. The glyph character is - * looked up from [BlockRow] based on the [row] parameter. + * Each instance renders one slot-sized glyph (plus 1 pixel spacing) that visually "blocks" a cell + * of the inventory. The horizontal position is computed from [ViewHeaderGeometry]; the row cannot + * be — a glyph's vertical position is baked into the `ascent` of its font provider in the resource + * pack — so the glyph character itself is picked per [row] from [BlockRow]. * - * Typically used via the DSL helper [dev.slne.surf.api.minestom.api.inventory.framework.view.container.dsl.blockCell] - * or [dev.slne.surf.api.minestom.api.inventory.framework.view.container.dsl.blockRow]. + * Blocking a cell is **purely cosmetic**: it draws a texture into the inventory title and touches + * neither the slot's contents nor its click handling, so items can still be placed into and taken + * out of a blocked slot exactly as before (subject to the view's usual + * [cancelOnClick][dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettings.cancelOnClick] + * settings). + * + * Typically used via the DSL helpers + * [blockCell][dev.slne.surf.api.minestom.inventory.framework.view.container.dsl.blockCell], + * [blockSlot][dev.slne.surf.api.minestom.inventory.framework.view.container.dsl.blockSlot], + * [blockRow][dev.slne.surf.api.minestom.inventory.framework.view.container.dsl.blockRow] or + * [blockAllSlots][dev.slne.surf.api.minestom.inventory.framework.view.container.dsl.blockAllSlots]. * * @property column the zero-based column index (0–8) of the cell to block * @property row the one-based row index (1–6) of the cell to block + * @param geometry the header [ViewHeaderGeometry] describing the slot grid */ class ViewBlockCellComponent( val column: Int, - val row: Int + val row: Int, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, ) : ViewContainerComponent { - override val textureWidth = 18 + 1 // 1 pixel for spacing - override val positionalShift = 1 + column * (textureWidth - 1) + + /** @suppress binary compatibility, blocks the cell with the default geometry */ + @Deprecated( + "Binary compatibility", + ReplaceWith("ViewBlockCellComponent(column, row)"), + DeprecationLevel.HIDDEN + ) + constructor(column: Int, row: Int) : this(column, row, ViewHeaderGeometry.DEFAULT) + + init { + require(column in 0 until ViewSlotGeometry.COLUMNS) { + "Column must be in 0..${ViewSlotGeometry.COLUMNS - 1}, was $column" + } + require(row in 1..ViewSlotGeometry.MAX_ROWS) { + "Row must be in 1..${ViewSlotGeometry.MAX_ROWS}, was $row" + } + } + + override val textureWidth = geometry.slotSize + 1 // 1 pixel for spacing + override val positionalShift = geometry.slotGlyphShift(column) override fun SurfComponentBuilder.renderComponent() { val blockRow = BlockRow.fromRow(row) ?: return @@ -49,10 +81,31 @@ class ViewBlockCellComponent( return result } + companion object { + /** + * Returns the component that blocks the cell at the zero-based [slot] index, counted + * left-to-right and then top-to-bottom just like the inventory's own slot numbering. + * + * @param slot the zero-based slot index (0–53) + * @param geometry the header [ViewHeaderGeometry] describing the slot grid + */ + fun forSlot( + slot: Int, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + ): ViewBlockCellComponent = ViewBlockCellComponent( + column = ViewSlotGeometry.columnOf(slot), + row = ViewSlotGeometry.rowOf(slot), + geometry = geometry + ) + } + /** * Maps a row number to its corresponding block-cell glyph character. * + * One glyph per row: the textures are identical, but each one is declared with the `ascent` + * that drops it onto its row, which is the only way to position a header texture vertically. + * * @property glyph the Unicode character in the resource-pack font that draws the cell overlay * @property row the one-based inventory row this glyph corresponds to (1–6) */ @@ -90,4 +143,4 @@ class ViewBlockCellComponent( fun fromRow(row: Int): BlockRow? = index[row] } } -} \ No newline at end of file +} diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerBackHintComponent.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerBackHintComponent.kt deleted file mode 100644 index a008388ab..000000000 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerBackHintComponent.kt +++ /dev/null @@ -1,23 +0,0 @@ -package dev.slne.surf.api.minestom.inventory.framework.view.container.component.components - -import dev.slne.surf.api.core.messages.Colors -import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder -import dev.slne.surf.api.minestom.inventory.framework.view.container.component.ViewContainerComponent - -/** - * A [ViewContainerComponent] that renders a "navigate back" hint glyph in the inventory header. - * - * This singleton component is added automatically when - * [SurfViewSettings.navigateBackOnOutsideClick][dev.slne.surf.api.minestom.api.inventory.framework.view.settings.SurfViewSettings.navigateBackOnOutsideClick] - * is `true`. It displays a small arrow icon at a fixed position (shift of −21 pixels, - * texture width 15 pixels) to hint to the player that clicking outside the inventory navigates back. - */ -data object ViewContainerBackHintComponent : ViewContainerComponent { - override val positionalShift = -22 - override val textureWidth = 15 + 2 // 2 pixel for spacing - - override fun SurfComponentBuilder.renderComponent() { - text("ꐷ") - color(Colors.WHITE) - } -} \ No newline at end of file diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerGlyphComponent.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerGlyphComponent.kt index 6d22831f6..ef8cb3058 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerGlyphComponent.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerGlyphComponent.kt @@ -9,15 +9,16 @@ import dev.slne.surf.api.minestom.inventory.framework.view.settings.ViewRows * A [ViewContainerComponent] that renders the inventory background glyph for the given [rows]. * * Each [ViewRows] value has a corresponding special-font character that renders the correct - * background texture for that row count. The glyph is positioned with a fixed negative shift - * of 48 pixels (moving the cursor to the left edge of the texture area) and has a texture - * width of 223 pixels. + * background texture for that row count. The glyph is positioned with a fixed negative shift of 48 + * pixels (moving the cursor to the left edge of the texture area) and advances the cursor by 222 + * pixels - the advance measured off the glyph sheet, which has to match exactly or every component + * rendered afterwards is shifted. * * @property rows the [ViewRows] value whose glyph character should be rendered */ internal class ViewContainerGlyphComponent(val rows: ViewRows) : ViewContainerComponent { override val positionalShift = -48 - override val textureWidth = 223 + override val textureWidth = 222 override fun SurfComponentBuilder.renderComponent() { text(rows.glyph) diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerRowTextComponent.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerRowTextComponent.kt new file mode 100644 index 000000000..450d306be --- /dev/null +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerRowTextComponent.kt @@ -0,0 +1,142 @@ +package dev.slne.surf.api.minestom.inventory.framework.view.container.component.components + +import dev.slne.surf.api.core.inventory.framework.internal.ViewSlotGeometry +import dev.slne.surf.api.core.inventory.framework.internal.layoutViewText +import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder +import dev.slne.surf.api.minestom.inventory.framework.view.container.component.ViewContainerComponent +import dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettingsDefaults +import dev.slne.surf.api.minestom.inventory.framework.view.settings.ViewFontMetrics +import dev.slne.surf.api.minestom.inventory.framework.view.settings.ViewHeaderGeometry +import dev.slne.surf.api.minestom.inventory.framework.view.settings.align.TextAlignment +import net.kyori.adventure.key.Key +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor + +/** + * A [ViewContainerComponent] that renders a line of text inside a slot [row] of the inventory, + * vertically centred in the row. + * + * ### How a row is addressed + * + * The header is drawn as glyphs inside the inventory title, and a glyph's vertical position is + * baked into the `ascent` of its font provider in the resource pack — the server cannot move it + * down. The **row** therefore comes from the [font]: one font per slot row, each declaring the + * `ascent` that centres its glyphs vertically in that row (`surf:menu_font_row_1` … + * `surf:menu_font_row_6` by default, see [SurfViewSettingsDefaults.rowFont]). Only the + * **horizontal** position is computed here, from [alignment] within the span of slot [columns]. + * + * ### Styling and metrics + * + * [text] is a full Adventure [Component], so colours, gradients, decorations and hover events all + * survive; parts without a colour are rendered in [defaultColor]. Because the text is positioned by + * shifting the render cursor, its width has to be known up front, and that width comes from + * [metrics] — which also applies the spacing, the casing and the space handling that font needs. + * [metrics] therefore has to describe the font passed as [font]; the default + * [SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS] describes the row fonts of the resource pack, + * which copy the client font glyph sheets onto their row. + * + * ### Multiple texts per row + * + * Components are equal when they render the same component with the same font, alignment and + * column span, so a row can carry several independent texts — for example one aligned left over the + * first three columns and one aligned right over the last three. + * + * @param row the one-based slot row the text is rendered in (`1`–`6`); used for identity and + * validation, the actual vertical position comes from [font] + * @param text the component to render + * @param font the row font whose `ascent` centres the text in [row] + * @param alignment horizontal [TextAlignment] within the [columns] span + * @param columns the zero-based, inclusive span of slot columns the text is aligned in + * @param geometry the header [ViewHeaderGeometry] describing the slot grid + * @param defaultColor the colour applied to parts of [text] that do not set one + * @param metrics the [ViewFontMetrics] of [font] + */ +class ViewContainerRowTextComponent( + val row: Int, + val text: Component, + private val font: Key, + val alignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_ALIGNMENT, + val columns: IntRange = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_COLUMNS, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + private val defaultColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + metrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS, +) : ViewContainerComponent { + + /** + * Renders the plain string [text] in [row]. + * + * @param text the plain text to render + */ + constructor( + row: Int, + text: String, + font: Key, + alignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_ALIGNMENT, + columns: IntRange = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_COLUMNS, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + defaultColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + metrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS, + ) : this(row, Component.text(text), font, alignment, columns, geometry, defaultColor, metrics) + + init { + require(row in 1..ViewSlotGeometry.MAX_ROWS) { + "Row must be in 1..${ViewSlotGeometry.MAX_ROWS}, was $row" + } + require(!columns.isEmpty()) { "A column span must cover at least one column" } + require(columns.first >= 0 && columns.last < ViewSlotGeometry.COLUMNS) { + "Columns must be in 0..${ViewSlotGeometry.COLUMNS - 1}, was $columns" + } + } + + private val laidOut = layoutViewText( + component = text, + charSpacing = metrics.charSpacing, + uppercase = metrics.uppercase, + spaceShift = metrics.spaceShift, + advance = metrics::advance + ) + + override val textureWidth = laidOut.width + + override val positionalShift = alignment.calculateShift( + textureWidth, + geometry.columnSpanOptions(columns, metrics.charWidths, metrics.charSize, metrics.charSpacing) + ) + + /** + * `false`: the width is derived from a font-metrics table, so a glyph the client renders at an + * unexpected width would make it an estimate. Keeps the text from displacing the header + * textures — see [ViewContainerComponent.hasExactWidth]. + */ + override val hasExactWidth = false + + override fun SurfComponentBuilder.renderComponent() { + append(laidOut.component) + font(font) + colorIfAbsent(defaultColor) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ViewContainerRowTextComponent + + if (row != other.row) return false + if (text != other.text) return false + if (font != other.font) return false + if (alignment != other.alignment) return false + if (columns != other.columns) return false + + return true + } + + override fun hashCode(): Int { + var result = row + result = 31 * result + text.hashCode() + result = 31 * result + font.hashCode() + result = 31 * result + alignment.hashCode() + result = 31 * result + columns.hashCode() + return result + } +} diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerTitleComponent.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerTitleComponent.kt index 05c54b8d9..987c657ef 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerTitleComponent.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/component/components/ViewContainerTitleComponent.kt @@ -1,80 +1,86 @@ package dev.slne.surf.api.minestom.inventory.framework.view.container.component.components -import dev.slne.surf.api.core.inventory.framework.internal.renderViewTitle -import dev.slne.surf.api.core.messages.Colors +import dev.slne.surf.api.core.inventory.framework.internal.layoutViewText import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder import dev.slne.surf.api.minestom.inventory.framework.view.container.component.ViewContainerComponent +import dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettingsDefaults +import dev.slne.surf.api.minestom.inventory.framework.view.settings.ViewFontMetrics +import dev.slne.surf.api.minestom.inventory.framework.view.settings.ViewHeaderGeometry import dev.slne.surf.api.minestom.inventory.framework.view.settings.align.TextAlignment -import dev.slne.surf.api.minestom.inventory.framework.view.settings.align.TextAlignmentOptions -import it.unimi.dsi.fastutil.ints.Int2IntMap -import it.unimi.dsi.fastutil.ints.Int2IntMaps import net.kyori.adventure.key.Key +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor /** - * A [ViewContainerComponent] that renders the inventory title text using a custom font. + * A [ViewContainerComponent] that renders the inventory title at the top of the header. * - * The title string is converted to uppercase and inter-character spacing glyphs are inserted - * between each letter using [renderViewTitle]. That same call reports the exact pixel width of the - * string it produced, which is used both to position the text according to [textAlignment] and to - * reset the render cursor afterwards — measuring the raw title instead would drift as soon as - * uppercasing changes the glyph count (`ß` uppercases to `SS`) or the leading spacing glyph is - * emitted. + * The [title] component keeps its colours, decorations and hover events; parts without a colour are + * rendered in [defaultColor]. It is positioned by [textAlignment] across the full width of the + * inventory as described by [geometry], which for the default [TextAlignment.CENTER] means centred + * above the slot grid. * - * The component uses constants from its companion object to define the geometry of the - * container area: - * - [LEFT_SHIFT]: the base pixel offset from the left edge of the container - * - [PADDING]: horizontal padding on each side - * - [CONTAINER_WIDTH]: the usable pixel width of the title area - * - [CHAR_SIZE]: the pixel width of a single character in the title font - * - [CHAR_SPACING]: the inter-character spacing (negative = tighter) + * Since the title is positioned by shifting the render cursor, its width has to be known up front, + * and that width comes from [metrics]: the proportional widths of the client font by default, or the + * cell width, spacing and casing of a fixed-width resource-pack font. [metrics] is also what applies + * that spacing and casing to the text, so it has to describe the font passed as [font]. * - * @param title the plain-text inventory title to render - * @param font the Adventure [Key] identifying the resource-pack font to use - * @param charSpacing the pixel spacing to insert between each character + * @param title the inventory title, rendered as-is apart from what [metrics] transforms + * @param font the Adventure [Key] of the font to render the title in; defaults to the vanilla + * font via [SurfViewSettingsDefaults.DEFAULT_HEADER_FONT] * @param textAlignment the [TextAlignment] controlling horizontal positioning - * @param charWidths per-code-point pixel width overrides for glyphs the [font] does not render at - * [CHAR_SIZE] pixels; see [TextAlignmentOptions.charWidths] + * @param geometry the header [ViewHeaderGeometry] the title is aligned in + * @param defaultColor the colour applied to parts of [title] that do not set one + * @param metrics the [ViewFontMetrics] of [font] */ class ViewContainerTitleComponent( - title: String, - private val font: Key, - charSpacing: Int, - textAlignment: TextAlignment, - charWidths: Int2IntMap = Int2IntMaps.EMPTY_MAP + val title: Component, + private val font: Key = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT, + val textAlignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_HEADER_ALIGNMENT, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + private val defaultColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + metrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT_METRICS, ) : ViewContainerComponent { - private val alignmentOptions = TextAlignmentOptions( - leftShift = LEFT_SHIFT, - padding = PADDING, - containerWidth = CONTAINER_WIDTH, - charSize = CHAR_SIZE, - charSpacing = charSpacing, - charWidths = charWidths - ) - private val rendered = renderViewTitle( - title = title, - charSize = CHAR_SIZE, - charSpacing = charSpacing, - alignRight = textAlignment == TextAlignment.RIGHT, - charWidths = charWidths + /** + * Renders the plain string [title] as the inventory title. + * + * @param title the plain-text inventory title + */ + constructor( + title: String, + font: Key = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT, + textAlignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_HEADER_ALIGNMENT, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + defaultColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + metrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT_METRICS, + ) : this(Component.text(title), font, textAlignment, geometry, defaultColor, metrics) + + private val laidOut = layoutViewText( + component = title, + charSpacing = metrics.charSpacing, + uppercase = metrics.uppercase, + spaceShift = metrics.spaceShift, + advance = metrics::advance ) - private val formattedTitle = rendered.text + override val textureWidth = laidOut.width - override val textureWidth = rendered.width - override val positionalShift = textAlignment.calculateShift(rendered.width, alignmentOptions) + override val positionalShift = textAlignment.calculateShift( + textureWidth, + geometry.titleOptions(metrics.charWidths, metrics.charSize, metrics.charSpacing) + ) /** - * `false`: the width is derived from the configured font metrics, so a glyph the font renders - * at an unexpected width would make it an estimate. Keeps the title from displacing the header + * `false`: the width is derived from a font-metrics table, so a glyph the client renders at an + * unexpected width would make it an estimate. Keeps the title from displacing the header * textures — see [ViewContainerComponent.hasExactWidth]. */ override val hasExactWidth = false override fun SurfComponentBuilder.renderComponent() { - text(formattedTitle) + append(laidOut.component) font(font) - color(Colors.WHITE) + colorIfAbsent(defaultColor) } override fun equals(other: Any?): Boolean { @@ -83,28 +89,17 @@ class ViewContainerTitleComponent( other as ViewContainerTitleComponent - return formattedTitle == other.formattedTitle - } + if (title != other.title) return false + if (font != other.font) return false + if (textAlignment != other.textAlignment) return false - override fun hashCode(): Int { - return formattedTitle.hashCode() + return true } - - companion object { - /** The base pixel offset from the left edge of the container area. */ - const val LEFT_SHIFT = 31 - - /** Horizontal padding on each side of the title text within the container. */ - const val PADDING = 2 - - /** Total usable pixel width of the title container area. */ - const val CONTAINER_WIDTH = 100 - - /** Pixel width of a single uppercase character in the title font. */ - const val CHAR_SIZE = 9 - - /** Default inter-character spacing (negative = tighter). */ - const val CHAR_SPACING = -1 + override fun hashCode(): Int { + var result = title.hashCode() + result = 31 * result + font.hashCode() + result = 31 * result + textAlignment.hashCode() + return result } } diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerDSL.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerDSL.kt index ad6874bfe..aa87e4c57 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerDSL.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/container/dsl/ViewContainerDSL.kt @@ -1,12 +1,22 @@ package dev.slne.surf.api.minestom.inventory.framework.view.container.dsl +import dev.slne.surf.api.core.inventory.framework.internal.ViewSlotGeometry +import dev.slne.surf.api.core.messages.adventure.buildText +import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder import dev.slne.surf.api.minestom.inventory.framework.view.AbstractSurfViewRef import dev.slne.surf.api.minestom.inventory.framework.view.container.component.ViewContainerComponent import dev.slne.surf.api.minestom.inventory.framework.view.container.component.components.ViewBlockCellComponent -import dev.slne.surf.api.minestom.inventory.framework.view.container.component.components.ViewContainerBackHintComponent -import dev.slne.surf.api.minestom.inventory.framework.view.container.component.components.ViewContainerTitleComponent +import dev.slne.surf.api.minestom.inventory.framework.view.container.component.components.ViewContainerRowTextComponent +import dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettingsDefaults +import dev.slne.surf.api.minestom.inventory.framework.view.settings.ViewFontMetrics +import dev.slne.surf.api.minestom.inventory.framework.view.settings.ViewHeaderGeometry +import dev.slne.surf.api.minestom.inventory.framework.view.settings.ViewRows +import dev.slne.surf.api.minestom.inventory.framework.view.settings.align.TextAlignment import it.unimi.dsi.fastutil.ints.IntCollection import it.unimi.dsi.fastutil.ints.IntLists +import net.kyori.adventure.key.Key +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor /** * Adds a [ViewContainerComponent] to the container. @@ -69,21 +79,355 @@ inline fun removeChildrenOfType() { context.container.removeChildrenOfType() } +/** + * Sets the inventory title, replacing the one currently rendered. + * + * The title is rendered as-is in the font configured via + * [SurfViewSettings.font][dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettings.font] + * - the client's own font by default - and positioned across the full width of the header texture, + * which for the default [TextAlignment.CENTER] means centred above the slot grid. Colours and + * decorations on [header] are kept; parts without a colour render white. + * + * ```kotlin + * containerDefaults { + * header(text("Spieler-Statistiken", Colors.GOLD)) + * } + * ``` + * + * @param header the title component to render + * @param alignment horizontal alignment of the title; defaults to the alignment the title + * currently rendered uses, or to the view's + * [headerTextAlignment][dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettings.headerTextAlignment] + * if the container has no title yet + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun header( + header: Component, + alignment: TextAlignment? = null, +) { + ref.getRegisteredView().applyHeader(header, alignment) +} + +/** + * Sets the inventory title to the plain, white string [header]. + * + * ```kotlin + * containerDefaults { + * header("Spieler-Statistiken") + * } + * ``` + * + * @param header the plain-text title to render + * @param alignment horizontal alignment of the title; defaults to the alignment the title + * currently rendered uses, or to the view's + * [headerTextAlignment][dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettings.headerTextAlignment] + * if the container has no title yet + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun header( + header: String, + alignment: TextAlignment? = null, +) { + header(Component.text(header), alignment) +} + +/** + * Sets the inventory title, building the component with a [SurfComponentBuilder] block. + * + * ```kotlin + * containerDefaults { + * header { + * text("Shop ", Colors.GOLD) + * text("(Seite 1)", Colors.GRAY) + * } + * } + * ``` + * + * @param alignment horizontal alignment of the title; defaults to the alignment the title + * currently rendered uses, or to the view's + * [headerTextAlignment][dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettings.headerTextAlignment] + * if the container has no title yet + * @param block builds the title component + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun header( + alignment: TextAlignment? = null, + block: SurfComponentBuilder.() -> Unit, +) { + header(buildText(block), alignment) +} + +/** + * Renders a line of [text] inside the slot [row], vertically centred in the row. + * + * The row comes from the font: the text is drawn in the view's + * [rowFont][dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettings.rowFont] + * for [row], whose `ascent` in the resource pack centres it in that row - the server can only + * position header glyphs horizontally. That horizontal position is computed from [alignment] within + * the span of slot [columns], so a row can carry several independent texts. + * + * [text] is a full component: colours, gradients, decorations and hover events are kept, and parts + * without a colour render white. + * + * ```kotlin + * containerDefaults { + * rowText(1, text("Deine Statistiken", Colors.GOLD)) + * rowText(3, text("Seite 1/4"), TextAlignment.RIGHT) + * rowText(5, text("Kategorie"), TextAlignment.LEFT, columns = 0..2) + * } + * ``` + * + * @param row the one-based slot row to render in (1-6) + * @param text the component to render, emitted as-is + * @param alignment horizontal alignment within the [columns] span + * @param columns the zero-based, inclusive span of slot columns to align in + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun rowText( + @ViewRows.Companion.Rows row: Int, + text: Component, + alignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_ALIGNMENT, + columns: IntRange = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_COLUMNS, +) { + val settings = ref.getRegisteredView().settings + require(row <= settings.rows.rows) { + "Row $row is outside of this view, which only has ${settings.rows.rows} rows" + } + + rowText( + row = row, + text = text, + font = settings.rowFont(row), + alignment = alignment, + columns = columns, + geometry = settings.headerGeometry, + defaultColor = settings.headerTextColor, + metrics = settings.rowFontMetrics + ) +} + +/** + * Renders the plain, white string [text] inside the slot [row], vertically centred in the row. + * + * @param row the one-based slot row to render in (1-6) + * @param text the plain text to render, emitted as-is + * @param alignment horizontal alignment within the [columns] span + * @param columns the zero-based, inclusive span of slot columns to align in + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun rowText( + @ViewRows.Companion.Rows row: Int, + text: String, + alignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_ALIGNMENT, + columns: IntRange = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_COLUMNS, +) { + rowText(row, Component.text(text), alignment, columns) +} + +/** + * Renders a line of text inside the slot [row], building the component with a + * [SurfComponentBuilder] block. + * + * ```kotlin + * containerDefaults { + * rowText(2, TextAlignment.LEFT, columns = 0..3) { + * text("Guthaben: ", Colors.GRAY) + * text("1.250", Colors.GREEN) + * } + * } + * ``` + * + * @param row the one-based slot row to render in (1-6) + * @param alignment horizontal alignment within the [columns] span + * @param columns the zero-based, inclusive span of slot columns to align in + * @param block builds the component to render + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun rowText( + @ViewRows.Companion.Rows row: Int, + alignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_ALIGNMENT, + columns: IntRange = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_COLUMNS, + block: SurfComponentBuilder.() -> Unit, +) { + rowText(row, buildText(block), alignment, columns) +} + +/** + * Renders a line of [text] inside the slot [row] using an explicitly given row [font]. + * + * Use this overload outside of a view's DSL scope, where the view's settings are not available as a + * context receiver, or to render a row of text in a font other than the view's configured + * [rowFont][dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettings.rowFont]. + * + * @param row the one-based slot row to render in (1-6) + * @param text the component to render, emitted as-is + * @param font the font whose `ascent` centres the text in [row] + * @param alignment horizontal alignment within the [columns] span + * @param columns the zero-based, inclusive span of slot columns to align in + * @param geometry the header geometry describing the slot grid + * @param defaultColor the colour applied to parts of [text] that do not set one + * @param metrics the glyph metrics of [font] + */ +context(context: ViewContainerModificationContext) +fun rowText( + @ViewRows.Companion.Rows row: Int, + text: Component, + font: Key, + alignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_ALIGNMENT, + columns: IntRange = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_COLUMNS, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, + defaultColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + metrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS, +) { + addChild( + ViewContainerRowTextComponent( + row = row, + text = text, + font = font, + alignment = alignment, + columns = columns, + geometry = geometry, + defaultColor = defaultColor, + metrics = metrics + ) + ) +} + +/** + * Renders the plain, white string [text] inside the slot [row] using an explicitly given row [font]. + * + * @param row the one-based slot row to render in (1-6) + * @param text the plain text to render, emitted as-is + * @param font the font whose `ascent` centres the text in [row] + * @param alignment horizontal alignment within the [columns] span + * @param columns the zero-based, inclusive span of slot columns to align in + * @param geometry the header geometry describing the slot grid + * @param defaultColor the colour applied to parts of [text] that do not set one + * @param metrics the glyph metrics of [font] + */ +context(context: ViewContainerModificationContext) +fun rowText( + @ViewRows.Companion.Rows row: Int, + text: String, + font: Key, + alignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_ALIGNMENT, + columns: IntRange = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_COLUMNS, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, + defaultColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + metrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS, +) { + rowText(row, Component.text(text), font, alignment, columns, geometry, defaultColor, metrics) +} + +/** + * Renders [text] in the slot [row], starting at the left edge of [column]. + * + * Shorthand for a [rowText] left-aligned in the span from [column] to the last column. + * + * ```kotlin + * containerDefaults { + * rowTextAt(2, 4, text("hier ->")) // starts above the middle slot of row 2 + * } + * ``` + * + * @param row the one-based slot row to render in (1-6) + * @param column the zero-based slot column the text starts at (0-8) + * @param text the component to render, emitted as-is + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun rowTextAt( + @ViewRows.Companion.Rows row: Int, + column: Int, + text: Component, +) { + rowText( + row = row, + text = text, + alignment = TextAlignment.LEFT, + columns = column until ViewSlotGeometry.COLUMNS + ) +} + +/** + * Renders the plain, white string [text] in the slot [row], starting at the left edge of [column]. + * + * @param row the one-based slot row to render in (1-6) + * @param column the zero-based slot column the text starts at (0-8) + * @param text the plain text to render, emitted as-is + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun rowTextAt( + @ViewRows.Companion.Rows row: Int, + column: Int, + text: String, +) { + rowTextAt(row, column, Component.text(text)) +} + +/** + * Removes every text rendered on the slot [row]. + * + * @param row the one-based slot row to clear (1-6) + */ +context(context: ViewContainerModificationContext) +fun clearRowText(@ViewRows.Companion.Rows row: Int) { + context.container.removeChildren { it is ViewContainerRowTextComponent && it.row == row } +} + +/** + * Removes every text rendered on any slot row, leaving the inventory title itself untouched. + */ +context(context: ViewContainerModificationContext) +fun clearRowTexts() { + removeChildrenOfType() +} + /** * Adds a [ViewBlockCellComponent] that visually blocks the cell at [column], [row]. * + * Blocking is **cosmetic only**: it draws a texture over the cell and changes nothing about the + * slot itself, so items can still be placed into and taken out of a blocked slot exactly as before. + * * ```kotlin * containerDefaults { * blockCell(0, 1) // block column 0, row 1 * } * ``` * - * @param column the zero-based column index (0–8) - * @param row the one-based row index (1–6) + * @param column the zero-based column index (0-8) + * @param row the one-based row index (1-6) + * @param geometry the header geometry describing the slot grid; pass the view's + * [headerGeometry][dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettings.headerGeometry] + * when it deviates from the default */ context(context: ViewContainerModificationContext) -fun blockCell(column: Int, row: Int) { - addChild(ViewBlockCellComponent(column, row)) +fun blockCell( + column: Int, + @ViewRows.Companion.Rows row: Int, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + addChild(ViewBlockCellComponent(column, row, geometry)) +} + +/** + * Blocks the cell at the zero-based [slot] index, counted left-to-right and then top-to-bottom just + * like the inventory's own slot numbering. + * + * ```kotlin + * containerDefaults { + * blockSlot(13) // centre slot of row 2 + * } + * ``` + * + * @param slot the zero-based slot index (0-53) + * @param geometry the header geometry describing the slot grid + */ +context(context: ViewContainerModificationContext) +fun blockSlot( + slot: Int, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + addChild(ViewBlockCellComponent.forSlot(slot, geometry)) } /** @@ -95,14 +439,19 @@ fun blockCell(column: Int, row: Int) { * } * ``` * - * @param row the one-based row index (1–6) + * @param row the one-based row index (1-6) * @param exemptColumns zero-based column indices to leave unblocked + * @param geometry the header geometry describing the slot grid */ context(context: ViewContainerModificationContext) -fun blockRow(row: Int, exemptColumns: IntArray) { - for (x in 0 until 9) { +fun blockRow( + @ViewRows.Companion.Rows row: Int, + exemptColumns: IntArray, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + for (x in 0 until ViewSlotGeometry.COLUMNS) { if (x in exemptColumns) continue - blockCell(x, row) + blockCell(x, row, geometry) } } @@ -115,79 +464,229 @@ fun blockRow(row: Int, exemptColumns: IntArray) { * } * ``` * - * @param row the one-based row index (1–6) + * @param row the one-based row index (1-6) * @param exemptColumns zero-based column indices to leave unblocked; defaults to none + * @param geometry the header geometry describing the slot grid */ context(context: ViewContainerModificationContext) -fun blockRow(row: Int, exemptColumns: IntCollection = IntLists.EMPTY_LIST) { - for (x in 0 until 9) { +fun blockRow( + @ViewRows.Companion.Rows row: Int, + exemptColumns: IntCollection = IntLists.EMPTY_LIST, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + for (x in 0 until ViewSlotGeometry.COLUMNS) { if (exemptColumns.contains(x)) continue - blockCell(x, row) + blockCell(x, row, geometry) } } /** * Blocks all cells in the given [column], skipping rows listed in [exemptRows]. * - * @param column the zero-based column index (0–8) + * @param column the zero-based column index (0-8) * @param exemptRows one-based row indices to leave unblocked + * @param rows how many rows the view has; cells below it have no glyph to render + * @param geometry the header geometry describing the slot grid */ context(context: ViewContainerModificationContext) -fun blockColumn(column: Int, exemptRows: IntArray) { - for (row in 1..6) { +fun blockColumn( + column: Int, + exemptRows: IntArray, + @ViewRows.Companion.Rows rows: Int = ViewSlotGeometry.MAX_ROWS, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + for (row in 1..rows) { if (row in exemptRows) continue - blockCell(column, row) + blockCell(column, row, geometry) } } /** * Blocks all cells in the given [column], skipping rows in [exemptRows]. * - * @param column the zero-based column index (0–8) + * @param column the zero-based column index (0-8) * @param exemptRows one-based row indices to leave unblocked; defaults to none + * @param rows how many rows the view has; cells below it have no glyph to render + * @param geometry the header geometry describing the slot grid */ context(context: ViewContainerModificationContext) -fun blockColumn(column: Int, exemptRows: IntCollection = IntLists.EMPTY_LIST) { - for (row in 1..6) { +fun blockColumn( + column: Int, + exemptRows: IntCollection = IntLists.EMPTY_LIST, + @ViewRows.Companion.Rows rows: Int = ViewSlotGeometry.MAX_ROWS, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + for (row in 1..rows) { if (exemptRows.contains(row)) continue - blockCell(column, row) + blockCell(column, row, geometry) } } /** - * Adds a [ViewContainerBackHintComponent] to the container. + * Blocks every slot of a view with [rows] rows, skipping the slots listed in [exemptSlots]. * - * This renders a small navigation arrow in the inventory header, indicating to the player - * that clicking outside the inventory will navigate back to the previous view. + * ```kotlin + * containerDefaults { + * // every slot of a 5-row view is blocked, except the one in the middle + * blockAllSlots(ViewRows.FIVE, intSetOf(22)) + * } + * ``` * - * Added automatically when [SurfViewSettings.navigateBackOnOutsideClick][dev.slne.surf.api.minestom.api.inventory.framework.view.settings.SurfViewSettings.navigateBackOnOutsideClick] - * is `true`. + * @param rows how many rows the view has + * @param exemptSlots zero-based slot indices to leave unblocked; defaults to none + * @param geometry the header geometry describing the slot grid */ context(context: ViewContainerModificationContext) -fun backHint() { - addChild(ViewContainerBackHintComponent) +fun blockAllSlots( + rows: ViewRows, + exemptSlots: IntCollection = IntLists.EMPTY_LIST, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + blockAllSlots(rows.rows, exemptSlots, geometry) +} + +/** + * Blocks every slot of a view with [rows] rows, skipping the slots listed in [exemptSlots]. + * + * @param rows how many rows the view has (1-6) + * @param exemptSlots zero-based slot indices to leave unblocked; defaults to none + * @param geometry the header geometry describing the slot grid + */ +context(context: ViewContainerModificationContext) +fun blockAllSlots( + @ViewRows.Companion.Rows rows: Int, + exemptSlots: IntCollection = IntLists.EMPTY_LIST, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + for (slot in 0 until rows * ViewSlotGeometry.COLUMNS) { + if (exemptSlots.contains(slot)) continue + blockSlot(slot, geometry) + } } /** - * Sets a title header for the [ViewContainer][dev.slne.surf.api.minestom.api.inventory.framework.view.container.ViewContainer] by removing any existing title component - * and adding a new `ViewContainerTitleComponent` with the specified header text. + * Removes the block overlay from the cell at [column], [row], if there is one. * - * @param header The text to set as the title for the container. - * @param context Provides access to the container modification API for the current view. - * @param ref A deferred reference to the associated view, providing configuration details - * such as font and text alignment settings. + * @param column the zero-based column index (0-8) + * @param row the one-based row index (1-6) */ +context(context: ViewContainerModificationContext) +fun unblockCell(column: Int, @ViewRows.Companion.Rows row: Int) { + removeChild(ViewBlockCellComponent(column, row)) +} + +/** + * Removes the block overlay from the cell at the zero-based [slot] index, if there is one. + * + * @param slot the zero-based slot index (0-53) + */ +context(context: ViewContainerModificationContext) +fun unblockSlot(slot: Int) { + removeChild(ViewBlockCellComponent.forSlot(slot)) +} + +/** + * Removes every block overlay from the container. + */ +context(context: ViewContainerModificationContext) +fun unblockAllSlots() { + removeChildrenOfType() +} + +// ------------------------------------------------------------------------------------------------- +// Binary compatibility +// +// The container DSL gained a `geometry` parameter - and `blockColumn` additionally a `rows` +// parameter - which changed the JVM signatures of the functions below, so plugins compiled against +// an older surf-api fail with a `NoSuchMethodError` at runtime. The shims keep the old signatures +// alive and delegate to the current functions with the parameters those callers never passed. +// +// They are hidden from Kotlin callers, so new code always resolves to the functions above. Remove +// them once every consumer has been rebuilt against this version. +// ------------------------------------------------------------------------------------------------- + +/** @suppress */ +@Deprecated("Binary compatibility", ReplaceWith("blockCell(column, row)"), DeprecationLevel.HIDDEN) +context(context: ViewContainerModificationContext) +fun blockCell(column: Int, @ViewRows.Companion.Rows row: Int) { + blockCell(column, row, SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY) +} + +/** @suppress */ +@Deprecated( + "Binary compatibility", + ReplaceWith("blockRow(row, exemptColumns)"), + DeprecationLevel.HIDDEN +) +context(context: ViewContainerModificationContext) +fun blockRow(@ViewRows.Companion.Rows row: Int, exemptColumns: IntArray) { + blockRow(row, exemptColumns, SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY) +} + +/** @suppress */ +@Deprecated( + "Binary compatibility", + ReplaceWith("blockRow(row, exemptColumns)"), + DeprecationLevel.HIDDEN +) +context(context: ViewContainerModificationContext) +fun blockRow( + @ViewRows.Companion.Rows row: Int, + exemptColumns: IntCollection = IntLists.EMPTY_LIST, +) { + blockRow(row, exemptColumns, SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY) +} + +/** @suppress */ +@Deprecated( + "Binary compatibility", + ReplaceWith("blockColumn(column, exemptRows)"), + DeprecationLevel.HIDDEN +) +context(context: ViewContainerModificationContext) +fun blockColumn(column: Int, exemptRows: IntArray) { + blockColumn( + column = column, + exemptRows = exemptRows, + rows = ViewSlotGeometry.MAX_ROWS, + geometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY + ) +} + +/** @suppress */ +@Deprecated( + "Binary compatibility", + ReplaceWith("blockColumn(column, exemptRows)"), + DeprecationLevel.HIDDEN +) +context(context: ViewContainerModificationContext) +fun blockColumn(column: Int, exemptRows: IntCollection = IntLists.EMPTY_LIST) { + blockColumn( + column = column, + exemptRows = exemptRows, + rows = ViewSlotGeometry.MAX_ROWS, + geometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY + ) +} + +/** @suppress */ +@Deprecated("Binary compatibility", ReplaceWith("header(header)"), DeprecationLevel.HIDDEN) context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) fun header(header: String) { - removeChildrenOfType() + header(header, null) +} - val settings = ref.getRegisteredView().settings - addChild( - ViewContainerTitleComponent( - header, - settings.font, - ViewContainerTitleComponent.CHAR_SPACING, - settings.headerTextAlignment - ) - ) -} \ No newline at end of file +/** + * No-op that keeps plugins compiled against the removed back-hint component loading. + * + * The navigation arrow the old `ViewContainerBackHintComponent` drew is part of the inventory + * texture now; whether an outside click navigates back is decided solely by + * [navigateBackOnOutsideClick][dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettings.navigateBackOnOutsideClick]. + * + * @suppress + */ +@Deprecated("The back hint is part of the inventory texture now", level = DeprecationLevel.HIDDEN) +context(context: ViewContainerModificationContext) +fun backHint() { + // nothing to render anymore +} diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/pagination/AbstractPaginatedSurfView.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/pagination/AbstractPaginatedSurfView.kt index 8106f9cc6..869c1db85 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/pagination/AbstractPaginatedSurfView.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/pagination/AbstractPaginatedSurfView.kt @@ -29,6 +29,9 @@ import java.util.concurrent.atomic.AtomicBoolean * - Left/right navigation buttons in the designated button row. * - A [PaginationButtonGlyphComponent] overlay that reflects the current pagination state * (both-disabled, left-disabled, right-disabled, or both-enabled). + * - A [PaginationPageIndicatorComponent] between the two buttons showing the current page and the + * page count, as rendered by + * [paginationPageIndicator][dev.slne.surf.api.minestom.inventory.framework.view.settings.PaginatedViewSettings.paginationPageIndicator]. * * The initial pagination glyph is updated asynchronously one tick after the pagination state * first resolves (via [InitialPaginationStateWatcher]) to work around scheduling constraints. @@ -53,7 +56,7 @@ abstract class AbstractPaginatedSurfView(header: String) : AbstractSurfView(head .layoutTarget(layoutTarget) .apply { onPageSwitch(pageSwitchHandler.prepend { context, _ -> - updatePaginationGlyph(context) + updatePaginationOverlay(context) }) }.build() } @@ -117,33 +120,61 @@ abstract class AbstractPaginatedSurfView(header: String) : AbstractSurfView(head protected open fun onPaginatedUpdate(update: Context) = Unit - private fun updatePaginationGlyph(context: Context) { + /** + * Re-renders the pagination overlay in the header: the navigation button glyph for the current + * state, plus the page counter between the two buttons. + */ + private fun updatePaginationOverlay(context: Context) { val pagination = paginationState.get(context) ?: return + val row = settings.paginationButtonRow + val geometry = settings.headerGeometry + val buttonGlyph = PaginationButtonGlyphComponent.getByPaginationState( - row = settings.paginationButtonRow, - pagination = pagination + row = row, + pagination = pagination, + geometry = geometry ) + val totalPages = pagination.lastPage().coerceAtLeast(1) + val currentPage = pagination.currentPage().coerceIn(1, totalPages) + + val pageText = settings.paginationPageIndicator + ?.render(currentPage, totalPages) + modifyContainer(context) { removeChildrenOfType() addChild(buttonGlyph) + + removeChildrenOfType() + if (pageText != null) { + addChild( + PaginationPageIndicatorComponent( + row = row, + text = pageText, + font = settings.rowFont(row), + geometry = geometry, + defaultColor = settings.headerTextColor, + metrics = settings.rowFontMetrics + ) + ) + } } } /** - * Applies the paginated container defaults: blocks all border cells and all cells outside - * the pagination content rows, then calls [applyContainerDefaults] for subclass customisation. + * Applies the paginated container defaults: blocks all border cells, the button row and every + * empty row, then calls [applyContainerDefaults] for subclass customisation. * * This override is `final` — subclasses should override [applyContainerDefaults] instead. */ context(_: ViewContainerModificationContext) final override fun containerDefaults() { - val paginationContentRows = settings.paginationViewRows.paginationContentRows + val paginationContentRows = settings.paginationContentRows for (y in 1..settings.rows.rows) { for (x in 0 until 9) { if (y in paginationContentRows && x in 1..7) continue - blockCell(x, y) + blockCell(x, y, settings.headerGeometry) } } @@ -160,21 +191,35 @@ abstract class AbstractPaginatedSurfView(header: String) : AbstractSurfView(head protected open fun applyContainerDefaults() { } + /** + * Plays the configured + * [paginationSwitchSound][dev.slne.surf.api.minestom.inventory.framework.view.settings.PaginatedViewSettings.paginationSwitchSound] + * to the viewer that just navigated to another page. Does nothing if no sound is configured. + */ + private fun playPageSwitchSound(click: SlotClickContext) { + val sound = settings.paginationSwitchSound ?: return + click.player.playSound(sound) + } + final override fun onViewInit(config: ViewConfigBuilder) { paginationState // initialize pagination state onPaginatedInit(config) config.layout(*createLayout()) } + /** + * Builds the layout: only the + * [paginationContentRows][dev.slne.surf.api.minestom.inventory.framework.view.settings.PaginatedViewSettings.paginationContentRows] + * are filled with [layoutTarget] slots. The button row and the + * [paginationEmptyRows][dev.slne.surf.api.minestom.inventory.framework.view.settings.PaginatedViewSettings.paginationEmptyRows] + * rows at the opposite inventory edge stay empty. + */ private fun createLayout(): Array { - val layout = arrayOfNulls(settings.rows.rows) - layout[0] = EMPTY_ROW - repeat(settings.rows.rows - 2) { i -> - layout[i + 1] = paginationRow - } - layout[layout.lastIndex] = EMPTY_ROW + val contentRows = settings.paginationContentRows - return layout.requireNoNulls() + return Array(settings.rows.rows) { index -> + if (index + 1 in contentRows) paginationRow else EMPTY_ROW + } } final override fun onViewOpen(open: OpenContext) { @@ -187,7 +232,7 @@ abstract class AbstractPaginatedSurfView(header: String) : AbstractSurfView(head if (pagination.isStatic) { nextTick { - updatePaginationGlyph(render) + updatePaginationOverlay(render) } } else { render.watchState(pagination.id, InitialPaginationStateWatcher()) @@ -197,13 +242,21 @@ abstract class AbstractPaginatedSurfView(header: String) : AbstractSurfView(head .withItem(ItemStack.AIR) .updateOnStateChange(paginationState) .displayIf(pagination::canBack) - .onClick(pagination::back) + .onClick { click: SlotClickContext -> + if (!pagination.canBack()) return@onClick + pagination.back() + playPageSwitchSound(click) + } render.slot(PaginationButton.RIGHT.clickSlot(paginationButtonRow)) .withItem(ItemStack.AIR) .updateOnStateChange(paginationState) .displayIf(pagination::canAdvance) - .onClick(pagination::advance) + .onClick { click: SlotClickContext -> + if (!pagination.canAdvance()) return@onClick + pagination.advance() + playPageSwitchSound(click) + } onPaginatedRender(render) } @@ -219,7 +272,7 @@ abstract class AbstractPaginatedSurfView(header: String) : AbstractSurfView(head final override fun onViewUpdate(update: Context) { val pagination = paginationState.get(update) - if (pagination != null) { + if (pagination != null && pagination.hasPage(pagination.currentPageIndex())) { pagination.switchTo(pagination.currentPageIndex()) // trigger pagination state update to refresh dynamic elements } @@ -251,7 +304,7 @@ abstract class AbstractPaginatedSurfView(header: String) : AbstractSurfView(head if (host !is Context) return nextTick { - updatePaginationGlyph(host) + updatePaginationOverlay(host) } } } diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationButtonGlyphComponent.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationButtonGlyphComponent.kt index 0566dc230..be1ff2026 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationButtonGlyphComponent.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationButtonGlyphComponent.kt @@ -4,6 +4,7 @@ import dev.slne.surf.api.core.inventory.framework.internal.PaginationButtonGlyph import dev.slne.surf.api.core.messages.Colors import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder import dev.slne.surf.api.minestom.inventory.framework.view.container.component.ViewContainerComponent +import dev.slne.surf.api.minestom.inventory.framework.view.settings.ViewHeaderGeometry import me.devnatan.inventoryframework.component.Pagination /** @@ -17,18 +18,21 @@ import me.devnatan.inventoryframework.component.Pagination * - [Enabled] — both buttons are available * * Each subclass delegates its per-row glyph lookup to the shared [PaginationButtonGlyphs] table. - * The correct glyph character is selected based - * on the [row] (1-based) in which the buttons appear. The component has a fixed positional shift - * of 38 pixels and a texture width of 88 pixels. + * The correct glyph character is selected based on the [row] (1-based) in which the buttons appear. + * The overlay is positioned on the column of the left navigation button (see [PaginationButton]), + * plus the [GLYPH_INSET] the texture keeps free on its left, and spans [TEXTURE_WIDTH] pixels. * * Use [getByPaginationState] to obtain the correct instance for a given [Pagination] state. * * @param row the 1-based inventory row where the pagination buttons are located + * @param geometry the header [ViewHeaderGeometry] describing the slot grid */ -internal sealed class PaginationButtonGlyphComponent(private val row: Int) : - ViewContainerComponent { - override val positionalShift = 39 - override val textureWidth = 88 +internal sealed class PaginationButtonGlyphComponent( + private val row: Int, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, +) : ViewContainerComponent { + override val positionalShift = geometry.columnShift(PaginationButton.LEFT.column) + GLYPH_INSET + override val textureWidth = TEXTURE_WIDTH /** * Returns the glyph character for this button state at the given [rows] (1-based row index). @@ -58,26 +62,81 @@ internal sealed class PaginationButtonGlyphComponent(private val row: Int) : } /** Both navigation buttons are disabled (no previous and no next page). */ - class Disabled(row: Int) : PaginationButtonGlyphComponent(row) { + class Disabled( + row: Int, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + ) : PaginationButtonGlyphComponent(row, geometry) { override fun glyph(rows: Int): Char = PaginationButtonGlyphs.DISABLED.glyph(rows) } /** The right (next) button is disabled; only the left (previous) button is active. */ - class DisabledRight(row: Int) : PaginationButtonGlyphComponent(row) { + class DisabledRight( + row: Int, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + ) : PaginationButtonGlyphComponent(row, geometry) { override fun glyph(rows: Int): Char = PaginationButtonGlyphs.DISABLED_RIGHT.glyph(rows) } /** The left (previous) button is disabled; only the right (next) button is active. */ - class DisabledLeft(row: Int) : PaginationButtonGlyphComponent(row) { + class DisabledLeft( + row: Int, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + ) : PaginationButtonGlyphComponent(row, geometry) { override fun glyph(rows: Int): Char = PaginationButtonGlyphs.DISABLED_LEFT.glyph(rows) } /** Both navigation buttons are active (there are previous and next pages). */ - class Enabled(row: Int) : PaginationButtonGlyphComponent(row) { + class Enabled( + row: Int, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + ) : PaginationButtonGlyphComponent(row, geometry) { override fun glyph(rows: Int): Char = PaginationButtonGlyphs.ENABLED.glyph(rows) } companion object { + /** + * Pixels the button overlay glyph advances the render cursor by, measured off the glyph + * sheet of the pack: `menu/pagination/buttons.png` is 88x48, i.e. four 88x12 bands, and + * rendering a band one-to-one (`height: 12`) advances its 88 pixels plus the one the font + * renderer adds. + * + * This has to match what the glyph really advances - the container resets the cursor by + * `-(textureWidth + positionalShift)` after every component, so a wrong value shifts every + * component rendered afterwards. + */ + const val TEXTURE_WIDTH = 89 + + /** + * Pixels the overlay is moved relative to the left edge of the left button slot, tuned + * against the texture of the pack in-game. + * + * Note that the texture cannot line up with both buttons at once: the centres of its two + * arrows are 73 pixels apart (ink columns 0-14 and 73-87 of the 88 pixel bar), while the + * slot grid puts the two button slots 72 pixels apart. Whatever this offset is, one arrow + * ends up a pixel off its slot centre - move the right arrow one pixel left in + * `menu/pagination/buttons.png` to fix that at the source. + */ + const val GLYPH_INSET = 0 + + /** + * Pixel offset of the free area between the two arrows, measured from the left edge of the + * bar: the arrows of `menu/pagination/buttons.png` occupy its first and last 15 pixels. + */ + const val COUNTER_AREA_OFFSET = 15 + + /** Width in pixels of the free area between the two arrows. */ + const val COUNTER_AREA_WIDTH = 58 + + /** + * Returns the pixel offset of the free area between the two arrows, relative to the title + * origin - where the page counter of a paginated view belongs. + * + * @param geometry the header geometry describing the slot grid the bar is placed on + */ + fun counterAreaShift(geometry: ViewHeaderGeometry): Int = + geometry.columnShift(PaginationButton.LEFT.column) + GLYPH_INSET + COUNTER_AREA_OFFSET + + /** * Returns the appropriate [PaginationButtonGlyphComponent] for the given [pagination] state. * @@ -86,14 +145,19 @@ internal sealed class PaginationButtonGlyphComponent(private val row: Int) : * * @param row the 1-based row index where the buttons are located * @param pagination the current [Pagination] state + * @param geometry the header [ViewHeaderGeometry] describing the slot grid * @return the matching [PaginationButtonGlyphComponent] */ - fun getByPaginationState(row: Int, pagination: Pagination): PaginationButtonGlyphComponent = + fun getByPaginationState( + row: Int, + pagination: Pagination, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + ): PaginationButtonGlyphComponent = when { - !pagination.canBack() && !pagination.canAdvance() -> Disabled(row) - !pagination.canBack() && pagination.canAdvance() -> DisabledLeft(row) - pagination.canBack() && !pagination.canAdvance() -> DisabledRight(row) - pagination.canBack() && pagination.canAdvance() -> Enabled(row) + !pagination.canBack() && !pagination.canAdvance() -> Disabled(row, geometry) + !pagination.canBack() && pagination.canAdvance() -> DisabledLeft(row, geometry) + pagination.canBack() && !pagination.canAdvance() -> DisabledRight(row, geometry) + pagination.canBack() && pagination.canAdvance() -> Enabled(row, geometry) else -> throw MatchException( "Invalid pagination state: canBack=${pagination.canBack()}, canAdvance=${pagination.canAdvance()}", null diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator.kt new file mode 100644 index 000000000..bad37d16f --- /dev/null +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicator.kt @@ -0,0 +1,45 @@ +package dev.slne.surf.api.minestom.inventory.framework.view.pagination + +import dev.slne.surf.api.core.messages.Colors +import dev.slne.surf.api.core.messages.adventure.text +import dev.slne.surf.api.minestom.inventory.framework.view.pagination.PaginationPageIndicator.Companion.Default +import net.kyori.adventure.text.Component + +/** + * Renders the page counter that a paginated view shows between its navigation buttons. + * + * Called every time the pagination state changes, with the one-based number of the page currently + * shown and the number of pages in total. Returning `null` hides the counter for that state — for + * example to show nothing while there is only a single page. + * + * ```kotlin + * paginatedSurfView("Shop") { + * settings { + * paginationPageIndicator { current, total -> + * if (total <= 1) null else text("Seite $current von $total", Colors.GRAY) + * } + * } + * } + * ``` + * + * @see Default + * @see dev.slne.surf.api.minestom.inventory.framework.view.settings.PaginatedViewSettings.paginationPageIndicator + */ +fun interface PaginationPageIndicator { + + /** + * Returns the component to render as the page counter, or `null` to render nothing. + * + * @param currentPage the one-based number of the page currently shown, never below `1` + * @param totalPages the total number of pages, never below `1` - a pagination without any + * content is reported as page `1` of `1` + */ + fun render(currentPage: Int, totalPages: Int): Component? + + companion object { + /** Renders the counter as `current/total`, e.g. `2/7`. */ + val Default = PaginationPageIndicator { currentPage, totalPages -> + text("$currentPage/$totalPages", Colors.WHITE) + } + } +} diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicatorComponent.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicatorComponent.kt new file mode 100644 index 000000000..8746a582f --- /dev/null +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/pagination/PaginationPageIndicatorComponent.kt @@ -0,0 +1,90 @@ +package dev.slne.surf.api.minestom.inventory.framework.view.pagination + +import dev.slne.surf.api.core.inventory.framework.internal.TextAlignmentMath +import dev.slne.surf.api.core.inventory.framework.internal.layoutViewText +import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder +import dev.slne.surf.api.minestom.inventory.framework.view.container.component.ViewContainerComponent +import dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettingsDefaults +import dev.slne.surf.api.minestom.inventory.framework.view.settings.ViewFontMetrics +import dev.slne.surf.api.minestom.inventory.framework.view.settings.ViewHeaderGeometry +import net.kyori.adventure.key.Key +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor + +/** + * The page counter a paginated view renders between its navigation buttons. + * + * It is centred in the free area of the button bar drawn by [PaginationButtonGlyphComponent] — the + * gap between the two arrows, which sit at the ends of that bar — rather than on the slot grid. The + * two centres are two pixels apart, so aligning on the slot grid would leave the counter looking + * slightly off inside the bar. See [PaginationButtonGlyphComponent.counterAreaShift]. + * + * The row itself comes from the [font], exactly like a row text: its `ascent` in the resource pack + * is what puts the counter on the button row. + * + * @param row the one-based row the navigation buttons are in + * @param text the counter component produced by the [PaginationPageIndicator] + * @param font the row font whose `ascent` centres the counter in [row] + * @param geometry the header [ViewHeaderGeometry] describing the slot grid the bar sits on + * @param defaultColor the colour applied to parts of [text] that do not set one + * @param metrics the [ViewFontMetrics] of [font] + */ +internal class PaginationPageIndicatorComponent( + val row: Int, + val text: Component, + private val font: Key, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + private val defaultColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + metrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS, +) : ViewContainerComponent { + + private val laidOut = layoutViewText( + component = text, + charSpacing = metrics.charSpacing, + uppercase = metrics.uppercase, + spaceShift = metrics.spaceShift, + advance = metrics::advance + ) + + override val textureWidth = laidOut.width + + override val positionalShift = TextAlignmentMath.centerAlignedShift( + textWidth = textureWidth, + leftShift = PaginationButtonGlyphComponent.counterAreaShift(geometry), + padding = 0, + containerWidth = PaginationButtonGlyphComponent.COUNTER_AREA_WIDTH + ) + + /** + * `false`: the width is derived from a font-metrics table, so a glyph the client renders at an + * unexpected width would make it an estimate. Keeps the counter from displacing the header + * textures — see [ViewContainerComponent.hasExactWidth]. + */ + override val hasExactWidth = false + + override fun SurfComponentBuilder.renderComponent() { + append(laidOut.component) + font(font) + colorIfAbsent(defaultColor) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as PaginationPageIndicatorComponent + + if (row != other.row) return false + if (text != other.text) return false + if (font != other.font) return false + + return true + } + + override fun hashCode(): Int { + var result = row + result = 31 * result + text.hashCode() + result = 31 * result + font.hashCode() + return result + } +} diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginatedViewSettings.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginatedViewSettings.kt index 7dc01f970..76aee79be 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginatedViewSettings.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginatedViewSettings.kt @@ -1,41 +1,69 @@ package dev.slne.surf.api.minestom.inventory.framework.view.settings +import dev.slne.surf.api.minestom.inventory.framework.view.pagination.PaginationPageIndicator import dev.slne.surf.api.minestom.inventory.framework.view.settings.align.TextAlignment +import it.unimi.dsi.fastutil.ints.Int2ObjectMap import net.kyori.adventure.key.Key +import net.kyori.adventure.sound.Sound +import net.kyori.adventure.text.format.TextColor /** * View settings for a paginated [AbstractPaginatedSurfView]. * * Extends [SurfViewSettings] with pagination-specific configuration: * - The number of visible content rows and the total inventory rows via [paginationViewRows] + * - The number of rows left empty next to the content via [paginationEmptyRows] * - The position (top or bottom) of the previous/next navigation buttons via [paginationButtonPosition] * * Create instances via the DSL builder [paginatedViewSettings] or through the * [PaginatedViewSettingsBuilder] within a `settings { }` DSL block. * * @property font the Adventure [Key] of the font used for the inventory title - * @property headerTextAlignment horizontal alignment of the title text in the header + * @property headerTextAlignment horizontal alignment of the title text + * @property headerTextColor colour for header text that does not carry one of its own + * @property headerFontMetrics glyph metrics of the font the title is rendered in + * @property rowFontMetrics glyph metrics of the fonts row text is rendered in + * @property headerGeometry the pixel geometry of the inventory and its slot grid + * @property backgroundGlyph whether the per-row background glyph of a custom inventory texture is + * rendered + * @property rowFonts the fonts that render header text on a slot row, keyed by one-based row in the header * @property cancelOnClick whether inventory click events should be cancelled by default * @property cancelOnDrag whether inventory drag events should be cancelled by default * @property cancelOnDrop whether item-drop events should be cancelled by default * @property cancelOnPickup whether item-pickup events should be cancelled by default * @property navigateBackOnOutsideClick whether an outside click navigates to the parent view * @property paginationViewRows the [PaginationViewRows] controlling the number of content rows + * @property paginationEmptyRows the [PaginationEmptyRows] left empty at the inventory edge opposite + * the navigation buttons; taken out of the content rows, not added to the inventory height * @property paginationButtonPosition the [PaginationButtonPosition] for navigation buttons + * @property paginationPageIndicator renders the page counter between the navigation buttons, or + * `null` to render none + * @property paginationSwitchSound the [Sound] played to the viewer when a navigation button + * switches to another page, or `null` to play none * @see SurfViewSettings * @see PaginationViewRows + * @see PaginationEmptyRows * @see PaginationButtonPosition */ data class PaginatedViewSettings( override val font: Key = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT, override val headerTextAlignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_HEADER_ALIGNMENT, + override val headerTextColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + override val headerFontMetrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT_METRICS, + override val rowFontMetrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS, + override val headerGeometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, + override val backgroundGlyph: Boolean = SurfViewSettingsDefaults.DEFAULT_BACKGROUND_GLYPH, + override val rowFonts: Int2ObjectMap = SurfViewSettingsDefaults.DEFAULT_ROW_FONTS, override val cancelOnClick: Boolean = SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_CLICK, override val cancelOnDrag: Boolean = SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_DRAG, override val cancelOnDrop: Boolean = SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_DROP, override val cancelOnPickup: Boolean = SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_PICKUP, override val navigateBackOnOutsideClick: Boolean = SurfViewSettingsDefaults.DEFAULT_NAVIGATE_BACK_ON_CLOSE, val paginationViewRows: PaginationViewRows = SurfViewSettingsDefaults.DEFAULT_PAGINATION_VIEW_ROWS, - val paginationButtonPosition: PaginationButtonPosition = SurfViewSettingsDefaults.DEFAULT_PAGINATION_BUTTON_POSITION + val paginationEmptyRows: PaginationEmptyRows = SurfViewSettingsDefaults.DEFAULT_PAGINATION_EMPTY_ROWS, + val paginationButtonPosition: PaginationButtonPosition = SurfViewSettingsDefaults.DEFAULT_PAGINATION_BUTTON_POSITION, + val paginationPageIndicator: PaginationPageIndicator? = SurfViewSettingsDefaults.DEFAULT_PAGINATION_PAGE_INDICATOR, + val paginationSwitchSound: Sound? = SurfViewSettingsDefaults.DEFAULT_PAGINATION_SWITCH_SOUND, ) : SurfViewSettings { override val rows: ViewRows = paginationViewRows.actualRows internal val paginationButtonRow = @@ -44,4 +72,26 @@ data class PaginatedViewSettings( } else { 1 } + + /** + * The one-based rows the pagination engine fills: every row except [paginationButtonRow] and + * the [paginationEmptyRows] rows kept free at the opposite inventory edge. + */ + internal val paginationContentRows: IntRange = + if (paginationButtonPosition == PaginationButtonPosition.BOTTOM) { + (1 + paginationEmptyRows.rows) until rows.rows + } else { + 2..(rows.rows - paginationEmptyRows.rows) + } + + /** How many rows the pagination engine actually fills with content. */ + val paginationContentRowCount: Int = rows.rows - 1 - paginationEmptyRows.rows + + init { + require(paginationContentRowCount >= 1) { + "$paginationViewRows leaves ${paginationViewRows.contentRows} content row(s) in a " + + "${rows.rows}-row inventory, which cannot hold $paginationEmptyRows empty " + + "row(s) as well - use fewer empty rows or more pagination rows" + } + } } \ No newline at end of file diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows.kt new file mode 100644 index 000000000..9d5f57fb2 --- /dev/null +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationEmptyRows.kt @@ -0,0 +1,59 @@ +package dev.slne.surf.api.minestom.inventory.framework.view.settings + +/** + * Defines how many rows of a paginated view are left empty instead of being filled by the + * pagination engine. + * + * The empty rows sit at the inventory edge opposite the navigation buttons, so they pad the + * pagination content away from that edge: with the buttons at the bottom + * ([PaginationButtonPosition.BOTTOM]) they are the topmost rows, with the buttons at the top + * ([PaginationButtonPosition.TOP]) they are the bottommost ones. + * + * The empty rows are taken out of the content rows - they do not enlarge the inventory. The total + * inventory height stays [PaginationViewRows.actualRows], so a view always keeps at least one + * content row: [PaginatedViewSettings] rejects a combination that would leave none. + * + * - [NONE] - no empty row; the content starts right at the inventory edge + * - [ONE] - 1 empty row + * - [TWO] - 2 empty rows + * - [THREE] - 3 empty rows + * + * @property rows the number of empty rows (0-3) + * @see PaginatedViewSettings + * @see PaginationViewRows + */ +enum class PaginationEmptyRows(val rows: Int) { + /** No empty row; the pagination content starts right at the inventory edge. */ + NONE(0), + + /** 1 empty row between the pagination content and the inventory edge. */ + ONE(1), + + /** 2 empty rows between the pagination content and the inventory edge. */ + TWO(2), + + /** 3 empty rows between the pagination content and the inventory edge. */ + THREE(3); + + companion object { + /** + * Returns the [PaginationEmptyRows] for the given [rows] count, or `null` if it is not in + * the range 0..3. + * + * @param rows the number of empty rows to look up (0..3) + * @return the matching [PaginationEmptyRows], or `null` + */ + fun byRowsOrNull(rows: Int): PaginationEmptyRows? = entries.getOrNull(rows) + + /** + * Returns the [PaginationEmptyRows] for the given [rows] count. + * + * @param rows the number of empty rows to look up (0..3) + * @return the matching [PaginationEmptyRows] + * @throws IllegalArgumentException if [rows] is not in the range 0..3 + */ + fun byRows(rows: Int): PaginationEmptyRows = requireNotNull(byRowsOrNull(rows)) { + "A paginated view supports 0 to 3 empty rows, but $rows were requested" + } + } +} diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows.kt index c6ecb1cd7..336827d81 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/PaginationViewRows.kt @@ -3,29 +3,50 @@ package dev.slne.surf.api.minestom.inventory.framework.view.settings /** * Defines the number of pagination content rows and the total inventory size for a paginated view. * - * The total inventory has two additional rows compared to the content rows: one top row and one - * bottom row. The top and bottom rows are used for the inventory border/buttons, while the - * content rows are populated by the pagination engine. + * The total inventory has exactly one row more than the content rows: the row that carries the + * navigation buttons, which sits above or below the content depending on + * [PaginatedViewSettings.paginationButtonPosition]. Every other row is filled by the pagination + * engine - the topmost one included, so no row is left empty just to keep the buttons apart from + * the content. Set [PaginatedViewSettings.paginationEmptyRows] to keep one to three rows free + * again; those rows are taken out of the content rows, the inventory height stays [actualRows]. * - * - [ONE] — 1 content row, 3 total rows ([ViewRows.THREE]) - * - [TWO] — 2 content rows, 4 total rows ([ViewRows.FOUR]) - * - [THREE] — 3 content rows, 5 total rows ([ViewRows.FIVE]) - * - [FOUR] — 4 content rows, 6 total rows ([ViewRows.SIX]) + * - [ONE] - 1 content row, 2 total rows ([ViewRows.TWO]) + * - [TWO] - 2 content rows, 3 total rows ([ViewRows.THREE]) + * - [THREE] - 3 content rows, 4 total rows ([ViewRows.FOUR]) + * - [FOUR] - 4 content rows, 5 total rows ([ViewRows.FIVE]) + * - [FIVE] - 5 content rows, 6 total rows ([ViewRows.SIX]) * - * @property actualRows the total [ViewRows] value (including top and bottom rows) - * @property paginationContentRows the range of 1-based row indices used for pagination content + * @property actualRows the total [ViewRows] value: the content rows plus the button row * @see PaginatedViewSettings */ -enum class PaginationViewRows(val actualRows: ViewRows, val paginationContentRows: IntRange) { - /** 1 content row; 3 total inventory rows. */ - ONE(ViewRows.THREE, 2..2), +enum class PaginationViewRows(val actualRows: ViewRows) { + /** 1 content row; 2 total inventory rows. */ + ONE(ViewRows.TWO), - /** 2 content rows; 4 total inventory rows. */ - TWO(ViewRows.FOUR, 2..3), + /** 2 content rows; 3 total inventory rows. */ + TWO(ViewRows.THREE), - /** 3 content rows; 5 total inventory rows. */ - THREE(ViewRows.FIVE, 2..4), + /** 3 content rows; 4 total inventory rows. */ + THREE(ViewRows.FOUR), - /** 4 content rows; 6 total inventory rows. */ - FOUR(ViewRows.SIX, 2..5); -} \ No newline at end of file + /** 4 content rows; 5 total inventory rows. */ + FOUR(ViewRows.FIVE), + + /** 5 content rows; 6 total inventory rows. */ + FIVE(ViewRows.SIX); + + /** + * How many rows the pagination engine fills at most: one less than [actualRows], because a + * single row is reserved for the navigation buttons. Configured empty rows reduce it further, + * see [PaginatedViewSettings.paginationContentRowCount]. + */ + val contentRows: Int = actualRows.rows - 1 + + /** @suppress */ + @Deprecated( + "The content rows depend on the button position now, use PaginatedViewSettings instead", + level = DeprecationLevel.HIDDEN + ) + val paginationContentRows: IntRange + get() = 1..contentRows +} diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/SimpleViewSettings.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/SimpleViewSettings.kt index a62b60ca0..7b4e7769b 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/SimpleViewSettings.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/SimpleViewSettings.kt @@ -1,7 +1,9 @@ package dev.slne.surf.api.minestom.inventory.framework.view.settings import dev.slne.surf.api.minestom.inventory.framework.view.settings.align.TextAlignment +import it.unimi.dsi.fastutil.ints.Int2ObjectMap import net.kyori.adventure.key.Key +import net.kyori.adventure.text.format.TextColor /** * View settings for a simple (non-paginated) [AbstractSurfView]. @@ -11,6 +13,13 @@ import net.kyori.adventure.key.Key * * @property font the Adventure [Key] of the font used for the inventory title * @property headerTextAlignment horizontal alignment of the title text + * @property headerTextColor colour for header text that does not carry one of its own + * @property headerFontMetrics glyph metrics of the font the title is rendered in + * @property rowFontMetrics glyph metrics of the fonts row text is rendered in + * @property headerGeometry the pixel geometry of the inventory and its slot grid + * @property backgroundGlyph whether the per-row background glyph of a custom inventory texture is + * rendered + * @property rowFonts the fonts that render header text on a slot row, keyed by one-based row * @property cancelOnClick whether click events should be cancelled by default * @property cancelOnDrag whether drag events should be cancelled by default * @property cancelOnDrop whether drop events should be cancelled by default @@ -23,6 +32,12 @@ import net.kyori.adventure.key.Key data class SimpleViewSettings( override val font: Key = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT, override val headerTextAlignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_HEADER_ALIGNMENT, + override val headerTextColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + override val headerFontMetrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT_METRICS, + override val rowFontMetrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS, + override val headerGeometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, + override val backgroundGlyph: Boolean = SurfViewSettingsDefaults.DEFAULT_BACKGROUND_GLYPH, + override val rowFonts: Int2ObjectMap = SurfViewSettingsDefaults.DEFAULT_ROW_FONTS, override val cancelOnClick: Boolean = SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_CLICK, override val cancelOnDrag: Boolean = SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_DRAG, override val cancelOnDrop: Boolean = SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_DROP, diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettings.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettings.kt index 1ec121e1f..e50ff49f7 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettings.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettings.kt @@ -1,7 +1,9 @@ package dev.slne.surf.api.minestom.inventory.framework.view.settings import dev.slne.surf.api.minestom.inventory.framework.view.settings.align.TextAlignment +import it.unimi.dsi.fastutil.ints.Int2ObjectMap import net.kyori.adventure.key.Key +import net.kyori.adventure.text.format.TextColor /** * Sealed interface that defines the full configuration contract for all Surf view types. @@ -10,14 +12,22 @@ import net.kyori.adventure.key.Key * [PaginatedViewSettings] (for paginated views). All properties have sensible defaults * defined in [SurfViewSettingsDefaults]. * - * @property font the Adventure [Key] of the resource-pack font for the inventory title + * @property font the Adventure [Key] of the font for the inventory title; the client's own font + * by default * @property headerTextAlignment horizontal [TextAlignment] of the title in the header + * @property headerTextColor colour for header text that does not carry one of its own + * @property headerFontMetrics glyph metrics of the font the title is rendered in + * @property rowFontMetrics glyph metrics of the fonts row text is rendered in + * @property headerGeometry the pixel geometry of the inventory and its slot grid + * @property backgroundGlyph whether the per-row background glyph of a custom inventory texture is + * rendered + * @property rowFonts the fonts that render header text on a slot row, keyed by one-based row * @property cancelOnClick whether inventory click events are cancelled by default * @property cancelOnDrag whether inventory drag events are cancelled by default * @property cancelOnDrop whether item-drop events are cancelled by default * @property cancelOnPickup whether item-pickup events are cancelled by default * @property navigateBackOnOutsideClick whether clicking outside the inventory navigates - * back to the previous (parent) view + * back to the previous (parent) view; nothing is drawn in the header for it * @property rows the number of chest rows displayed in the inventory * @see SimpleViewSettings * @see PaginatedViewSettings @@ -26,10 +36,26 @@ import net.kyori.adventure.key.Key sealed interface SurfViewSettings { val font: Key val headerTextAlignment: TextAlignment + val headerTextColor: TextColor + val headerFontMetrics: ViewFontMetrics + val rowFontMetrics: ViewFontMetrics + val headerGeometry: ViewHeaderGeometry + val backgroundGlyph: Boolean + val rowFonts: Int2ObjectMap val cancelOnClick: Boolean val cancelOnDrag: Boolean val cancelOnDrop: Boolean val cancelOnPickup: Boolean val navigateBackOnOutsideClick: Boolean val rows: ViewRows + + /** + * Returns the font that renders header text on the one-based slot [row]. + * + * Falls back to [SurfViewSettingsDefaults.rowFont] when [rowFonts] carries no entry for [row]. + * + * @param row the one-based slot row (1-6) + */ + fun rowFont(@ViewRows.Companion.Rows row: Int): Key = + rowFonts[row] ?: SurfViewSettingsDefaults.rowFont(row) } \ No newline at end of file diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettingsDefaults.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettingsDefaults.kt index 11eafd7eb..0c1a67a8e 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettingsDefaults.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/SurfViewSettingsDefaults.kt @@ -1,7 +1,17 @@ package dev.slne.surf.api.minestom.inventory.framework.view.settings +import dev.slne.surf.api.core.inventory.framework.internal.ViewSlotGeometry import dev.slne.surf.api.core.messages.adventure.key +import dev.slne.surf.api.core.util.freeze +import dev.slne.surf.api.minestom.inventory.framework.view.pagination.PaginationPageIndicator +import dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettingsDefaults.DEFAULT_HEADER_FONT +import dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettingsDefaults.rowFont import dev.slne.surf.api.minestom.inventory.framework.view.settings.align.TextAlignment +import it.unimi.dsi.fastutil.ints.Int2ObjectMap +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import net.kyori.adventure.key.Key +import net.kyori.adventure.sound.Sound +import net.kyori.adventure.text.format.TextColor /** * Centralised default values for all [SurfViewSettings] properties. @@ -13,7 +23,7 @@ import dev.slne.surf.api.minestom.inventory.framework.view.settings.align.TextAl * All defaults can be overridden per-view via the `settings { }` DSL block. * * @see SurfViewSettings - * @see dev.slne.surf.api.minestom.api.inventory.framework.view.settings.builder.SurfViewSettingsBuilder + * @see dev.slne.surf.api.minestom.inventory.framework.view.settings.builder.SurfViewSettingsBuilder */ object SurfViewSettingsDefaults { /** Default value for [SurfViewSettings.cancelOnClick]: `true`. */ @@ -31,21 +41,130 @@ object SurfViewSettingsDefaults { /** Default value for [SurfViewSettings.navigateBackOnOutsideClick]: `true`. */ const val DEFAULT_NAVIGATE_BACK_ON_CLOSE = true - /** The Adventure [Key] for the default header font (`surf:menu_font_normal_ascended`). */ - val DEFAULT_HEADER_FONT = key("surf", "menu_font_normal_ascended") + /** + * The Adventure [Key] for the default header font: the client's own font + * (`minecraft:default`). + * + * The title is rendered like any other vanilla inventory title — plain, in the player's font, + * at the top of the header. Its horizontal position is computed from the proportional glyph + * widths of that font, see + * [DefaultFontWidths][dev.slne.surf.api.core.inventory.framework.internal.DefaultFontWidths]. + */ + val DEFAULT_HEADER_FONT = key("minecraft", "default") - /** The Adventure [Key] for the default menu font (`surf:menu`), used for the title component. */ + /** + * The Adventure [Key] of the ascended copy of the vanilla font (`surf:menu_font_normal_ascended`). + * + * Renders text one line below the title row. Pass it to + * [font][dev.slne.surf.api.minestom.inventory.framework.view.settings.builder.SurfViewSettingsBuilder.font] + * for a view whose header text should sit lower than the vanilla title position. + */ + val ASCENDED_HEADER_FONT = key("surf", "menu_font_normal_ascended") + + /** The Adventure [Key] for the default menu font (`surf:menu`), used for the header textures. */ val DEFAULT_MENU_FONT = key("surf", "menu") /** Default [TextAlignment] for the header: [TextAlignment.CENTER]. */ val DEFAULT_HEADER_ALIGNMENT = TextAlignment.CENTER + /** + * Default colour for header text that does not carry one of its own: `#404040`, the colour + * vanilla draws inventory titles in. + * + * Both the title and any row text are drawn on the vanilla inventory background, which is light + * grey — white text would be all but invisible on it. Pass a coloured component (or set + * [SurfViewSettings.headerTextColor]) for anything else. + */ + val DEFAULT_HEADER_TEXT_COLOR: TextColor = TextColor.color(0x404040) + + /** + * Default value for [SurfViewSettings.backgroundGlyph]: `false`. + * + * Views render on the vanilla inventory background. Set it to `true` for a resource pack that + * draws its own inventory texture through the per-row glyphs of + * [ViewContainerGlyphComponent][dev.slne.surf.api.minestom.inventory.framework.view.container.component.components.ViewContainerGlyphComponent], + * and re-measure [ViewHeaderGeometry] for that texture. + */ + const val DEFAULT_BACKGROUND_GLYPH = false + + /** Default [ViewHeaderGeometry]: the pixel layout of the current header texture. */ + val DEFAULT_HEADER_GEOMETRY = ViewHeaderGeometry.DEFAULT + + /** + * Default [ViewFontMetrics] for the inventory title: [ViewFontMetrics.VANILLA], matching the + * client font of [DEFAULT_HEADER_FONT]. + */ + val DEFAULT_HEADER_FONT_METRICS = ViewFontMetrics.VANILLA + + /** + * Default [ViewFontMetrics] for text rendered on a slot row: [ViewFontMetrics.VANILLA_ROW], + * because the row fonts of [rowFont] are copies of the client font glyph sheets, ascended onto + * their row - same widths as the client font, but without its space provider. + * + * Switch it to [ViewFontMetrics.SURF_MENU] for row fonts built from the fixed-width, + * capitals-only menu font texture instead. + */ + val DEFAULT_ROW_FONT_METRICS = ViewFontMetrics.VANILLA_ROW + + /** Default [TextAlignment] for text rendered on a slot row: [TextAlignment.CENTER]. */ + val DEFAULT_ROW_TEXT_ALIGNMENT = TextAlignment.CENTER + + /** Default column span for text rendered on a slot row: the full width of the slot grid. */ + val DEFAULT_ROW_TEXT_COLUMNS = 0 until ViewSlotGeometry.COLUMNS + /** Default number of rows for simple views: [ViewRows.FIVE]. */ val DEFAULT_VIEW_ROWS = ViewRows.FIVE - /** Default [PaginationViewRows] for paginated views: [PaginationViewRows.FOUR]. */ - val DEFAULT_PAGINATION_VIEW_ROWS = PaginationViewRows.FOUR + /** Default [PaginationViewRows] for paginated views: [PaginationViewRows.FIVE]. */ + val DEFAULT_PAGINATION_VIEW_ROWS = PaginationViewRows.FIVE + + /** + * Default [PaginationEmptyRows] for paginated views: [PaginationEmptyRows.NONE], so the + * pagination content fills every row except the navigation button row. + */ + val DEFAULT_PAGINATION_EMPTY_ROWS = PaginationEmptyRows.NONE /** Default [PaginationButtonPosition] for paginated views: [PaginationButtonPosition.BOTTOM]. */ val DEFAULT_PAGINATION_BUTTON_POSITION = PaginationButtonPosition.BOTTOM -} \ No newline at end of file + + /** + * Default [PaginationPageIndicator] for paginated views: [PaginationPageIndicator.Default], + * which renders `current/total` between the navigation buttons. + */ + val DEFAULT_PAGINATION_PAGE_INDICATOR: PaginationPageIndicator = PaginationPageIndicator.Default + + /** + * Default [Sound] played to the viewer when a paginated view switches to another page: + * `minecraft:item.book.page_turn`, the sound the client plays when a book page is turned. + */ + val DEFAULT_PAGINATION_SWITCH_SOUND: Sound = Sound.sound( + key("minecraft", "item.book.page_turn"), + Sound.Source.UI, + 1f, + 1f + ) + + /** + * Returns the default font that renders header text on the one-based slot [row]: + * `surf:menu_font_row_1` … `surf:menu_font_row_6`. + * + * A glyph's vertical position is baked into the `ascent` of its font provider in the resource + * pack, so text can only be moved down a row by rendering it in a font that declares the + * matching `ascent`. Each of these fonts is therefore a copy of the vanilla font whose `ascent` + * puts its glyphs in the vertical centre of the row - not above its slots. + * + * @param row the one-based slot row (1–6) + */ + fun rowFont(@ViewRows.Companion.Rows row: Int): Key = key("surf", "menu_font_row_$row") + + /** + * Default [SurfViewSettings.rowFonts]: the [rowFont] of every slot row a chest inventory can + * have, keyed by its one-based row number. + */ + val DEFAULT_ROW_FONTS: Int2ObjectMap = + Int2ObjectOpenHashMap(ViewSlotGeometry.MAX_ROWS).apply { + for (row in 1..ViewSlotGeometry.MAX_ROWS) { + put(row, rowFont(row)) + } + }.freeze() +} diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics.kt new file mode 100644 index 000000000..d05d941ee --- /dev/null +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/ViewFontMetrics.kt @@ -0,0 +1,112 @@ +package dev.slne.surf.api.minestom.inventory.framework.view.settings + +import dev.slne.surf.api.core.inventory.framework.internal.DefaultFontWidths +import dev.slne.surf.api.core.util.int2IntMapOf +import it.unimi.dsi.fastutil.ints.Int2IntMap +import it.unimi.dsi.fastutil.ints.Int2IntMaps + +/** + * Metrics of the font a piece of header text is rendered in: how wide its glyphs are, how much + * spacing goes between them, and whether it only draws capitals. + * + * Header text is positioned by shifting the render cursor, so its width has to be known before it + * is drawn. That width can only be derived from the font: [VANILLA] describes the proportional + * client font (widths from [DefaultFontWidths]), [SURF_MENU] the fixed-width resource-pack menu + * font, and a custom instance any other font in the pack. + * + * ```kotlin + * settings { + * // A row font drawn 10px per glyph with no extra spacing, capitals and lowercase: + * rowFontMetrics(ViewFontMetrics(charSize = 10)) + * } + * ``` + * + * @property charSize pixel advance of a glyph that has no entry in [charWidths]; for a bitmap font + * this is its cell width including the one pixel the font renderer adds after every glyph + * @property charSpacing pixels of spacing inserted in front of every glyph — negative tightens the + * run, `0` renders the text as-is. The spacing is emitted as shift glyphs **inside** the text, so + * the font must provide them + * @property uppercase whether the text is uppercased before rendering, for a font that only draws + * capitals + * @property spaceAsShift whether a space is rendered as a shift glyph instead of as a space. A font + * built from a glyph sheet draws no space of its own, so the client would fall back to the + * missing-glyph box, which lands on the title line instead of in the row - rendering the space as + * a shift of [advance] pixels avoids depending on the font providing one + * @property charWidths per-code-point advance overrides for the glyphs the font does not render at + * [charSize] pixels + * @see SurfViewSettings.headerFontMetrics + * @see SurfViewSettings.rowFontMetrics + */ +data class ViewFontMetrics( + val charSize: Int = DefaultFontWidths.DEFAULT_ADVANCE, + val charSpacing: Int = 0, + val uppercase: Boolean = false, + val spaceAsShift: Boolean = false, + val charWidths: Int2IntMap = Int2IntMaps.EMPTY_MAP, +) { + /** + * Pixels a space advances when it is rendered as a shift glyph, or `null` when the font draws + * the space itself. + */ + val spaceShift: Int? get() = if (spaceAsShift) advance(' '.code) else null + + /** + * Returns the pixel advance of [codePoint] in this font. + * + * @param codePoint the Unicode code point to measure + * @param bold whether the glyph is rendered bold, which widens it by one pixel + */ + fun advance(codePoint: Int, bold: Boolean = false): Int { + val advance = + if (charWidths.containsKey(codePoint)) charWidths.get(codePoint) else charSize + + return advance + if (bold) DefaultFontWidths.BOLD_EXTRA_ADVANCE else 0 + } + + companion object { + /** + * Metrics of the proportional client font (`minecraft:default`): per-glyph widths from + * [DefaultFontWidths], no extra spacing, text rendered as written. The client font brings + * its own space provider, so spaces are emitted as spaces. + */ + val VANILLA = ViewFontMetrics( + charSize = DefaultFontWidths.DEFAULT_ADVANCE, + charWidths = DefaultFontWidths.advances + ) + + /** + * Metrics of a row font that copies the client font glyph sheets onto a slot row: the same + * widths as [VANILLA], but the space is rendered as a shift glyph, because such a copy + * carries only the glyph sheets and no space provider. + */ + val VANILLA_ROW = VANILLA.copy(spaceAsShift = true) + + /** + * Metrics of the fixed-width resource-pack menu font, including the per-row fonts of + * [SurfViewSettingsDefaults.rowFont]: 9-pixel cells tightened by one pixel — 8 pixels per + * glyph — and uppercased, because the font only draws capitals. + * + * The space is the one exception: the font draws no space glyph, so it is rendered as a + * shift of [MENU_SPACE_SIZE] pixels. + */ + val SURF_MENU = ViewFontMetrics( + charSize = MENU_CHAR_SIZE, + charSpacing = MENU_CHAR_SPACING, + uppercase = true, + spaceAsShift = true, + charWidths = int2IntMapOf(' '.code to MENU_SPACE_SIZE) + ) + + /** Pixel advance of one glyph cell in the resource-pack menu font. */ + const val MENU_CHAR_SIZE = 9 + + /** Spacing that tightens the menu font to 8 pixels per glyph. */ + const val MENU_CHAR_SPACING = -1 + + /** + * Advance of a space in the menu font, taken from the space provider of the pack rather than + * from the glyph texture. + */ + const val MENU_SPACE_SIZE = 4 + } +} diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry.kt new file mode 100644 index 000000000..8b106552f --- /dev/null +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/ViewHeaderGeometry.kt @@ -0,0 +1,147 @@ +package dev.slne.surf.api.minestom.inventory.framework.view.settings + +import dev.slne.surf.api.core.inventory.framework.internal.DefaultFontWidths +import dev.slne.surf.api.core.inventory.framework.internal.ViewSlotGeometry +import dev.slne.surf.api.minestom.inventory.framework.view.settings.align.TextAlignment +import dev.slne.surf.api.minestom.inventory.framework.view.settings.align.TextAlignmentOptions +import it.unimi.dsi.fastutil.ints.Int2IntMap + +/** + * Pixel geometry of the inventory header: how wide the inventory is and where its slot grid starts. + * + * Everything the header draws is a glyph inside the inventory title, so all of these values are + * horizontal offsets measured from the **title origin** — the pixel the client starts drawing the + * inventory title at. Negative values are left of it. + * + * The defaults describe the vanilla chest inventory, whose screen is 176 pixels wide with the title + * drawn 8 pixels in from its left edge (hence [titleLeftShift] `= -8`) and whose slot cells are + * 18 pixels apart starting one pixel left of the title origin (hence [slotOriginX] `= -1`). With + * those numbers the centre of the inventory and the centre of the slot grid coincide, so a centred + * title sits exactly above a centred row text. Only override them for a resource pack that replaces + * the inventory background with a texture of a different size. + * + * ```kotlin + * settings { + * headerGeometry { copy(titleWidth = 223, titleLeftShift = -48) } + * } + * ``` + * + * @property titleLeftShift pixel offset of the left edge of the inventory, and therefore of the + * area the title is aligned in + * @property titlePadding horizontal padding kept free on each side of the title area + * @property titleWidth width of the inventory in pixels, i.e. the width the title is aligned in + * @property slotOriginX pixel offset of the left edge of slot column `0` + * @property slotSize horizontal pitch of the slot grid: one slot plus its one-pixel border + * @property slotGlyphInset transparent padding on the left of the slot overlay texture, so a + * texture that does not start at its very first pixel still lands on the slot border + * @property rowTextPadding horizontal padding kept free on each side when text is aligned inside a + * span of slot columns + * @see SurfViewSettings.headerGeometry + */ +data class ViewHeaderGeometry( + val titleLeftShift: Int = DEFAULT_TITLE_LEFT_SHIFT, + val titlePadding: Int = DEFAULT_TITLE_PADDING, + val titleWidth: Int = DEFAULT_TITLE_WIDTH, + val slotOriginX: Int = DEFAULT_SLOT_ORIGIN_X, + val slotSize: Int = ViewSlotGeometry.SLOT_SIZE, + val slotGlyphInset: Int = DEFAULT_SLOT_GLYPH_INSET, + val rowTextPadding: Int = DEFAULT_ROW_TEXT_PADDING, +) { + /** Width in pixels of the full nine-column slot grid. */ + val slotAreaWidth: Int get() = ViewSlotGeometry.spanWidth(ViewSlotGeometry.COLUMNS, slotSize) + + /** + * Returns the pixel offset of the left edge of [column]. + * + * @param column the zero-based column index (`0`–`8`) + */ + fun columnShift(column: Int): Int = ViewSlotGeometry.columnShift(column, slotOriginX, slotSize) + + /** + * Returns the pixel offset a slot overlay texture is rendered at in [column]: the left edge of + * the column, moved right by the [slotGlyphInset] the texture keeps free on its left. + * + * @param column the zero-based column index (`0`-`8`) + */ + fun slotGlyphShift(column: Int): Int = columnShift(column) + slotGlyphInset + + /** + * Returns the [TextAlignmentOptions] that align text across the full width of the inventory — + * the geometry the inventory title itself uses. + * + * The returned options measure text in the vanilla font. Pass [charWidths] for glyphs + * [DefaultFontWidths] does not cover (custom resource-pack glyphs, CJK from the Unifont + * fallback), or a [charSize]/[charSpacing] pair describing a fixed-width resource-pack font. + * + * @param charWidths per-code-point advance overrides + * @param charSize advance of a glyph without an override + * @param charSpacing extra spacing inserted between adjacent glyphs + */ + fun titleOptions( + charWidths: Int2IntMap = DefaultFontWidths.advances, + charSize: Int = DefaultFontWidths.DEFAULT_ADVANCE, + charSpacing: Int = 0, + ): TextAlignmentOptions = TextAlignmentOptions( + leftShift = titleLeftShift, + padding = titlePadding, + containerWidth = titleWidth, + charSize = charSize, + charSpacing = charSpacing, + charWidths = charWidths + ) + + /** + * Returns the [TextAlignmentOptions] that align text inside the span of slot [columns], so a + * [TextAlignment] positions it relative to those slots rather than to the whole inventory. + * + * @param columns the zero-based, inclusive range of slot columns to align inside + * @param charWidths per-code-point advance overrides + * @param charSize advance of a glyph without an override + * @param charSpacing extra spacing inserted between adjacent glyphs + */ + fun columnSpanOptions( + columns: IntRange, + charWidths: Int2IntMap = DefaultFontWidths.advances, + charSize: Int = DefaultFontWidths.DEFAULT_ADVANCE, + charSpacing: Int = 0, + ): TextAlignmentOptions { + require(!columns.isEmpty()) { "A column span must cover at least one column" } + + return TextAlignmentOptions( + leftShift = columnShift(columns.first), + padding = rowTextPadding, + containerWidth = ViewSlotGeometry.spanWidth(columns.count(), slotSize), + charSize = charSize, + charSpacing = charSpacing, + charWidths = charWidths + ) + } + + companion object { + /** Default [titleLeftShift]: the vanilla inventory starts 8 pixels left of the title origin. */ + const val DEFAULT_TITLE_LEFT_SHIFT = -8 + + /** Default [titlePadding]: the title may use the full width of the inventory. */ + const val DEFAULT_TITLE_PADDING = 0 + + /** Default [titleWidth]: the width of the vanilla chest inventory screen. */ + const val DEFAULT_TITLE_WIDTH = 176 + + /** Default [slotOriginX]: slot column `0` starts one pixel left of the title origin. */ + const val DEFAULT_SLOT_ORIGIN_X = -1 + + /** + * Default [slotGlyphInset]: none. The slot overlay glyphs of the pack are 18x18 textures that + * start at their first pixel, so they land on the cell border the grid is measured from. + * Set it to the number of transparent columns your texture carries on its left instead of + * re-cutting the texture. + */ + const val DEFAULT_SLOT_GLYPH_INSET = 0 + + /** Default [rowTextPadding]: one pixel of breathing room inside a column span. */ + const val DEFAULT_ROW_TEXT_PADDING = 1 + + /** The geometry of the vanilla chest inventory. */ + val DEFAULT = ViewHeaderGeometry() + } +} diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/builder/PaginatedViewSettingsBuilder.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/builder/PaginatedViewSettingsBuilder.kt index 408ae0a6e..7274edb4e 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/builder/PaginatedViewSettingsBuilder.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/builder/PaginatedViewSettingsBuilder.kt @@ -1,16 +1,20 @@ package dev.slne.surf.api.minestom.inventory.framework.view.settings.builder import dev.slne.surf.api.minestom.inventory.framework.view.InventoryFrameworkDSL +import dev.slne.surf.api.minestom.inventory.framework.view.pagination.PaginationPageIndicator import dev.slne.surf.api.minestom.inventory.framework.view.settings.PaginatedViewSettings import dev.slne.surf.api.minestom.inventory.framework.view.settings.PaginationButtonPosition +import dev.slne.surf.api.minestom.inventory.framework.view.settings.PaginationEmptyRows import dev.slne.surf.api.minestom.inventory.framework.view.settings.PaginationViewRows import dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettingsDefaults +import net.kyori.adventure.sound.Sound /** * DSL builder for [PaginatedViewSettings]. * * Extends [SurfViewSettingsBuilder] with pagination-specific properties: * - [paginationViewRows] — controls the number of visible item rows and total inventory height + * - [paginationEmptyRows] — controls how many rows stay empty next to the pagination content * - [paginationButtonPosition] — controls whether the prev/next buttons are at the top or bottom * * Create instances via [paginatedViewSettings] or the `settings { }` DSL function in a @@ -51,6 +55,40 @@ class PaginatedViewSettingsBuilder @PublishedApi internal constructor() : this.paginationViewRows = rows } + /** + * The [PaginationEmptyRows] controlling how many rows stay empty at the inventory edge opposite + * the navigation buttons. They are taken out of the content rows, so the inventory height stays + * the one of [paginationViewRows]. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_PAGINATION_EMPTY_ROWS]. + */ + var paginationEmptyRows: PaginationEmptyRows = + SurfViewSettingsDefaults.DEFAULT_PAGINATION_EMPTY_ROWS + private set + + /** + * Sets the [PaginationEmptyRows]. + * + * @param emptyRows the desired [PaginationEmptyRows] + */ + fun paginationEmptyRows(emptyRows: PaginationEmptyRows) { + this.paginationEmptyRows = emptyRows + } + + /** + * Sets the number of rows that stay empty next to the pagination content. + * + * @param emptyRows the number of empty rows (0..3) + * @throws IllegalArgumentException if [emptyRows] is not in the range 0..3 + */ + fun paginationEmptyRows(emptyRows: Int) { + paginationEmptyRows(PaginationEmptyRows.byRows(emptyRows)) + } + + /** Shorthand for `paginationEmptyRows(PaginationEmptyRows.NONE)`. */ + fun noPaginationEmptyRows() { + paginationEmptyRows(PaginationEmptyRows.NONE) + } + /** * The [PaginationButtonPosition] controlling whether navigation buttons are at the top or bottom. * Defaults to [SurfViewSettingsDefaults.DEFAULT_PAGINATION_BUTTON_POSITION]. @@ -68,6 +106,58 @@ class PaginatedViewSettingsBuilder @PublishedApi internal constructor() : this.paginationButtonPosition = position } + /** + * Renders the page counter between the navigation buttons, or `null` to render none. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_PAGINATION_PAGE_INDICATOR], which renders + * `current/total`. + */ + var paginationPageIndicator: PaginationPageIndicator? = + SurfViewSettingsDefaults.DEFAULT_PAGINATION_PAGE_INDICATOR + private set + + /** + * Sets the [PaginationPageIndicator] that renders the page counter. + * + * ```kotlin + * settings { + * paginationPageIndicator { current, total -> text("Seite $current von $total") } + * } + * ``` + * + * @param indicator renders the counter, or `null` to render none + */ + fun paginationPageIndicator(indicator: PaginationPageIndicator?) { + this.paginationPageIndicator = indicator + } + + /** Renders no page counter between the navigation buttons. */ + fun noPaginationPageIndicator() { + paginationPageIndicator(null) + } + + /** + * The [Sound] played to the viewer when a navigation button switches to another page, or `null` + * to play none. Defaults to [SurfViewSettingsDefaults.DEFAULT_PAGINATION_SWITCH_SOUND], the + * vanilla book page-turn sound. + */ + var paginationSwitchSound: Sound? = + SurfViewSettingsDefaults.DEFAULT_PAGINATION_SWITCH_SOUND + private set + + /** + * Sets the [Sound] played when a navigation button switches to another page. + * + * @param sound the sound to play, or `null` to play none + */ + fun paginationSwitchSound(sound: Sound?) { + this.paginationSwitchSound = sound + } + + /** Plays no sound when a navigation button switches to another page. */ + fun noPaginationSwitchSound() { + paginationSwitchSound(null) + } + /** Shorthand for `paginationButtonPosition(PaginationButtonPosition.BOTTOM)`. */ fun paginationButtonsAtBottom() { paginationButtonPosition(PaginationButtonPosition.BOTTOM) @@ -82,13 +172,22 @@ class PaginatedViewSettingsBuilder @PublishedApi internal constructor() : override fun build(): PaginatedViewSettings = PaginatedViewSettings( font = font, headerTextAlignment = headerTextAlignment, + headerTextColor = headerTextColor, + headerFontMetrics = headerFontMetrics, + rowFontMetrics = rowFontMetrics, + headerGeometry = headerGeometry, + backgroundGlyph = backgroundGlyph, + rowFonts = rowFonts, cancelOnClick = cancelOnClick, cancelOnDrag = cancelOnDrag, cancelOnDrop = cancelOnDrop, cancelOnPickup = cancelOnPickup, navigateBackOnOutsideClick = navigateBackOnOutsideClick, paginationViewRows = paginationViewRows, + paginationEmptyRows = paginationEmptyRows, paginationButtonPosition = paginationButtonPosition, + paginationPageIndicator = paginationPageIndicator, + paginationSwitchSound = paginationSwitchSound, ) } diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/builder/SimpleViewSettingsBuilder.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/builder/SimpleViewSettingsBuilder.kt index 260688f84..269927a34 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/builder/SimpleViewSettingsBuilder.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/builder/SimpleViewSettingsBuilder.kt @@ -61,6 +61,12 @@ class SimpleViewSettingsBuilder @PublishedApi internal constructor() : SurfViewS override fun build(): SimpleViewSettings = SimpleViewSettings( font = font, headerTextAlignment = headerTextAlignment, + headerTextColor = headerTextColor, + headerFontMetrics = headerFontMetrics, + rowFontMetrics = rowFontMetrics, + headerGeometry = headerGeometry, + backgroundGlyph = backgroundGlyph, + rowFonts = rowFonts, cancelOnClick = cancelOnClick, cancelOnDrag = cancelOnDrag, cancelOnDrop = cancelOnDrop, diff --git a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/builder/SurfViewSettingsBuilder.kt b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/builder/SurfViewSettingsBuilder.kt index 36b2c8aa6..1fdfa9c5b 100644 --- a/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/builder/SurfViewSettingsBuilder.kt +++ b/surf-api-minestom/src/main/kotlin/dev/slne/surf/api/minestom/inventory/framework/view/settings/builder/SurfViewSettingsBuilder.kt @@ -1,10 +1,16 @@ package dev.slne.surf.api.minestom.inventory.framework.view.settings.builder +import dev.slne.surf.api.core.util.freeze import dev.slne.surf.api.minestom.inventory.framework.view.InventoryFrameworkDSL import dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettings import dev.slne.surf.api.minestom.inventory.framework.view.settings.SurfViewSettingsDefaults +import dev.slne.surf.api.minestom.inventory.framework.view.settings.ViewFontMetrics +import dev.slne.surf.api.minestom.inventory.framework.view.settings.ViewHeaderGeometry import dev.slne.surf.api.minestom.inventory.framework.view.settings.align.TextAlignment +import it.unimi.dsi.fastutil.ints.Int2ObjectMap +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import net.kyori.adventure.key.Key +import net.kyori.adventure.text.format.TextColor /** * Abstract base builder for all [SurfViewSettings] implementations. @@ -55,6 +61,144 @@ sealed class SurfViewSettingsBuilder { this.headerTextAlignment = alignment } + /** + * Colour for header text that does not carry a colour of its own. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR]. + */ + var headerTextColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR + private set + + /** + * Sets the colour used for header text that does not carry one of its own. + * + * @param color the colour to fall back to + */ + fun headerTextColor(color: TextColor) { + this.headerTextColor = color + } + + /** + * Glyph metrics of the font the title is rendered in. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_HEADER_FONT_METRICS]. + */ + var headerFontMetrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT_METRICS + private set + + /** + * Sets the glyph metrics of the title font. + * + * Has to match the font set through [font], otherwise the title is measured wrong and lands off + * centre. + * + * @param metrics the metrics of the title font + */ + fun headerFontMetrics(metrics: ViewFontMetrics) { + this.headerFontMetrics = metrics + } + + /** + * Glyph metrics of the fonts row text is rendered in. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS]. + */ + var rowFontMetrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS + private set + + /** + * Sets the glyph metrics of the row fonts. + * + * Has to match the fonts set through [rowFont], otherwise row text is measured wrong and lands + * off centre. Pass [ViewFontMetrics.VANILLA] for row fonts that are copies of the client font. + * + * @param metrics the metrics of the row fonts + */ + fun rowFontMetrics(metrics: ViewFontMetrics) { + this.rowFontMetrics = metrics + } + + /** + * Whether the per-row background glyph of a custom inventory texture is rendered. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_BACKGROUND_GLYPH]. + */ + var backgroundGlyph: Boolean = SurfViewSettingsDefaults.DEFAULT_BACKGROUND_GLYPH + private set + + /** + * Sets whether the view draws its own inventory background through the per-row glyphs of + * [ViewContainerGlyphComponent][dev.slne.surf.api.minestom.inventory.framework.view.container.component.components.ViewContainerGlyphComponent]. + * + * Leave it off for a view rendering on the vanilla inventory texture. When enabling it, also + * re-measure [headerGeometry] for the custom texture. + * + * @param render `true` to render the background glyph; defaults to `true` + */ + fun backgroundGlyph(render: Boolean = true) { + this.backgroundGlyph = render + } + + /** + * The [ViewHeaderGeometry] describing the pixel layout of the inventory. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY]. + */ + var headerGeometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY + private set + + /** + * Sets the [ViewHeaderGeometry] of the header. + * + * @param geometry the geometry of the header texture in use + */ + fun headerGeometry(geometry: ViewHeaderGeometry) { + this.headerGeometry = geometry + } + + /** + * Derives the [ViewHeaderGeometry] of the header from the current one. + * + * ```kotlin + * settings { + * headerGeometry { copy(titleWidth = 240) } + * } + * ``` + * + * @param block returns the geometry to use, with the current one as its receiver + */ + fun headerGeometry(block: ViewHeaderGeometry.() -> ViewHeaderGeometry) { + this.headerGeometry = headerGeometry.block() + } + + /** + * The fonts that render header text on a slot row, keyed by one-based row number. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_ROW_FONTS]. + */ + var rowFonts: Int2ObjectMap = SurfViewSettingsDefaults.DEFAULT_ROW_FONTS + private set + + /** + * Replaces the whole row-font mapping. + * + * Rows without an entry fall back to [SurfViewSettingsDefaults.rowFont]. + * + * @param fonts the fonts to use, keyed by one-based row number + */ + fun rowFonts(fonts: Int2ObjectMap) { + this.rowFonts = fonts + } + + /** + * Overrides the font that renders header text on a single slot [row]. + * + * Each row needs its own font because a glyph's vertical position comes from the `ascent` its + * font provider declares in the resource pack — see [SurfViewSettingsDefaults.rowFont]. + * + * @param row the one-based slot row (1-6) + * @param font the Adventure [Key] of the font ascended onto that row + */ + fun rowFont(row: Int, font: Key) { + val fonts = Int2ObjectOpenHashMap(rowFonts) + fonts.put(row, font) + this.rowFonts = fonts.freeze() + } + /** * Whether click events in the inventory are cancelled by default. * Defaults to [SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_CLICK]. diff --git a/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/PaperPluginMain.kt b/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/PaperPluginMain.kt index 2e7064a77..97a8fd077 100644 --- a/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/PaperPluginMain.kt +++ b/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/PaperPluginMain.kt @@ -15,6 +15,7 @@ import dev.slne.surf.api.paper.test.listener.ChatListener import dev.slne.surf.surfapi.bukkit.test.command.dialog.dialogTestCommand import dev.slne.surf.surfapi.bukkit.test.command.subcommands.inventory.TestInventoryView import dev.slne.surf.surfapi.bukkit.test.command.subcommands.inventory.testInventoryViewDsl +import dev.slne.surf.surfapi.bukkit.test.command.subcommands.inventory.testPaginatedViewDsl import dev.slne.surf.surfapi.bukkit.test.config.ModernTestConfig import dev.slne.surf.surfapi.bukkit.test.config.MyPluginConfig import net.minecraft.server.MinecraftServer @@ -31,6 +32,7 @@ class PaperPluginMain : SuspendingJavaPlugin() { SurfPaperPacketListenerApi.registerListeners(ChatListener()) TestInventoryView.register() testInventoryViewDsl.register() + testPaginatedViewDsl.register() // SurfConfigApi.createSpongeYmlConfig(TestConfig2::class.java, dataPath, "test-config-2.yml") } diff --git a/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/subcommands/InventoryTest.kt b/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/subcommands/InventoryTest.kt index d590ec8ca..ecbf79aa3 100644 --- a/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/subcommands/InventoryTest.kt +++ b/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/subcommands/InventoryTest.kt @@ -2,8 +2,10 @@ package dev.slne.surf.surfapi.bukkit.test.command.subcommands import dev.jorel.commandapi.CommandAPICommand import dev.jorel.commandapi.kotlindsl.playerExecutor +import dev.jorel.commandapi.kotlindsl.subcommand import dev.slne.surf.api.paper.inventory.framework.open import dev.slne.surf.surfapi.bukkit.test.command.subcommands.inventory.testInventoryViewDsl +import dev.slne.surf.surfapi.bukkit.test.command.subcommands.inventory.testPaginatedViewDsl class InventoryTest(name: String) : CommandAPICommand(name) { init { @@ -11,5 +13,11 @@ class InventoryTest(name: String) : CommandAPICommand(name) { // TestInventoryView.open(player) testInventoryViewDsl.open(player) } + + subcommand("paginated") { + playerExecutor { player, _ -> + testPaginatedViewDsl.open(player) + } + } } } \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/subcommands/inventory/TestInventoryView.kt b/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/subcommands/inventory/TestInventoryView.kt index 3151c7030..20c662a3f 100644 --- a/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/subcommands/inventory/TestInventoryView.kt +++ b/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/subcommands/inventory/TestInventoryView.kt @@ -1,28 +1,57 @@ package dev.slne.surf.surfapi.bukkit.test.command.subcommands.inventory +import dev.slne.surf.api.core.messages.Colors +import dev.slne.surf.api.core.messages.adventure.buildText import dev.slne.surf.api.core.messages.adventure.text +import dev.slne.surf.api.core.util.intSetOf import dev.slne.surf.api.paper.builder.buildItem import dev.slne.surf.api.paper.builder.displayName import dev.slne.surf.api.paper.dialog.noticeDialog import dev.slne.surf.api.paper.inventory.framework.dsl.slot import dev.slne.surf.api.paper.inventory.framework.dsl.withItem import dev.slne.surf.api.paper.inventory.framework.titleBuilder -import dev.slne.surf.api.paper.inventory.framework.view.onClose -import dev.slne.surf.api.paper.inventory.framework.view.onFirstRender +import dev.slne.surf.api.paper.inventory.framework.view.* +import dev.slne.surf.api.paper.inventory.framework.view.container.dsl.blockAllSlots +import dev.slne.surf.api.paper.inventory.framework.view.container.dsl.header +import dev.slne.surf.api.paper.inventory.framework.view.container.dsl.rowText +import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewRows +import dev.slne.surf.api.paper.inventory.framework.view.settings.align.TextAlignment import dev.slne.surf.api.paper.inventory.framework.view.state.get import dev.slne.surf.api.paper.inventory.framework.view.state.increment import dev.slne.surf.api.paper.inventory.framework.view.state.mutableState -import dev.slne.surf.api.paper.inventory.framework.view.surfView import me.devnatan.inventoryframework.View import me.devnatan.inventoryframework.ViewConfigBuilder import me.devnatan.inventoryframework.ViewType import me.devnatan.inventoryframework.context.CloseContext import me.devnatan.inventoryframework.context.RenderContext +import net.kyori.adventure.text.format.TextDecoration import org.bukkit.inventory.ItemType val testInventoryViewDsl = surfView("TestInventoryView") { val counterState = mutableState(0) + settings { + rows(ViewRows.FIVE) + } + + containerDefaults { + header { + darkSpacer("Test Inventory") + } + + rowText(1, "<- Klick da mal drauf") + rowText(2, text("links", Colors.GREEN), TextAlignment.LEFT, columns = 0..2) + rowText(2, buildText { + error("rechts", TextDecoration.BOLD) + }, TextAlignment.RIGHT, columns = 6..8) + rowText(5, columns = 3..5) { + text("Guthaben: ", Colors.DARK_SPACER) + text("1.250", Colors.GREEN) + } + + blockAllSlots(ViewRows.FIVE, intSetOf(1)) + } + onFirstRender { slot(1) { onClick(counterState::increment) diff --git a/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/subcommands/inventory/TestPaginatedView.kt b/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/subcommands/inventory/TestPaginatedView.kt new file mode 100644 index 000000000..67d11ed79 --- /dev/null +++ b/surf-api-paper/surf-api-paper-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/subcommands/inventory/TestPaginatedView.kt @@ -0,0 +1,41 @@ +package dev.slne.surf.surfapi.bukkit.test.command.subcommands.inventory + +import dev.slne.surf.api.core.messages.adventure.text +import dev.slne.surf.api.paper.builder.displayName +import dev.slne.surf.api.paper.inventory.framework.dsl.withItem +import dev.slne.surf.api.paper.inventory.framework.view.container.dsl.header +import dev.slne.surf.api.paper.inventory.framework.view.containerDefaults +import dev.slne.surf.api.paper.inventory.framework.view.layoutTarget +import dev.slne.surf.api.paper.inventory.framework.view.paginatedSurfView +import dev.slne.surf.api.paper.inventory.framework.view.pagination.pagination +import dev.slne.surf.api.paper.inventory.framework.view.settings +import dev.slne.surf.api.paper.inventory.framework.view.settings.PaginationEmptyRows +import dev.slne.surf.api.paper.inventory.framework.view.settings.PaginationViewRows +import org.bukkit.inventory.ItemType + +/** Genug Einträge für mehrere Seiten, damit die Navigations-Buttons beide Zustände zeigen. */ +private val testPaginationEntries = List(40) { index -> "Eintrag ${index + 1}" } + +val testPaginatedViewDsl = paginatedSurfView("Test Pagination") { + settings { + paginationViewRows(PaginationViewRows.THREE) + paginationEmptyRows(PaginationEmptyRows.ONE) + paginationButtonsAtBottom() + } + + layoutTarget('I') + + containerDefaults { + // Die oberste Zeile bleibt leer (paginationEmptyRows), darunter die Items, unten die Buttons. + header { darkSpacer("Paginated Inventory Test (${testPaginationEntries.size} Einträge)") } + } + + pagination { + source { testPaginationEntries } + itemFactory { entry -> + withItem(ItemType.PAPER) { + displayName { text(entry) } + } + } + } +} diff --git a/surf-api-paper/surf-api-paper/api/surf-api-paper.api b/surf-api-paper/surf-api-paper/api/surf-api-paper.api index 81d3c2c6c..65ffb5527 100644 --- a/surf-api-paper/surf-api-paper/api/surf-api-paper.api +++ b/surf-api-paper/surf-api-paper/api/surf-api-paper.api @@ -1008,6 +1008,10 @@ public abstract class dev/slne/surf/api/paper/inventory/framework/view/AbstractS protected fun onViewOpen (Lme/devnatan/inventoryframework/context/OpenContext;)V protected fun onViewRender (Lme/devnatan/inventoryframework/context/RenderContext;)V protected fun onViewUpdate (Lme/devnatan/inventoryframework/context/Context;)V + protected final fun updateHeader (Lme/devnatan/inventoryframework/context/Context;Ljava/lang/String;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;)V + protected final fun updateHeader (Lme/devnatan/inventoryframework/context/Context;Lnet/kyori/adventure/text/Component;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;)V + public static synthetic fun updateHeader$default (Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfView;Lme/devnatan/inventoryframework/context/Context;Ljava/lang/String;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;ILjava/lang/Object;)V + public static synthetic fun updateHeader$default (Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfView;Lme/devnatan/inventoryframework/context/Context;Lnet/kyori/adventure/text/Component;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;ILjava/lang/Object;)V } public abstract class dev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewContext { @@ -1168,10 +1172,12 @@ public final class dev/slne/surf/api/paper/inventory/framework/view/container/Vi public fun ()V public final fun addChild (Ldev/slne/surf/api/paper/inventory/framework/view/container/component/ViewContainerComponent;)V public final fun getChildren ()Ljava/util/List; + public final fun getLastRenderedTitle ()Lnet/kyori/adventure/text/Component; public final fun hasComponentOfType (Ljava/lang/Class;)Z public final fun removeChild (Ldev/slne/surf/api/paper/inventory/framework/view/container/component/ViewContainerComponent;)V + public final fun removeChildren (Lkotlin/jvm/functions/Function1;)V public final fun removeChildrenOfType (Ljava/lang/Class;)V - public final fun render ()Lnet/kyori/adventure/text/TextComponent; + public final fun render ()Lnet/kyori/adventure/text/Component; } public abstract interface class dev/slne/surf/api/paper/inventory/framework/view/container/component/ViewContainerComponent { @@ -1188,7 +1194,10 @@ public final class dev/slne/surf/api/paper/inventory/framework/view/container/co } public final class dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewBlockCellComponent : dev/slne/surf/api/paper/inventory/framework/view/container/component/ViewContainerComponent { - public fun (II)V + public static final field Companion Ldev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewBlockCellComponent$Companion; + public synthetic fun (II)V + public fun (IILdev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;)V + public synthetic fun (IILdev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z public final fun getColumn ()I public fun getHasExactWidth ()Z @@ -1199,51 +1208,96 @@ public final class dev/slne/surf/api/paper/inventory/framework/view/container/co public fun renderComponent (Ldev/slne/surf/api/core/messages/builder/SurfComponentBuilder;)V } -public final class dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerBackHintComponent : dev/slne/surf/api/paper/inventory/framework/view/container/component/ViewContainerComponent { - public static final field INSTANCE Ldev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerBackHintComponent; +public final class dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewBlockCellComponent$Companion { + public final fun forSlot (ILdev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;)Ldev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewBlockCellComponent; + public static synthetic fun forSlot$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewBlockCellComponent$Companion;ILdev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)Ldev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewBlockCellComponent; +} + +public final class dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerRowTextComponent : dev/slne/surf/api/paper/inventory/framework/view/container/component/ViewContainerComponent { + public fun (ILjava/lang/String;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;)V + public synthetic fun (ILjava/lang/String;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ILnet/kyori/adventure/text/Component;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;)V + public synthetic fun (ILnet/kyori/adventure/text/Component;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z + public final fun getAlignment ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment; + public final fun getColumns ()Lkotlin/ranges/IntRange; public fun getHasExactWidth ()Z public fun getPositionalShift ()I + public final fun getRow ()I + public final fun getText ()Lnet/kyori/adventure/text/Component; public fun getTextureWidth ()I public fun hashCode ()I public fun renderComponent (Ldev/slne/surf/api/core/messages/builder/SurfComponentBuilder;)V - public fun toString ()Ljava/lang/String; } public final class dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerTitleComponent : dev/slne/surf/api/paper/inventory/framework/view/container/component/ViewContainerComponent { - public static final field CHAR_SIZE I - public static final field CHAR_SPACING I - public static final field CONTAINER_WIDTH I - public static final field Companion Ldev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerTitleComponent$Companion; - public static final field LEFT_SHIFT I - public static final field PADDING I - public fun (Ljava/lang/String;Lnet/kyori/adventure/key/Key;ILdev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lit/unimi/dsi/fastutil/ints/Int2IntMap;)V - public synthetic fun (Ljava/lang/String;Lnet/kyori/adventure/key/Key;ILdev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lit/unimi/dsi/fastutil/ints/Int2IntMap;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;)V + public synthetic fun (Ljava/lang/String;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lnet/kyori/adventure/text/Component;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;)V + public synthetic fun (Lnet/kyori/adventure/text/Component;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z public fun getHasExactWidth ()Z public fun getPositionalShift ()I + public final fun getTextAlignment ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment; public fun getTextureWidth ()I + public final fun getTitle ()Lnet/kyori/adventure/text/Component; public fun hashCode ()I public fun renderComponent (Ldev/slne/surf/api/core/messages/builder/SurfComponentBuilder;)V } -public final class dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerTitleComponent$Companion { -} - public final class dev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerDSLKt { public static final fun addChild (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/container/component/ViewContainerComponent;)V - public static final fun backHint (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;)V - public static final fun blockCell (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;II)V - public static final fun blockColumn (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;)V - public static final fun blockColumn (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[I)V + public static final synthetic fun backHint (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;)V + public static final fun blockAllSlots (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;)V + public static final fun blockAllSlots (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows;Lit/unimi/dsi/fastutil/ints/IntCollection;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;)V + public static synthetic fun blockAllSlots$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V + public static synthetic fun blockAllSlots$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows;Lit/unimi/dsi/fastutil/ints/IntCollection;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V + public static final synthetic fun blockCell (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;II)V + public static final fun blockCell (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;IILdev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;)V + public static synthetic fun blockCell$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;IILdev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V + public static final synthetic fun blockColumn (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;)V + public static final fun blockColumn (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;ILdev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;)V + public static final synthetic fun blockColumn (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[I)V + public static final fun blockColumn (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[IILdev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;)V + public static synthetic fun blockColumn$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;ILdev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V public static synthetic fun blockColumn$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;ILjava/lang/Object;)V - public static final fun blockRow (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;)V - public static final fun blockRow (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[I)V + public static synthetic fun blockColumn$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[IILdev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V + public static final synthetic fun blockRow (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;)V + public static final fun blockRow (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;)V + public static final synthetic fun blockRow (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[I)V + public static final fun blockRow (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[ILdev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;)V public static synthetic fun blockRow$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;ILjava/lang/Object;)V + public static synthetic fun blockRow$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILit/unimi/dsi/fastutil/ints/IntCollection;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V + public static synthetic fun blockRow$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;I[ILdev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V + public static final fun blockSlot (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILdev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;)V + public static synthetic fun blockSlot$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILdev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ILjava/lang/Object;)V + public static final fun clearRowText (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;I)V + public static final fun clearRowTexts (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;)V public static final fun hasComponentOfType (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ljava/lang/Class;)Z - public static final fun header (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;Ljava/lang/String;)V + public static final fun header (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/jvm/functions/Function1;)V + public static final synthetic fun header (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;Ljava/lang/String;)V + public static final fun header (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;Ljava/lang/String;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;)V + public static final fun header (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;Lnet/kyori/adventure/text/Component;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;)V + public static synthetic fun header$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static synthetic fun header$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;Ljava/lang/String;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;ILjava/lang/Object;)V + public static synthetic fun header$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;Lnet/kyori/adventure/text/Component;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;ILjava/lang/Object;)V public static final fun removeChild (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/container/component/ViewContainerComponent;)V public static final fun removeChildrenOfType (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ljava/lang/Class;)V + public static final fun rowText (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILjava/lang/String;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;)V + public static final fun rowText (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILnet/kyori/adventure/text/Component;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;)V + public static final fun rowText (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;ILdev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Lkotlin/jvm/functions/Function1;)V + public static final fun rowText (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;ILjava/lang/String;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;)V + public static final fun rowText (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;ILnet/kyori/adventure/text/Component;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;)V + public static synthetic fun rowText$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILjava/lang/String;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;ILjava/lang/Object;)V + public static synthetic fun rowText$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;ILnet/kyori/adventure/text/Component;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;ILjava/lang/Object;)V + public static synthetic fun rowText$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;ILdev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static synthetic fun rowText$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;ILjava/lang/String;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;ILjava/lang/Object;)V + public static synthetic fun rowText$default (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;ILnet/kyori/adventure/text/Component;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lkotlin/ranges/IntRange;ILjava/lang/Object;)V + public static final fun rowTextAt (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;IILjava/lang/String;)V + public static final fun rowTextAt (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;Ldev/slne/surf/api/paper/inventory/framework/view/AbstractSurfViewRef;IILnet/kyori/adventure/text/Component;)V + public static final fun unblockAllSlots (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;)V + public static final fun unblockCell (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;II)V + public static final fun unblockSlot (Ldev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext;I)V } public final class dev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerModificationContext { @@ -1368,6 +1422,15 @@ public final class dev/slne/surf/api/paper/inventory/framework/view/pagination/P public static final fun pagination (Ldev/slne/surf/api/paper/inventory/framework/view/PaginatedSurfViewContext;Lkotlin/jvm/functions/Function1;)V } +public abstract interface class dev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator { + public static final field Companion Ldev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator$Companion; + public abstract fun render (II)Lnet/kyori/adventure/text/Component; +} + +public final class dev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator$Companion { + public final fun getDefault ()Ldev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator; +} + public abstract interface class dev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationSourceType { } @@ -1398,31 +1461,51 @@ public final class dev/slne/surf/api/paper/inventory/framework/view/pagination/P public final class dev/slne/surf/api/paper/inventory/framework/view/settings/PaginatedViewSettings : dev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettings { public fun ()V - public fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition;)V - public synthetic fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows;Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition;Ldev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator;Lnet/kyori/adventure/sound/Sound;)V + public synthetic fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows;Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition;Ldev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator;Lnet/kyori/adventure/sound/Sound;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lnet/kyori/adventure/key/Key; + public final fun component10 ()Z + public final fun component11 ()Z + public final fun component12 ()Z + public final fun component13 ()Z + public final fun component14 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows; + public final fun component15 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows; + public final fun component16 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition; + public final fun component17 ()Ldev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator; + public final fun component18 ()Lnet/kyori/adventure/sound/Sound; public final fun component2 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment; - public final fun component3 ()Z - public final fun component4 ()Z - public final fun component5 ()Z - public final fun component6 ()Z + public final fun component3 ()Lnet/kyori/adventure/text/format/TextColor; + public final fun component4 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public final fun component5 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public final fun component6 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry; public final fun component7 ()Z - public final fun component8 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows; - public final fun component9 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition; - public final fun copy (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginatedViewSettings; - public static synthetic fun copy$default (Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginatedViewSettings;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition;ILjava/lang/Object;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginatedViewSettings; + public final fun component8 ()Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; + public final fun component9 ()Z + public final fun copy (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows;Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition;Ldev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator;Lnet/kyori/adventure/sound/Sound;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginatedViewSettings; + public static synthetic fun copy$default (Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginatedViewSettings;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows;Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows;Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition;Ldev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator;Lnet/kyori/adventure/sound/Sound;ILjava/lang/Object;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginatedViewSettings; public fun equals (Ljava/lang/Object;)Z + public fun getBackgroundGlyph ()Z public fun getCancelOnClick ()Z public fun getCancelOnDrag ()Z public fun getCancelOnDrop ()Z public fun getCancelOnPickup ()Z public fun getFont ()Lnet/kyori/adventure/key/Key; + public fun getHeaderFontMetrics ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public fun getHeaderGeometry ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry; public fun getHeaderTextAlignment ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment; + public fun getHeaderTextColor ()Lnet/kyori/adventure/text/format/TextColor; public fun getNavigateBackOnOutsideClick ()Z public final fun getPaginationButtonPosition ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition; + public final fun getPaginationContentRowCount ()I + public final fun getPaginationEmptyRows ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows; + public final fun getPaginationPageIndicator ()Ldev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator; + public final fun getPaginationSwitchSound ()Lnet/kyori/adventure/sound/Sound; public final fun getPaginationViewRows ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows; + public fun getRowFontMetrics ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public fun getRowFonts ()Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; public fun getRows ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows; public fun hashCode ()I + public fun rowFont (I)Lnet/kyori/adventure/key/Key; public fun toString ()Ljava/lang/String; } @@ -1434,69 +1517,202 @@ public final class dev/slne/surf/api/paper/inventory/framework/view/settings/Pag public static fun values ()[Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition; } +public final class dev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows : java/lang/Enum { + public static final field Companion Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows$Companion; + public static final field NONE Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows; + public static final field ONE Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows; + public static final field THREE Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows; + public static final field TWO Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getRows ()I + public static fun valueOf (Ljava/lang/String;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows; + public static fun values ()[Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows; +} + +public final class dev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows$Companion { + public final fun byRows (I)Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows; + public final fun byRowsOrNull (I)Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows; +} + public final class dev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows : java/lang/Enum { + public static final field FIVE Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows; public static final field FOUR Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows; public static final field ONE Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows; public static final field THREE Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows; public static final field TWO Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows; public final fun getActualRows ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows; + public final fun getContentRows ()I public static fun getEntries ()Lkotlin/enums/EnumEntries; - public final fun getPaginationContentRows ()Lkotlin/ranges/IntRange; + public final synthetic fun getPaginationContentRows ()Lkotlin/ranges/IntRange; public static fun valueOf (Ljava/lang/String;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows; public static fun values ()[Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows; } public final class dev/slne/surf/api/paper/inventory/framework/view/settings/SimpleViewSettings : dev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettings { public fun ()V - public fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows;)V - public synthetic fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows;)V + public synthetic fun (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lnet/kyori/adventure/key/Key; + public final fun component10 ()Z + public final fun component11 ()Z + public final fun component12 ()Z + public final fun component13 ()Z + public final fun component14 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows; public final fun component2 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment; - public final fun component3 ()Z - public final fun component4 ()Z - public final fun component5 ()Z - public final fun component6 ()Z + public final fun component3 ()Lnet/kyori/adventure/text/format/TextColor; + public final fun component4 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public final fun component5 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public final fun component6 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry; public final fun component7 ()Z - public final fun component8 ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows; - public final fun copy (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/SimpleViewSettings; - public static synthetic fun copy$default (Ldev/slne/surf/api/paper/inventory/framework/view/settings/SimpleViewSettings;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows;ILjava/lang/Object;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/SimpleViewSettings; + public final fun component8 ()Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; + public final fun component9 ()Z + public final fun copy (Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/SimpleViewSettings; + public static synthetic fun copy$default (Ldev/slne/surf/api/paper/inventory/framework/view/settings/SimpleViewSettings;Lnet/kyori/adventure/key/Key;Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;Lnet/kyori/adventure/text/format/TextColor;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;ZLit/unimi/dsi/fastutil/ints/Int2ObjectMap;ZZZZZLdev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows;ILjava/lang/Object;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/SimpleViewSettings; public fun equals (Ljava/lang/Object;)Z + public fun getBackgroundGlyph ()Z public fun getCancelOnClick ()Z public fun getCancelOnDrag ()Z public fun getCancelOnDrop ()Z public fun getCancelOnPickup ()Z public fun getFont ()Lnet/kyori/adventure/key/Key; + public fun getHeaderFontMetrics ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public fun getHeaderGeometry ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry; public fun getHeaderTextAlignment ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment; + public fun getHeaderTextColor ()Lnet/kyori/adventure/text/format/TextColor; public fun getNavigateBackOnOutsideClick ()Z + public fun getRowFontMetrics ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public fun getRowFonts ()Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; public fun getRows ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows; public fun hashCode ()I + public fun rowFont (I)Lnet/kyori/adventure/key/Key; public fun toString ()Ljava/lang/String; } public abstract interface class dev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettings { + public abstract fun getBackgroundGlyph ()Z public abstract fun getCancelOnClick ()Z public abstract fun getCancelOnDrag ()Z public abstract fun getCancelOnDrop ()Z public abstract fun getCancelOnPickup ()Z public abstract fun getFont ()Lnet/kyori/adventure/key/Key; + public abstract fun getHeaderFontMetrics ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public abstract fun getHeaderGeometry ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry; public abstract fun getHeaderTextAlignment ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment; + public abstract fun getHeaderTextColor ()Lnet/kyori/adventure/text/format/TextColor; public abstract fun getNavigateBackOnOutsideClick ()Z + public abstract fun getRowFontMetrics ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public abstract fun getRowFonts ()Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; public abstract fun getRows ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows; + public fun rowFont (I)Lnet/kyori/adventure/key/Key; +} + +public final class dev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettings$DefaultImpls { + public static fun rowFont (Ldev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettings;I)Lnet/kyori/adventure/key/Key; } public final class dev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettingsDefaults { + public static final field DEFAULT_BACKGROUND_GLYPH Z public static final field DEFAULT_CANCEL_ON_CLICK Z public static final field DEFAULT_CANCEL_ON_DRAG Z public static final field DEFAULT_CANCEL_ON_DROP Z public static final field DEFAULT_CANCEL_ON_PICKUP Z public static final field DEFAULT_NAVIGATE_BACK_ON_CLOSE Z public static final field INSTANCE Ldev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettingsDefaults; + public final fun getASCENDED_HEADER_FONT ()Lnet/kyori/adventure/key/Key; public final fun getDEFAULT_HEADER_ALIGNMENT ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment; public final fun getDEFAULT_HEADER_FONT ()Lnet/kyori/adventure/key/Key; + public final fun getDEFAULT_HEADER_FONT_METRICS ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public final fun getDEFAULT_HEADER_GEOMETRY ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry; + public final fun getDEFAULT_HEADER_TEXT_COLOR ()Lnet/kyori/adventure/text/format/TextColor; public final fun getDEFAULT_MENU_FONT ()Lnet/kyori/adventure/key/Key; public final fun getDEFAULT_PAGINATION_BUTTON_POSITION ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition; + public final fun getDEFAULT_PAGINATION_EMPTY_ROWS ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows; + public final fun getDEFAULT_PAGINATION_PAGE_INDICATOR ()Ldev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator; + public final fun getDEFAULT_PAGINATION_SWITCH_SOUND ()Lnet/kyori/adventure/sound/Sound; public final fun getDEFAULT_PAGINATION_VIEW_ROWS ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows; + public final fun getDEFAULT_ROW_FONTS ()Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; + public final fun getDEFAULT_ROW_FONT_METRICS ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public final fun getDEFAULT_ROW_TEXT_ALIGNMENT ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment; + public final fun getDEFAULT_ROW_TEXT_COLUMNS ()Lkotlin/ranges/IntRange; public final fun getDEFAULT_VIEW_ROWS ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows; + public final fun rowFont (I)Lnet/kyori/adventure/key/Key; +} + +public final class dev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics { + public static final field Companion Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics$Companion; + public static final field MENU_CHAR_SIZE I + public static final field MENU_CHAR_SPACING I + public static final field MENU_SPACE_SIZE I + public fun ()V + public fun (IIZZLit/unimi/dsi/fastutil/ints/Int2IntMap;)V + public synthetic fun (IIZZLit/unimi/dsi/fastutil/ints/Int2IntMap;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun advance (IZ)I + public static synthetic fun advance$default (Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;IZILjava/lang/Object;)I + public final fun component1 ()I + public final fun component2 ()I + public final fun component3 ()Z + public final fun component4 ()Z + public final fun component5 ()Lit/unimi/dsi/fastutil/ints/Int2IntMap; + public final fun copy (IIZZLit/unimi/dsi/fastutil/ints/Int2IntMap;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public static synthetic fun copy$default (Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;IIZZLit/unimi/dsi/fastutil/ints/Int2IntMap;ILjava/lang/Object;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public fun equals (Ljava/lang/Object;)Z + public final fun getCharSize ()I + public final fun getCharSpacing ()I + public final fun getCharWidths ()Lit/unimi/dsi/fastutil/ints/Int2IntMap; + public final fun getSpaceAsShift ()Z + public final fun getSpaceShift ()Ljava/lang/Integer; + public final fun getUppercase ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics$Companion { + public final fun getSURF_MENU ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public final fun getVANILLA ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public final fun getVANILLA_ROW ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; +} + +public final class dev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry { + public static final field Companion Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry$Companion; + public static final field DEFAULT_ROW_TEXT_PADDING I + public static final field DEFAULT_SLOT_GLYPH_INSET I + public static final field DEFAULT_SLOT_ORIGIN_X I + public static final field DEFAULT_TITLE_LEFT_SHIFT I + public static final field DEFAULT_TITLE_PADDING I + public static final field DEFAULT_TITLE_WIDTH I + public fun ()V + public fun (IIIIIII)V + public synthetic fun (IIIIIIIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun columnShift (I)I + public final fun columnSpanOptions (Lkotlin/ranges/IntRange;Lit/unimi/dsi/fastutil/ints/Int2IntMap;II)Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignmentOptions; + public static synthetic fun columnSpanOptions$default (Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;Lkotlin/ranges/IntRange;Lit/unimi/dsi/fastutil/ints/Int2IntMap;IIILjava/lang/Object;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignmentOptions; + public final fun component1 ()I + public final fun component2 ()I + public final fun component3 ()I + public final fun component4 ()I + public final fun component5 ()I + public final fun component6 ()I + public final fun component7 ()I + public final fun copy (IIIIIII)Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry; + public static synthetic fun copy$default (Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;IIIIIIIILjava/lang/Object;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry; + public fun equals (Ljava/lang/Object;)Z + public final fun getRowTextPadding ()I + public final fun getSlotAreaWidth ()I + public final fun getSlotGlyphInset ()I + public final fun getSlotOriginX ()I + public final fun getSlotSize ()I + public final fun getTitleLeftShift ()I + public final fun getTitlePadding ()I + public final fun getTitleWidth ()I + public fun hashCode ()I + public final fun slotGlyphShift (I)I + public final fun titleOptions (Lit/unimi/dsi/fastutil/ints/Int2IntMap;II)Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignmentOptions; + public static synthetic fun titleOptions$default (Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;Lit/unimi/dsi/fastutil/ints/Int2IntMap;IIILjava/lang/Object;)Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignmentOptions; + public fun toString ()Ljava/lang/String; +} + +public final class dev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry$Companion { + public final fun getDEFAULT ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry; } public final class dev/slne/surf/api/paper/inventory/framework/view/settings/ViewRows : java/lang/Enum { @@ -1564,10 +1780,20 @@ public final class dev/slne/surf/api/paper/inventory/framework/view/settings/bui public fun build ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginatedViewSettings; public synthetic fun build ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettings; public final fun getPaginationButtonPosition ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition; + public final fun getPaginationEmptyRows ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows; + public final fun getPaginationPageIndicator ()Ldev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator; + public final fun getPaginationSwitchSound ()Lnet/kyori/adventure/sound/Sound; public final fun getPaginationViewRows ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows; + public final fun noPaginationEmptyRows ()V + public final fun noPaginationPageIndicator ()V + public final fun noPaginationSwitchSound ()V public final fun paginationButtonPosition (Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationButtonPosition;)V public final fun paginationButtonsAtBottom ()V public final fun paginationButtonsAtTop ()V + public final fun paginationEmptyRows (I)V + public final fun paginationEmptyRows (Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows;)V + public final fun paginationPageIndicator (Ldev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator;)V + public final fun paginationSwitchSound (Lnet/kyori/adventure/sound/Sound;)V public final fun paginationViewRows (Ldev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows;)V } @@ -1589,6 +1815,8 @@ public final class dev/slne/surf/api/paper/inventory/framework/view/settings/bui } public abstract class dev/slne/surf/api/paper/inventory/framework/view/settings/builder/SurfViewSettingsBuilder { + public final fun backgroundGlyph (Z)V + public static synthetic fun backgroundGlyph$default (Ldev/slne/surf/api/paper/inventory/framework/view/settings/builder/SurfViewSettingsBuilder;ZILjava/lang/Object;)V public abstract fun build ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettings; public final fun cancelAllInteractions ()V public final fun cancelOnClick (Z)V @@ -1600,16 +1828,29 @@ public abstract class dev/slne/surf/api/paper/inventory/framework/view/settings/ public final fun cancelOnPickup (Z)V public static synthetic fun cancelOnPickup$default (Ldev/slne/surf/api/paper/inventory/framework/view/settings/builder/SurfViewSettingsBuilder;ZILjava/lang/Object;)V public final fun font (Lnet/kyori/adventure/key/Key;)V + public final fun getBackgroundGlyph ()Z public final fun getCancelOnClick ()Z public final fun getCancelOnDrag ()Z public final fun getCancelOnDrop ()Z public final fun getCancelOnPickup ()Z public final fun getFont ()Lnet/kyori/adventure/key/Key; + public final fun getHeaderFontMetrics ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public final fun getHeaderGeometry ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry; public final fun getHeaderTextAlignment ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment; + public final fun getHeaderTextColor ()Lnet/kyori/adventure/text/format/TextColor; public final fun getNavigateBackOnOutsideClick ()Z + public final fun getRowFontMetrics ()Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics; + public final fun getRowFonts ()Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; + public final fun headerFontMetrics (Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;)V + public final fun headerGeometry (Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry;)V + public final fun headerGeometry (Lkotlin/jvm/functions/Function1;)V public final fun headerTextAlignment (Ldev/slne/surf/api/paper/inventory/framework/view/settings/align/TextAlignment;)V + public final fun headerTextColor (Lnet/kyori/adventure/text/format/TextColor;)V public final fun navigateBackOnOutsideClick (Z)V public static synthetic fun navigateBackOnOutsideClick$default (Ldev/slne/surf/api/paper/inventory/framework/view/settings/builder/SurfViewSettingsBuilder;ZILjava/lang/Object;)V + public final fun rowFont (ILnet/kyori/adventure/key/Key;)V + public final fun rowFontMetrics (Ldev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics;)V + public final fun rowFonts (Lit/unimi/dsi/fastutil/ints/Int2ObjectMap;)V } public abstract interface class dev/slne/surf/api/paper/inventory/framework/view/state/DeferredState { diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/AbstractSurfView.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/AbstractSurfView.kt index 8a40c5718..6be4648ab 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/AbstractSurfView.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/AbstractSurfView.kt @@ -6,13 +6,15 @@ import dev.slne.surf.api.paper.inventory.framework.view.container.component.comp import dev.slne.surf.api.paper.inventory.framework.view.container.component.components.ViewContainerTitleComponent import dev.slne.surf.api.paper.inventory.framework.view.container.dsl.ViewContainerModificationContext import dev.slne.surf.api.paper.inventory.framework.view.container.dsl.addChild -import dev.slne.surf.api.paper.inventory.framework.view.container.dsl.backHint +import dev.slne.surf.api.paper.inventory.framework.view.container.dsl.removeChildrenOfType import dev.slne.surf.api.paper.inventory.framework.view.settings.SimpleViewSettings import dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettings +import dev.slne.surf.api.paper.inventory.framework.view.settings.align.TextAlignment import me.devnatan.inventoryframework.View import me.devnatan.inventoryframework.ViewConfigBuilder import me.devnatan.inventoryframework.ViewType import me.devnatan.inventoryframework.context.* +import net.kyori.adventure.text.Component /** * Abstract base class for all Surf inventory views built on top of the inventory framework. @@ -24,10 +26,15 @@ import me.devnatan.inventoryframework.context.* * and settings (cancel behaviours, row count, type) are always applied before delegating * to the subclass hooks. * - * The view's title is composed from a [ViewContainer] that holds a glyph background component - * and an aligned title text component. The container can be modified at any time via + * The view's title is composed from a [ViewContainer] that holds the header components: the + * aligned title text plus whatever the view adds on top of it. The container can be modified at any time via * [modifyContainer]. * + * A title set on the view's config instead - `onInit { title(...) }`, or `modifyConfig { title(...) }` + * inside [onViewOpen] - is adopted as the container's header rather than replacing the rendered + * container, so the header texture, the alignment and every other header element survive it. From + * any other context use [updateHeader], which does the same. + * * You can directly create a subclass or use the dsl functions ([surfView] or [paginatedSurfView]) instead. * * ```kotlin @@ -136,26 +143,121 @@ abstract class AbstractSurfView( } } - private fun applyContainerDefaults(context: Context) { - modifyContainer(context) { - addChild(ViewContainerGlyphComponent(settings.rows)) - addChild( - ViewContainerTitleComponent( - title = defaultHeader, - font = settings.font, - charSpacing = ViewContainerTitleComponent.CHAR_SPACING, - textAlignment = settings.headerTextAlignment - ) + /** + * Replaces the header text of the view, keeping every other container component - the + * background glyph, row texts, the pagination overlay - in place. + * + * This is the API to change a view's title with: setting the title directly on the view's + * config (`modifyConfig { title(...) }`) would replace the whole rendered container, dropping + * the header texture along with the alignment shift, which leaves the text unaligned and every + * other header element gone. + * + * ```kotlin + * override fun onViewClick(click: SlotClickContext) { + * updateHeader(click, text("Seite 2", Colors.GOLD)) + * } + * ``` + * + * @param context the context used to propagate the title change to viewers + * @param header the title component to render + * @param alignment horizontal alignment of the title; defaults to the alignment the current + * title is rendered with, or to [SurfViewSettings.headerTextAlignment] if there is none + */ + protected fun updateHeader( + context: Context, + header: Component, + alignment: TextAlignment? = null, + ) = modifyContainer(context) { applyHeader(header, alignment) } + + /** + * Replaces the header text of the view with the plain, white string [header]. + * + * @param context the context used to propagate the title change to viewers + * @param header the plain-text title to render + * @param alignment horizontal alignment of the title; defaults to the alignment the current + * title is rendered with, or to [SurfViewSettings.headerTextAlignment] if there is none + * @see updateHeader + */ + protected fun updateHeader( + context: Context, + header: String, + alignment: TextAlignment? = null, + ) = updateHeader(context, Component.text(header), alignment) + + /** + * Swaps the container's [ViewContainerTitleComponent] for one rendering [header]. + * + * Only the title component is replaced, so every other component the container holds survives + * the title change. When [alignment] is `null` the alignment of the title currently rendered is + * kept, falling back to [SurfViewSettings.headerTextAlignment] if the container has no title + * yet. + */ + context(ctx: ViewContainerModificationContext) + internal fun applyHeader(header: Component, alignment: TextAlignment? = null) { + val currentAlignment = ctx.container.children + .filterIsInstance() + .firstOrNull() + ?.textAlignment + + removeChildrenOfType() + + addChild( + ViewContainerTitleComponent( + title = header, + font = settings.font, + textAlignment = alignment ?: currentAlignment ?: settings.headerTextAlignment, + geometry = settings.headerGeometry, + defaultColor = settings.headerTextColor, + metrics = settings.headerFontMetrics ) + ) + } - if (settings.navigateBackOnOutsideClick) { - backHint() + private fun applyContainerDefaults(context: OpenContext) { + // A title set on the config before the container exists - via `onInit { title(...) }` or by + // whoever opened the view - is the header the view was asked to show, so it wins over the + // one the view was constructed with. + val header = context.configuredTitle() ?: Component.text(defaultHeader) + + modifyContainer(context) { + if (settings.backgroundGlyph) { + addChild(ViewContainerGlyphComponent(settings.rows)) } + applyHeader(header) + containerDefaults() } } + /** + * Adopts a title that was set on the config from outside the container - `modifyConfig + * { title(...) }` inside [onViewOpen] being the usual case - as the container's header. + * + * Without this, that title would replace the rendered container as a whole: the header texture, + * the alignment shift and every row text would be gone, and the raw text would sit wherever the + * client happens to draw an inventory title. + */ + private fun adoptExternalHeader(open: OpenContext) { + val container = containerState.get(open) + val header = open.configuredTitle() ?: return + + // The container itself put its own render on the config, that one is not external. + if (header == container.lastRenderedTitle) return + + modifyContainer(open) { applyHeader(header) } + } + + /** + * Returns the title currently set on this context's config as a [Component], or `null` if no + * title is set. Non-component titles (e.g. plain strings) are wrapped as-is. + */ + private fun OpenContext.configuredTitle(): Component? = when (val title = config.title) { + null -> null + is Component -> title + else -> Component.text(title.toString()) + } + /** * Hook called within [applyContainerDefaults] to allow subclasses to add their own * container components after the standard glyph and title components have been added. @@ -184,6 +286,7 @@ abstract class AbstractSurfView( recordHistory(open) applyContainerDefaults(open) onViewOpen(open) + adoptExternalHeader(open) } private fun recordHistory(open: OpenContext) { diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/ViewContainer.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/ViewContainer.kt index 858828f2a..2eec1d660 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/ViewContainer.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/ViewContainer.kt @@ -4,6 +4,7 @@ import dev.slne.surf.api.core.inventory.framework.internal.appendShiftedComponen import dev.slne.surf.api.core.messages.adventure.buildText import dev.slne.surf.api.paper.inventory.framework.view.container.component.ViewContainerComponent import dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettingsDefaults +import net.kyori.adventure.text.Component import java.util.concurrent.CopyOnWriteArrayList /** @@ -28,6 +29,17 @@ internal class ViewContainer { private val _children = CopyOnWriteArrayList() val children: List get() = _children + /** + * The title [Component] the last [render] call produced, or `null` while the container has + * never been rendered. + * + * Kept so that a title arriving from outside the container - e.g. one set through + * `modifyConfig { title(...) }` - can be told apart from the one the container itself put on + * the view's config. + */ + var lastRenderedTitle: Component? = null + private set + fun addChild(component: ViewContainerComponent) { _children.addIfAbsent(component) } @@ -48,11 +60,15 @@ internal class ViewContainer { _children.removeIf { type.isInstance(it) } } + fun removeChildren(predicate: (ViewContainerComponent) -> Boolean) { + _children.removeIf(predicate) + } + inline fun removeChildrenOfType() { removeChildrenOfType(T::class.java) } - fun render() = buildText { + fun render(): Component = buildText { // Components that only estimate their textureWidth leave the cursor slightly off the // container origin, so render them after every exact one instead of letting the following // components inherit that drift. partition() keeps the relative insertion order of both. @@ -67,5 +83,5 @@ internal class ViewContainer { } font(SurfViewSettingsDefaults.DEFAULT_MENU_FONT) - } + }.also { lastRenderedTitle = it } } \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewBlockCellComponent.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewBlockCellComponent.kt index a1112a2b1..c81280b81 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewBlockCellComponent.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewBlockCellComponent.kt @@ -1,31 +1,63 @@ package dev.slne.surf.api.paper.inventory.framework.view.container.component.components +import dev.slne.surf.api.core.inventory.framework.internal.ViewSlotGeometry import dev.slne.surf.api.core.messages.Colors import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder import dev.slne.surf.api.core.util.freeze import dev.slne.surf.api.paper.inventory.framework.view.container.component.ViewContainerComponent +import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewHeaderGeometry import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap /** * A [ViewContainerComponent] that renders a single inventory cell block overlay at the given * [column] and [row] position. * - * Each instance renders a 18×18 pixel glyph (plus 1 pixel spacing = 19 pixels wide) that - * visually "blocks" a specific cell in the inventory header texture. The glyph character is - * looked up from [BlockRow] based on the [row] parameter. + * Each instance renders one slot-sized glyph (plus 1 pixel spacing) that visually "blocks" a cell + * of the inventory. The horizontal position is computed from [ViewHeaderGeometry]; the row cannot + * be — a glyph's vertical position is baked into the `ascent` of its font provider in the resource + * pack — so the glyph character itself is picked per [row] from [BlockRow]. * - * Typically used via the DSL helper [dev.slne.surf.api.paper.api.inventory.framework.view.container.dsl.blockCell] - * or [dev.slne.surf.api.paper.api.inventory.framework.view.container.dsl.blockRow]. + * Blocking a cell is **purely cosmetic**: it draws a texture into the inventory title and touches + * neither the slot's contents nor its click handling, so items can still be placed into and taken + * out of a blocked slot exactly as before (subject to the view's usual + * [cancelOnClick][dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettings.cancelOnClick] + * settings). + * + * Typically used via the DSL helpers + * [blockCell][dev.slne.surf.api.paper.inventory.framework.view.container.dsl.blockCell], + * [blockSlot][dev.slne.surf.api.paper.inventory.framework.view.container.dsl.blockSlot], + * [blockRow][dev.slne.surf.api.paper.inventory.framework.view.container.dsl.blockRow] or + * [blockAllSlots][dev.slne.surf.api.paper.inventory.framework.view.container.dsl.blockAllSlots]. * * @property column the zero-based column index (0–8) of the cell to block * @property row the one-based row index (1–6) of the cell to block + * @param geometry the header [ViewHeaderGeometry] describing the slot grid */ class ViewBlockCellComponent( val column: Int, - val row: Int + val row: Int, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, ) : ViewContainerComponent { - override val textureWidth = 18 + 1 // 1 pixel for spacing - override val positionalShift = 1 + column * (textureWidth - 1) + + /** @suppress binary compatibility, blocks the cell with the default geometry */ + @Deprecated( + "Binary compatibility", + ReplaceWith("ViewBlockCellComponent(column, row)"), + DeprecationLevel.HIDDEN + ) + constructor(column: Int, row: Int) : this(column, row, ViewHeaderGeometry.DEFAULT) + + init { + require(column in 0 until ViewSlotGeometry.COLUMNS) { + "Column must be in 0..${ViewSlotGeometry.COLUMNS - 1}, was $column" + } + require(row in 1..ViewSlotGeometry.MAX_ROWS) { + "Row must be in 1..${ViewSlotGeometry.MAX_ROWS}, was $row" + } + } + + override val textureWidth = geometry.slotSize + 1 // 1 pixel for spacing + override val positionalShift = geometry.slotGlyphShift(column) override fun SurfComponentBuilder.renderComponent() { val blockRow = BlockRow.fromRow(row) ?: return @@ -49,10 +81,31 @@ class ViewBlockCellComponent( return result } + companion object { + /** + * Returns the component that blocks the cell at the zero-based [slot] index, counted + * left-to-right and then top-to-bottom just like the inventory's own slot numbering. + * + * @param slot the zero-based slot index (0–53) + * @param geometry the header [ViewHeaderGeometry] describing the slot grid + */ + fun forSlot( + slot: Int, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + ): ViewBlockCellComponent = ViewBlockCellComponent( + column = ViewSlotGeometry.columnOf(slot), + row = ViewSlotGeometry.rowOf(slot), + geometry = geometry + ) + } + /** * Maps a row number to its corresponding block-cell glyph character. * + * One glyph per row: the textures are identical, but each one is declared with the `ascent` + * that drops it onto its row, which is the only way to position a header texture vertically. + * * @property glyph the Unicode character in the resource-pack font that draws the cell overlay * @property row the one-based inventory row this glyph corresponds to (1–6) */ @@ -90,4 +143,4 @@ class ViewBlockCellComponent( fun fromRow(row: Int): BlockRow? = index[row] } } -} \ No newline at end of file +} diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerBackHintComponent.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerBackHintComponent.kt deleted file mode 100644 index bbe510610..000000000 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerBackHintComponent.kt +++ /dev/null @@ -1,23 +0,0 @@ -package dev.slne.surf.api.paper.inventory.framework.view.container.component.components - -import dev.slne.surf.api.core.messages.Colors -import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder -import dev.slne.surf.api.paper.inventory.framework.view.container.component.ViewContainerComponent - -/** - * A [ViewContainerComponent] that renders a "navigate back" hint glyph in the inventory header. - * - * This singleton component is added automatically when - * [SurfViewSettings.navigateBackOnOutsideClick][dev.slne.surf.api.paper.api.inventory.framework.view.settings.SurfViewSettings.navigateBackOnOutsideClick] - * is `true`. It displays a small arrow icon at a fixed position (shift of −21 pixels, - * texture width 15 pixels) to hint to the player that clicking outside the inventory navigates back. - */ -data object ViewContainerBackHintComponent : ViewContainerComponent { - override val positionalShift = -22 - override val textureWidth = 15 + 2 // 2 pixel for spacing - - override fun SurfComponentBuilder.renderComponent() { - text("ꐷ") - color(Colors.WHITE) - } -} \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerGlyphComponent.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerGlyphComponent.kt index ee20b4fb4..98854174c 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerGlyphComponent.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerGlyphComponent.kt @@ -9,15 +9,16 @@ import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewRows * A [ViewContainerComponent] that renders the inventory background glyph for the given [rows]. * * Each [ViewRows] value has a corresponding special-font character that renders the correct - * background texture for that row count. The glyph is positioned with a fixed negative shift - * of 48 pixels (moving the cursor to the left edge of the texture area) and has a texture - * width of 223 pixels. + * background texture for that row count. The glyph is positioned with a fixed negative shift of 48 + * pixels (moving the cursor to the left edge of the texture area) and advances the cursor by 222 + * pixels - the advance measured off the glyph sheet, which has to match exactly or every component + * rendered afterwards is shifted. * * @property rows the [ViewRows] value whose glyph character should be rendered */ internal class ViewContainerGlyphComponent(val rows: ViewRows) : ViewContainerComponent { override val positionalShift = -48 - override val textureWidth = 223 + override val textureWidth = 222 override fun SurfComponentBuilder.renderComponent() { text(rows.glyph) diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerRowTextComponent.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerRowTextComponent.kt new file mode 100644 index 000000000..6410e986d --- /dev/null +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerRowTextComponent.kt @@ -0,0 +1,142 @@ +package dev.slne.surf.api.paper.inventory.framework.view.container.component.components + +import dev.slne.surf.api.core.inventory.framework.internal.ViewSlotGeometry +import dev.slne.surf.api.core.inventory.framework.internal.layoutViewText +import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder +import dev.slne.surf.api.paper.inventory.framework.view.container.component.ViewContainerComponent +import dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettingsDefaults +import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewFontMetrics +import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewHeaderGeometry +import dev.slne.surf.api.paper.inventory.framework.view.settings.align.TextAlignment +import net.kyori.adventure.key.Key +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor + +/** + * A [ViewContainerComponent] that renders a line of text inside a slot [row] of the inventory, + * vertically centred in the row. + * + * ### How a row is addressed + * + * The header is drawn as glyphs inside the inventory title, and a glyph's vertical position is + * baked into the `ascent` of its font provider in the resource pack — the server cannot move it + * down. The **row** therefore comes from the [font]: one font per slot row, each declaring the + * `ascent` that centres its glyphs vertically in that row (`surf:menu_font_row_1` … + * `surf:menu_font_row_6` by default, see [SurfViewSettingsDefaults.rowFont]). Only the + * **horizontal** position is computed here, from [alignment] within the span of slot [columns]. + * + * ### Styling and metrics + * + * [text] is a full Adventure [Component], so colours, gradients, decorations and hover events all + * survive; parts without a colour are rendered in [defaultColor]. Because the text is positioned by + * shifting the render cursor, its width has to be known up front, and that width comes from + * [metrics] — which also applies the spacing, the casing and the space handling that font needs. + * [metrics] therefore has to describe the font passed as [font]; the default + * [SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS] describes the row fonts of the resource pack, + * which copy the client font glyph sheets onto their row. + * + * ### Multiple texts per row + * + * Components are equal when they render the same component with the same font, alignment and + * column span, so a row can carry several independent texts — for example one aligned left over the + * first three columns and one aligned right over the last three. + * + * @param row the one-based slot row the text is rendered in (`1`–`6`); used for identity and + * validation, the actual vertical position comes from [font] + * @param text the component to render + * @param font the row font whose `ascent` centres the text in [row] + * @param alignment horizontal [TextAlignment] within the [columns] span + * @param columns the zero-based, inclusive span of slot columns the text is aligned in + * @param geometry the header [ViewHeaderGeometry] describing the slot grid + * @param defaultColor the colour applied to parts of [text] that do not set one + * @param metrics the [ViewFontMetrics] of [font] + */ +class ViewContainerRowTextComponent( + val row: Int, + val text: Component, + private val font: Key, + val alignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_ALIGNMENT, + val columns: IntRange = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_COLUMNS, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + private val defaultColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + metrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS, +) : ViewContainerComponent { + + /** + * Renders the plain string [text] in [row]. + * + * @param text the plain text to render + */ + constructor( + row: Int, + text: String, + font: Key, + alignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_ALIGNMENT, + columns: IntRange = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_COLUMNS, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + defaultColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + metrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS, + ) : this(row, Component.text(text), font, alignment, columns, geometry, defaultColor, metrics) + + init { + require(row in 1..ViewSlotGeometry.MAX_ROWS) { + "Row must be in 1..${ViewSlotGeometry.MAX_ROWS}, was $row" + } + require(!columns.isEmpty()) { "A column span must cover at least one column" } + require(columns.first >= 0 && columns.last < ViewSlotGeometry.COLUMNS) { + "Columns must be in 0..${ViewSlotGeometry.COLUMNS - 1}, was $columns" + } + } + + private val laidOut = layoutViewText( + component = text, + charSpacing = metrics.charSpacing, + uppercase = metrics.uppercase, + spaceShift = metrics.spaceShift, + advance = metrics::advance + ) + + override val textureWidth = laidOut.width + + override val positionalShift = alignment.calculateShift( + textureWidth, + geometry.columnSpanOptions(columns, metrics.charWidths, metrics.charSize, metrics.charSpacing) + ) + + /** + * `false`: the width is derived from a font-metrics table, so a glyph the client renders at an + * unexpected width would make it an estimate. Keeps the text from displacing the header + * textures — see [ViewContainerComponent.hasExactWidth]. + */ + override val hasExactWidth = false + + override fun SurfComponentBuilder.renderComponent() { + append(laidOut.component) + font(font) + colorIfAbsent(defaultColor) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ViewContainerRowTextComponent + + if (row != other.row) return false + if (text != other.text) return false + if (font != other.font) return false + if (alignment != other.alignment) return false + if (columns != other.columns) return false + + return true + } + + override fun hashCode(): Int { + var result = row + result = 31 * result + text.hashCode() + result = 31 * result + font.hashCode() + result = 31 * result + alignment.hashCode() + result = 31 * result + columns.hashCode() + return result + } +} diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerTitleComponent.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerTitleComponent.kt index d8701e6bc..fb038c61f 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerTitleComponent.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/component/components/ViewContainerTitleComponent.kt @@ -1,80 +1,86 @@ package dev.slne.surf.api.paper.inventory.framework.view.container.component.components -import dev.slne.surf.api.core.inventory.framework.internal.renderViewTitle -import dev.slne.surf.api.core.messages.Colors +import dev.slne.surf.api.core.inventory.framework.internal.layoutViewText import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder import dev.slne.surf.api.paper.inventory.framework.view.container.component.ViewContainerComponent +import dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettingsDefaults +import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewFontMetrics +import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewHeaderGeometry import dev.slne.surf.api.paper.inventory.framework.view.settings.align.TextAlignment -import dev.slne.surf.api.paper.inventory.framework.view.settings.align.TextAlignmentOptions -import it.unimi.dsi.fastutil.ints.Int2IntMap -import it.unimi.dsi.fastutil.ints.Int2IntMaps import net.kyori.adventure.key.Key +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor /** - * A [ViewContainerComponent] that renders the inventory title text using a custom font. + * A [ViewContainerComponent] that renders the inventory title at the top of the header. * - * The title string is converted to uppercase and inter-character spacing glyphs are inserted - * between each letter using [renderViewTitle]. That same call reports the exact pixel width of the - * string it produced, which is used both to position the text according to [textAlignment] and to - * reset the render cursor afterwards — measuring the raw title instead would drift as soon as - * uppercasing changes the glyph count (`ß` uppercases to `SS`) or the leading spacing glyph is - * emitted. + * The [title] component keeps its colours, decorations and hover events; parts without a colour are + * rendered in [defaultColor]. It is positioned by [textAlignment] across the full width of the + * inventory as described by [geometry], which for the default [TextAlignment.CENTER] means centred + * above the slot grid. * - * The component uses constants from its companion object to define the geometry of the - * container area: - * - [LEFT_SHIFT]: the base pixel offset from the left edge of the container - * - [PADDING]: horizontal padding on each side - * - [CONTAINER_WIDTH]: the usable pixel width of the title area - * - [CHAR_SIZE]: the pixel width of a single character in the title font - * - [CHAR_SPACING]: the inter-character spacing (negative = tighter) + * Since the title is positioned by shifting the render cursor, its width has to be known up front, + * and that width comes from [metrics]: the proportional widths of the client font by default, or the + * cell width, spacing and casing of a fixed-width resource-pack font. [metrics] is also what applies + * that spacing and casing to the text, so it has to describe the font passed as [font]. * - * @param title the plain-text inventory title to render - * @param font the Adventure [Key] identifying the resource-pack font to use - * @param charSpacing the pixel spacing to insert between each character + * @param title the inventory title, rendered as-is apart from what [metrics] transforms + * @param font the Adventure [Key] of the font to render the title in; defaults to the vanilla + * font via [SurfViewSettingsDefaults.DEFAULT_HEADER_FONT] * @param textAlignment the [TextAlignment] controlling horizontal positioning - * @param charWidths per-code-point pixel width overrides for glyphs the [font] does not render at - * [CHAR_SIZE] pixels; see [TextAlignmentOptions.charWidths] + * @param geometry the header [ViewHeaderGeometry] the title is aligned in + * @param defaultColor the colour applied to parts of [title] that do not set one + * @param metrics the [ViewFontMetrics] of [font] */ class ViewContainerTitleComponent( - title: String, - private val font: Key, - charSpacing: Int, - textAlignment: TextAlignment, - charWidths: Int2IntMap = Int2IntMaps.EMPTY_MAP + val title: Component, + private val font: Key = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT, + val textAlignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_HEADER_ALIGNMENT, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + private val defaultColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + metrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT_METRICS, ) : ViewContainerComponent { - private val alignmentOptions = TextAlignmentOptions( - leftShift = LEFT_SHIFT, - padding = PADDING, - containerWidth = CONTAINER_WIDTH, - charSize = CHAR_SIZE, - charSpacing = charSpacing, - charWidths = charWidths - ) - private val rendered = renderViewTitle( - title = title, - charSize = CHAR_SIZE, - charSpacing = charSpacing, - alignRight = textAlignment == TextAlignment.RIGHT, - charWidths = charWidths + /** + * Renders the plain string [title] as the inventory title. + * + * @param title the plain-text inventory title + */ + constructor( + title: String, + font: Key = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT, + textAlignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_HEADER_ALIGNMENT, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + defaultColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + metrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT_METRICS, + ) : this(Component.text(title), font, textAlignment, geometry, defaultColor, metrics) + + private val laidOut = layoutViewText( + component = title, + charSpacing = metrics.charSpacing, + uppercase = metrics.uppercase, + spaceShift = metrics.spaceShift, + advance = metrics::advance ) - private val formattedTitle = rendered.text + override val textureWidth = laidOut.width - override val textureWidth = rendered.width - override val positionalShift = textAlignment.calculateShift(rendered.width, alignmentOptions) + override val positionalShift = textAlignment.calculateShift( + textureWidth, + geometry.titleOptions(metrics.charWidths, metrics.charSize, metrics.charSpacing) + ) /** - * `false`: the width is derived from the configured font metrics, so a glyph the font renders - * at an unexpected width would make it an estimate. Keeps the title from displacing the header + * `false`: the width is derived from a font-metrics table, so a glyph the client renders at an + * unexpected width would make it an estimate. Keeps the title from displacing the header * textures — see [ViewContainerComponent.hasExactWidth]. */ override val hasExactWidth = false override fun SurfComponentBuilder.renderComponent() { - text(formattedTitle) + append(laidOut.component) font(font) - color(Colors.WHITE) + colorIfAbsent(defaultColor) } override fun equals(other: Any?): Boolean { @@ -83,28 +89,17 @@ class ViewContainerTitleComponent( other as ViewContainerTitleComponent - return formattedTitle == other.formattedTitle - } + if (title != other.title) return false + if (font != other.font) return false + if (textAlignment != other.textAlignment) return false - override fun hashCode(): Int { - return formattedTitle.hashCode() + return true } - - companion object { - /** The base pixel offset from the left edge of the container area. */ - const val LEFT_SHIFT = 31 - - /** Horizontal padding on each side of the title text within the container. */ - const val PADDING = 2 - - /** Total usable pixel width of the title container area. */ - const val CONTAINER_WIDTH = 100 - - /** Pixel width of a single uppercase character in the title font. */ - const val CHAR_SIZE = 9 - - /** Default inter-character spacing (negative = tighter). */ - const val CHAR_SPACING = -1 + override fun hashCode(): Int { + var result = title.hashCode() + result = 31 * result + font.hashCode() + result = 31 * result + textAlignment.hashCode() + return result } } diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerDSL.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerDSL.kt index a321b0041..b0ec448fe 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerDSL.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/container/dsl/ViewContainerDSL.kt @@ -1,12 +1,22 @@ package dev.slne.surf.api.paper.inventory.framework.view.container.dsl +import dev.slne.surf.api.core.inventory.framework.internal.ViewSlotGeometry +import dev.slne.surf.api.core.messages.adventure.buildText +import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder import dev.slne.surf.api.paper.inventory.framework.view.AbstractSurfViewRef import dev.slne.surf.api.paper.inventory.framework.view.container.component.ViewContainerComponent import dev.slne.surf.api.paper.inventory.framework.view.container.component.components.ViewBlockCellComponent -import dev.slne.surf.api.paper.inventory.framework.view.container.component.components.ViewContainerBackHintComponent -import dev.slne.surf.api.paper.inventory.framework.view.container.component.components.ViewContainerTitleComponent +import dev.slne.surf.api.paper.inventory.framework.view.container.component.components.ViewContainerRowTextComponent +import dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettingsDefaults +import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewFontMetrics +import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewHeaderGeometry +import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewRows +import dev.slne.surf.api.paper.inventory.framework.view.settings.align.TextAlignment import it.unimi.dsi.fastutil.ints.IntCollection import it.unimi.dsi.fastutil.ints.IntLists +import net.kyori.adventure.key.Key +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor /** * Adds a [ViewContainerComponent] to the container. @@ -69,21 +79,355 @@ inline fun removeChildrenOfType() { context.container.removeChildrenOfType() } +/** + * Sets the inventory title, replacing the one currently rendered. + * + * The title is rendered as-is in the font configured via + * [SurfViewSettings.font][dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettings.font] + * - the client's own font by default - and positioned across the full width of the header texture, + * which for the default [TextAlignment.CENTER] means centred above the slot grid. Colours and + * decorations on [header] are kept; parts without a colour render white. + * + * ```kotlin + * containerDefaults { + * header(text("Spieler-Statistiken", Colors.GOLD)) + * } + * ``` + * + * @param header the title component to render + * @param alignment horizontal alignment of the title; defaults to the alignment the title + * currently rendered uses, or to the view's + * [headerTextAlignment][dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettings.headerTextAlignment] + * if the container has no title yet + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun header( + header: Component, + alignment: TextAlignment? = null, +) { + ref.getRegisteredView().applyHeader(header, alignment) +} + +/** + * Sets the inventory title to the plain, white string [header]. + * + * ```kotlin + * containerDefaults { + * header("Spieler-Statistiken") + * } + * ``` + * + * @param header the plain-text title to render + * @param alignment horizontal alignment of the title; defaults to the alignment the title + * currently rendered uses, or to the view's + * [headerTextAlignment][dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettings.headerTextAlignment] + * if the container has no title yet + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun header( + header: String, + alignment: TextAlignment? = null, +) { + header(Component.text(header), alignment) +} + +/** + * Sets the inventory title, building the component with a [SurfComponentBuilder] block. + * + * ```kotlin + * containerDefaults { + * header { + * text("Shop ", Colors.GOLD) + * text("(Seite 1)", Colors.GRAY) + * } + * } + * ``` + * + * @param alignment horizontal alignment of the title; defaults to the alignment the title + * currently rendered uses, or to the view's + * [headerTextAlignment][dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettings.headerTextAlignment] + * if the container has no title yet + * @param block builds the title component + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun header( + alignment: TextAlignment? = null, + block: SurfComponentBuilder.() -> Unit, +) { + header(buildText(block), alignment) +} + +/** + * Renders a line of [text] inside the slot [row], vertically centred in the row. + * + * The row comes from the font: the text is drawn in the view's + * [rowFont][dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettings.rowFont] + * for [row], whose `ascent` in the resource pack centres it in that row - the server can only + * position header glyphs horizontally. That horizontal position is computed from [alignment] within + * the span of slot [columns], so a row can carry several independent texts. + * + * [text] is a full component: colours, gradients, decorations and hover events are kept, and parts + * without a colour render white. + * + * ```kotlin + * containerDefaults { + * rowText(1, text("Deine Statistiken", Colors.GOLD)) + * rowText(3, text("Seite 1/4"), TextAlignment.RIGHT) + * rowText(5, text("Kategorie"), TextAlignment.LEFT, columns = 0..2) + * } + * ``` + * + * @param row the one-based slot row to render in (1-6) + * @param text the component to render, emitted as-is + * @param alignment horizontal alignment within the [columns] span + * @param columns the zero-based, inclusive span of slot columns to align in + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun rowText( + @ViewRows.Companion.Rows row: Int, + text: Component, + alignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_ALIGNMENT, + columns: IntRange = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_COLUMNS, +) { + val settings = ref.getRegisteredView().settings + require(row <= settings.rows.rows) { + "Row $row is outside of this view, which only has ${settings.rows.rows} rows" + } + + rowText( + row = row, + text = text, + font = settings.rowFont(row), + alignment = alignment, + columns = columns, + geometry = settings.headerGeometry, + defaultColor = settings.headerTextColor, + metrics = settings.rowFontMetrics + ) +} + +/** + * Renders the plain, white string [text] inside the slot [row], vertically centred in the row. + * + * @param row the one-based slot row to render in (1-6) + * @param text the plain text to render, emitted as-is + * @param alignment horizontal alignment within the [columns] span + * @param columns the zero-based, inclusive span of slot columns to align in + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun rowText( + @ViewRows.Companion.Rows row: Int, + text: String, + alignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_ALIGNMENT, + columns: IntRange = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_COLUMNS, +) { + rowText(row, Component.text(text), alignment, columns) +} + +/** + * Renders a line of text inside the slot [row], building the component with a + * [SurfComponentBuilder] block. + * + * ```kotlin + * containerDefaults { + * rowText(2, TextAlignment.LEFT, columns = 0..3) { + * text("Guthaben: ", Colors.GRAY) + * text("1.250", Colors.GREEN) + * } + * } + * ``` + * + * @param row the one-based slot row to render in (1-6) + * @param alignment horizontal alignment within the [columns] span + * @param columns the zero-based, inclusive span of slot columns to align in + * @param block builds the component to render + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun rowText( + @ViewRows.Companion.Rows row: Int, + alignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_ALIGNMENT, + columns: IntRange = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_COLUMNS, + block: SurfComponentBuilder.() -> Unit, +) { + rowText(row, buildText(block), alignment, columns) +} + +/** + * Renders a line of [text] inside the slot [row] using an explicitly given row [font]. + * + * Use this overload outside of a view's DSL scope, where the view's settings are not available as a + * context receiver, or to render a row of text in a font other than the view's configured + * [rowFont][dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettings.rowFont]. + * + * @param row the one-based slot row to render in (1-6) + * @param text the component to render, emitted as-is + * @param font the font whose `ascent` centres the text in [row] + * @param alignment horizontal alignment within the [columns] span + * @param columns the zero-based, inclusive span of slot columns to align in + * @param geometry the header geometry describing the slot grid + * @param defaultColor the colour applied to parts of [text] that do not set one + * @param metrics the glyph metrics of [font] + */ +context(context: ViewContainerModificationContext) +fun rowText( + @ViewRows.Companion.Rows row: Int, + text: Component, + font: Key, + alignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_ALIGNMENT, + columns: IntRange = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_COLUMNS, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, + defaultColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + metrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS, +) { + addChild( + ViewContainerRowTextComponent( + row = row, + text = text, + font = font, + alignment = alignment, + columns = columns, + geometry = geometry, + defaultColor = defaultColor, + metrics = metrics + ) + ) +} + +/** + * Renders the plain, white string [text] inside the slot [row] using an explicitly given row [font]. + * + * @param row the one-based slot row to render in (1-6) + * @param text the plain text to render, emitted as-is + * @param font the font whose `ascent` centres the text in [row] + * @param alignment horizontal alignment within the [columns] span + * @param columns the zero-based, inclusive span of slot columns to align in + * @param geometry the header geometry describing the slot grid + * @param defaultColor the colour applied to parts of [text] that do not set one + * @param metrics the glyph metrics of [font] + */ +context(context: ViewContainerModificationContext) +fun rowText( + @ViewRows.Companion.Rows row: Int, + text: String, + font: Key, + alignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_ALIGNMENT, + columns: IntRange = SurfViewSettingsDefaults.DEFAULT_ROW_TEXT_COLUMNS, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, + defaultColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + metrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS, +) { + rowText(row, Component.text(text), font, alignment, columns, geometry, defaultColor, metrics) +} + +/** + * Renders [text] in the slot [row], starting at the left edge of [column]. + * + * Shorthand for a [rowText] left-aligned in the span from [column] to the last column. + * + * ```kotlin + * containerDefaults { + * rowTextAt(2, 4, text("hier ->")) // starts above the middle slot of row 2 + * } + * ``` + * + * @param row the one-based slot row to render in (1-6) + * @param column the zero-based slot column the text starts at (0-8) + * @param text the component to render, emitted as-is + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun rowTextAt( + @ViewRows.Companion.Rows row: Int, + column: Int, + text: Component, +) { + rowText( + row = row, + text = text, + alignment = TextAlignment.LEFT, + columns = column until ViewSlotGeometry.COLUMNS + ) +} + +/** + * Renders the plain, white string [text] in the slot [row], starting at the left edge of [column]. + * + * @param row the one-based slot row to render in (1-6) + * @param column the zero-based slot column the text starts at (0-8) + * @param text the plain text to render, emitted as-is + */ +context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) +fun rowTextAt( + @ViewRows.Companion.Rows row: Int, + column: Int, + text: String, +) { + rowTextAt(row, column, Component.text(text)) +} + +/** + * Removes every text rendered on the slot [row]. + * + * @param row the one-based slot row to clear (1-6) + */ +context(context: ViewContainerModificationContext) +fun clearRowText(@ViewRows.Companion.Rows row: Int) { + context.container.removeChildren { it is ViewContainerRowTextComponent && it.row == row } +} + +/** + * Removes every text rendered on any slot row, leaving the inventory title itself untouched. + */ +context(context: ViewContainerModificationContext) +fun clearRowTexts() { + removeChildrenOfType() +} + /** * Adds a [ViewBlockCellComponent] that visually blocks the cell at [column], [row]. * + * Blocking is **cosmetic only**: it draws a texture over the cell and changes nothing about the + * slot itself, so items can still be placed into and taken out of a blocked slot exactly as before. + * * ```kotlin * containerDefaults { * blockCell(0, 1) // block column 0, row 1 * } * ``` * - * @param column the zero-based column index (0–8) - * @param row the one-based row index (1–6) + * @param column the zero-based column index (0-8) + * @param row the one-based row index (1-6) + * @param geometry the header geometry describing the slot grid; pass the view's + * [headerGeometry][dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettings.headerGeometry] + * when it deviates from the default */ context(context: ViewContainerModificationContext) -fun blockCell(column: Int, row: Int) { - addChild(ViewBlockCellComponent(column, row)) +fun blockCell( + column: Int, + @ViewRows.Companion.Rows row: Int, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + addChild(ViewBlockCellComponent(column, row, geometry)) +} + +/** + * Blocks the cell at the zero-based [slot] index, counted left-to-right and then top-to-bottom just + * like the inventory's own slot numbering. + * + * ```kotlin + * containerDefaults { + * blockSlot(13) // centre slot of row 2 + * } + * ``` + * + * @param slot the zero-based slot index (0-53) + * @param geometry the header geometry describing the slot grid + */ +context(context: ViewContainerModificationContext) +fun blockSlot( + slot: Int, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + addChild(ViewBlockCellComponent.forSlot(slot, geometry)) } /** @@ -95,14 +439,19 @@ fun blockCell(column: Int, row: Int) { * } * ``` * - * @param row the one-based row index (1–6) + * @param row the one-based row index (1-6) * @param exemptColumns zero-based column indices to leave unblocked + * @param geometry the header geometry describing the slot grid */ context(context: ViewContainerModificationContext) -fun blockRow(row: Int, exemptColumns: IntArray) { - for (x in 0 until 9) { +fun blockRow( + @ViewRows.Companion.Rows row: Int, + exemptColumns: IntArray, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + for (x in 0 until ViewSlotGeometry.COLUMNS) { if (x in exemptColumns) continue - blockCell(x, row) + blockCell(x, row, geometry) } } @@ -115,79 +464,229 @@ fun blockRow(row: Int, exemptColumns: IntArray) { * } * ``` * - * @param row the one-based row index (1–6) + * @param row the one-based row index (1-6) * @param exemptColumns zero-based column indices to leave unblocked; defaults to none + * @param geometry the header geometry describing the slot grid */ context(context: ViewContainerModificationContext) -fun blockRow(row: Int, exemptColumns: IntCollection = IntLists.EMPTY_LIST) { - for (x in 0 until 9) { +fun blockRow( + @ViewRows.Companion.Rows row: Int, + exemptColumns: IntCollection = IntLists.EMPTY_LIST, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + for (x in 0 until ViewSlotGeometry.COLUMNS) { if (exemptColumns.contains(x)) continue - blockCell(x, row) + blockCell(x, row, geometry) } } /** * Blocks all cells in the given [column], skipping rows listed in [exemptRows]. * - * @param column the zero-based column index (0–8) + * @param column the zero-based column index (0-8) * @param exemptRows one-based row indices to leave unblocked + * @param rows how many rows the view has; cells below it have no glyph to render + * @param geometry the header geometry describing the slot grid */ context(context: ViewContainerModificationContext) -fun blockColumn(column: Int, exemptRows: IntArray) { - for (row in 1..6) { +fun blockColumn( + column: Int, + exemptRows: IntArray, + @ViewRows.Companion.Rows rows: Int = ViewSlotGeometry.MAX_ROWS, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + for (row in 1..rows) { if (row in exemptRows) continue - blockCell(column, row) + blockCell(column, row, geometry) } } /** * Blocks all cells in the given [column], skipping rows in [exemptRows]. * - * @param column the zero-based column index (0–8) + * @param column the zero-based column index (0-8) * @param exemptRows one-based row indices to leave unblocked; defaults to none + * @param rows how many rows the view has; cells below it have no glyph to render + * @param geometry the header geometry describing the slot grid */ context(context: ViewContainerModificationContext) -fun blockColumn(column: Int, exemptRows: IntCollection = IntLists.EMPTY_LIST) { - for (row in 1..6) { +fun blockColumn( + column: Int, + exemptRows: IntCollection = IntLists.EMPTY_LIST, + @ViewRows.Companion.Rows rows: Int = ViewSlotGeometry.MAX_ROWS, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + for (row in 1..rows) { if (exemptRows.contains(row)) continue - blockCell(column, row) + blockCell(column, row, geometry) } } /** - * Adds a [ViewContainerBackHintComponent] to the container. + * Blocks every slot of a view with [rows] rows, skipping the slots listed in [exemptSlots]. * - * This renders a small navigation arrow in the inventory header, indicating to the player - * that clicking outside the inventory will navigate back to the previous view. + * ```kotlin + * containerDefaults { + * // every slot of a 5-row view is blocked, except the one in the middle + * blockAllSlots(ViewRows.FIVE, intSetOf(22)) + * } + * ``` * - * Added automatically when [SurfViewSettings.navigateBackOnOutsideClick][dev.slne.surf.api.paper.api.inventory.framework.view.settings.SurfViewSettings.navigateBackOnOutsideClick] - * is `true`. + * @param rows how many rows the view has + * @param exemptSlots zero-based slot indices to leave unblocked; defaults to none + * @param geometry the header geometry describing the slot grid */ context(context: ViewContainerModificationContext) -fun backHint() { - addChild(ViewContainerBackHintComponent) +fun blockAllSlots( + rows: ViewRows, + exemptSlots: IntCollection = IntLists.EMPTY_LIST, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + blockAllSlots(rows.rows, exemptSlots, geometry) +} + +/** + * Blocks every slot of a view with [rows] rows, skipping the slots listed in [exemptSlots]. + * + * @param rows how many rows the view has (1-6) + * @param exemptSlots zero-based slot indices to leave unblocked; defaults to none + * @param geometry the header geometry describing the slot grid + */ +context(context: ViewContainerModificationContext) +fun blockAllSlots( + @ViewRows.Companion.Rows rows: Int, + exemptSlots: IntCollection = IntLists.EMPTY_LIST, + geometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, +) { + for (slot in 0 until rows * ViewSlotGeometry.COLUMNS) { + if (exemptSlots.contains(slot)) continue + blockSlot(slot, geometry) + } } /** - * Sets a title header for the [ViewContainer][dev.slne.surf.api.paper.api.inventory.framework.view.container.ViewContainer] by removing any existing title component - * and adding a new `ViewContainerTitleComponent` with the specified header text. + * Removes the block overlay from the cell at [column], [row], if there is one. * - * @param header The text to set as the title for the container. - * @param context Provides access to the container modification API for the current view. - * @param ref A deferred reference to the associated view, providing configuration details - * such as font and text alignment settings. + * @param column the zero-based column index (0-8) + * @param row the one-based row index (1-6) */ +context(context: ViewContainerModificationContext) +fun unblockCell(column: Int, @ViewRows.Companion.Rows row: Int) { + removeChild(ViewBlockCellComponent(column, row)) +} + +/** + * Removes the block overlay from the cell at the zero-based [slot] index, if there is one. + * + * @param slot the zero-based slot index (0-53) + */ +context(context: ViewContainerModificationContext) +fun unblockSlot(slot: Int) { + removeChild(ViewBlockCellComponent.forSlot(slot)) +} + +/** + * Removes every block overlay from the container. + */ +context(context: ViewContainerModificationContext) +fun unblockAllSlots() { + removeChildrenOfType() +} + +// ------------------------------------------------------------------------------------------------- +// Binary compatibility +// +// The container DSL gained a `geometry` parameter - and `blockColumn` additionally a `rows` +// parameter - which changed the JVM signatures of the functions below, so plugins compiled against +// an older surf-api fail with a `NoSuchMethodError` at runtime. The shims keep the old signatures +// alive and delegate to the current functions with the parameters those callers never passed. +// +// They are hidden from Kotlin callers, so new code always resolves to the functions above. Remove +// them once every consumer has been rebuilt against this version. +// ------------------------------------------------------------------------------------------------- + +/** @suppress */ +@Deprecated("Binary compatibility", ReplaceWith("blockCell(column, row)"), DeprecationLevel.HIDDEN) +context(context: ViewContainerModificationContext) +fun blockCell(column: Int, @ViewRows.Companion.Rows row: Int) { + blockCell(column, row, SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY) +} + +/** @suppress */ +@Deprecated( + "Binary compatibility", + ReplaceWith("blockRow(row, exemptColumns)"), + DeprecationLevel.HIDDEN +) +context(context: ViewContainerModificationContext) +fun blockRow(@ViewRows.Companion.Rows row: Int, exemptColumns: IntArray) { + blockRow(row, exemptColumns, SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY) +} + +/** @suppress */ +@Deprecated( + "Binary compatibility", + ReplaceWith("blockRow(row, exemptColumns)"), + DeprecationLevel.HIDDEN +) +context(context: ViewContainerModificationContext) +fun blockRow( + @ViewRows.Companion.Rows row: Int, + exemptColumns: IntCollection = IntLists.EMPTY_LIST, +) { + blockRow(row, exemptColumns, SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY) +} + +/** @suppress */ +@Deprecated( + "Binary compatibility", + ReplaceWith("blockColumn(column, exemptRows)"), + DeprecationLevel.HIDDEN +) +context(context: ViewContainerModificationContext) +fun blockColumn(column: Int, exemptRows: IntArray) { + blockColumn( + column = column, + exemptRows = exemptRows, + rows = ViewSlotGeometry.MAX_ROWS, + geometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY + ) +} + +/** @suppress */ +@Deprecated( + "Binary compatibility", + ReplaceWith("blockColumn(column, exemptRows)"), + DeprecationLevel.HIDDEN +) +context(context: ViewContainerModificationContext) +fun blockColumn(column: Int, exemptRows: IntCollection = IntLists.EMPTY_LIST) { + blockColumn( + column = column, + exemptRows = exemptRows, + rows = ViewSlotGeometry.MAX_ROWS, + geometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY + ) +} + +/** @suppress */ +@Deprecated("Binary compatibility", ReplaceWith("header(header)"), DeprecationLevel.HIDDEN) context(context: ViewContainerModificationContext, ref: AbstractSurfViewRef) fun header(header: String) { - removeChildrenOfType() + header(header, null) +} - val settings = ref.getRegisteredView().settings - addChild( - ViewContainerTitleComponent( - header, - settings.font, - ViewContainerTitleComponent.CHAR_SPACING, - settings.headerTextAlignment - ) - ) -} \ No newline at end of file +/** + * No-op that keeps plugins compiled against the removed back-hint component loading. + * + * The navigation arrow the old `ViewContainerBackHintComponent` drew is part of the inventory + * texture now; whether an outside click navigates back is decided solely by + * [navigateBackOnOutsideClick][dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettings.navigateBackOnOutsideClick]. + * + * @suppress + */ +@Deprecated("The back hint is part of the inventory texture now", level = DeprecationLevel.HIDDEN) +context(context: ViewContainerModificationContext) +fun backHint() { + // nothing to render anymore +} diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/pagination/AbstractPaginatedSurfView.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/pagination/AbstractPaginatedSurfView.kt index 767e6fe54..723878b61 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/pagination/AbstractPaginatedSurfView.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/pagination/AbstractPaginatedSurfView.kt @@ -30,6 +30,9 @@ import java.util.concurrent.atomic.AtomicBoolean * - Left/right navigation buttons in the designated button row. * - A [PaginationButtonGlyphComponent] overlay that reflects the current pagination state * (both-disabled, left-disabled, right-disabled, or both-enabled). + * - A [PaginationPageIndicatorComponent] between the two buttons showing the current page and the + * page count, as rendered by + * [paginationPageIndicator][dev.slne.surf.api.paper.inventory.framework.view.settings.PaginatedViewSettings.paginationPageIndicator]. * * The initial pagination glyph is updated asynchronously one tick after the pagination state * first resolves (via [InitialPaginationStateWatcher]) to work around Folia scheduling constraints. @@ -54,7 +57,7 @@ abstract class AbstractPaginatedSurfView(header: String) : AbstractSurfView(head .layoutTarget(layoutTarget) .apply { onPageSwitch(pageSwitchHandler.prepend { context, _ -> - updatePaginationGlyph(context) + updatePaginationOverlay(context) }) }.build() } @@ -118,33 +121,61 @@ abstract class AbstractPaginatedSurfView(header: String) : AbstractSurfView(head protected open fun onPaginatedUpdate(update: Context) = Unit - private fun updatePaginationGlyph(context: Context) { + /** + * Re-renders the pagination overlay in the header: the navigation button glyph for the current + * state, plus the page counter between the two buttons. + */ + private fun updatePaginationOverlay(context: Context) { val pagination = paginationState.get(context) ?: return + val row = settings.paginationButtonRow + val geometry = settings.headerGeometry + val buttonGlyph = PaginationButtonGlyphComponent.getByPaginationState( - row = settings.paginationButtonRow, - pagination = pagination + row = row, + pagination = pagination, + geometry = geometry ) + val totalPages = pagination.lastPage().coerceAtLeast(1) + val currentPage = pagination.currentPage().coerceIn(1, totalPages) + + val pageText = settings.paginationPageIndicator + ?.render(currentPage, totalPages) + modifyContainer(context) { removeChildrenOfType() addChild(buttonGlyph) + + removeChildrenOfType() + if (pageText != null) { + addChild( + PaginationPageIndicatorComponent( + row = row, + text = pageText, + font = settings.rowFont(row), + geometry = geometry, + defaultColor = settings.headerTextColor, + metrics = settings.rowFontMetrics + ) + ) + } } } /** - * Applies the paginated container defaults: blocks all border cells and all cells outside - * the pagination content rows, then calls [applyContainerDefaults] for subclass customisation. + * Applies the paginated container defaults: blocks all border cells, the button row and every + * empty row, then calls [applyContainerDefaults] for subclass customisation. * * This override is `final` — subclasses should override [applyContainerDefaults] instead. */ context(_: ViewContainerModificationContext) final override fun containerDefaults() { - val paginationContentRows = settings.paginationViewRows.paginationContentRows + val paginationContentRows = settings.paginationContentRows for (y in 1..settings.rows.rows) { for (x in 0 until 9) { if (y in paginationContentRows && x in 1..7) continue - blockCell(x, y) + blockCell(x, y, settings.headerGeometry) } } @@ -161,21 +192,35 @@ abstract class AbstractPaginatedSurfView(header: String) : AbstractSurfView(head protected open fun applyContainerDefaults() { } + /** + * Plays the configured + * [paginationSwitchSound][dev.slne.surf.api.paper.inventory.framework.view.settings.PaginatedViewSettings.paginationSwitchSound] + * to the viewer that just navigated to another page. Does nothing if no sound is configured. + */ + private fun playPageSwitchSound(click: SlotClickContext) { + val sound = settings.paginationSwitchSound ?: return + click.player.playSound(sound) + } + final override fun onViewInit(config: ViewConfigBuilder) { paginationState // initialize pagination state onPaginatedInit(config) config.layout(*createLayout()) } + /** + * Builds the layout: only the + * [paginationContentRows][dev.slne.surf.api.paper.inventory.framework.view.settings.PaginatedViewSettings.paginationContentRows] + * are filled with [layoutTarget] slots. The button row and the + * [paginationEmptyRows][dev.slne.surf.api.paper.inventory.framework.view.settings.PaginatedViewSettings.paginationEmptyRows] + * rows at the opposite inventory edge stay empty. + */ private fun createLayout(): Array { - val layout = arrayOfNulls(settings.rows.rows) - layout[0] = EMPTY_ROW - repeat(settings.rows.rows - 2) { i -> - layout[i + 1] = paginationRow - } - layout[layout.lastIndex] = EMPTY_ROW + val contentRows = settings.paginationContentRows - return layout.requireNoNulls() + return Array(settings.rows.rows) { index -> + if (index + 1 in contentRows) paginationRow else EMPTY_ROW + } } final override fun onViewOpen(open: OpenContext) { @@ -188,7 +233,7 @@ abstract class AbstractPaginatedSurfView(header: String) : AbstractSurfView(head if (pagination.isStatic) { render.player.scheduler.run(JavaPlugin.getProvidingPlugin(javaClass), { - updatePaginationGlyph(render) + updatePaginationOverlay(render) }, null) } else { render.watchState(pagination.id, InitialPaginationStateWatcher()) @@ -198,13 +243,21 @@ abstract class AbstractPaginatedSurfView(header: String) : AbstractSurfView(head .withItem(ItemStack.empty()) .updateOnStateChange(paginationState) .displayIf(pagination::canBack) - .onClick(pagination::back) + .onClick { click: SlotClickContext -> + if (!pagination.canBack()) return@onClick + pagination.back() + playPageSwitchSound(click) + } render.slot(PaginationButton.RIGHT.clickSlot(paginationButtonRow)) .withItem(ItemStack.empty()) .updateOnStateChange(paginationState) .displayIf(pagination::canAdvance) - .onClick(pagination::advance) + .onClick { click: SlotClickContext -> + if (!pagination.canAdvance()) return@onClick + pagination.advance() + playPageSwitchSound(click) + } onPaginatedRender(render) } @@ -220,7 +273,7 @@ abstract class AbstractPaginatedSurfView(header: String) : AbstractSurfView(head final override fun onViewUpdate(update: Context) { val pagination = paginationState.get(update) - if (pagination != null) { + if (pagination != null && pagination.hasPage(pagination.currentPageIndex())) { pagination.switchTo(pagination.currentPageIndex()) // trigger pagination state update to refresh dynamic elements } @@ -252,7 +305,7 @@ abstract class AbstractPaginatedSurfView(header: String) : AbstractSurfView(head if (host !is Context) return host.player.scheduler.run(JavaPlugin.getProvidingPlugin(javaClass), { - updatePaginationGlyph(host) + updatePaginationOverlay(host) }, null) } } diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationButtonGlyphComponent.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationButtonGlyphComponent.kt index 775c7ed7d..cda616c05 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationButtonGlyphComponent.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationButtonGlyphComponent.kt @@ -4,6 +4,7 @@ import dev.slne.surf.api.core.inventory.framework.internal.PaginationButtonGlyph import dev.slne.surf.api.core.messages.Colors import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder import dev.slne.surf.api.paper.inventory.framework.view.container.component.ViewContainerComponent +import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewHeaderGeometry import me.devnatan.inventoryframework.component.Pagination /** @@ -17,18 +18,21 @@ import me.devnatan.inventoryframework.component.Pagination * - [Enabled] — both buttons are available * * Each subclass delegates its per-row glyph lookup to the shared [PaginationButtonGlyphs] table. - * The correct glyph character is selected based - * on the [row] (1-based) in which the buttons appear. The component has a fixed positional shift - * of 38 pixels and a texture width of 88 pixels. + * The correct glyph character is selected based on the [row] (1-based) in which the buttons appear. + * The overlay is positioned on the column of the left navigation button (see [PaginationButton]), + * plus the [GLYPH_INSET] the texture keeps free on its left, and spans [TEXTURE_WIDTH] pixels. * * Use [getByPaginationState] to obtain the correct instance for a given [Pagination] state. * * @param row the 1-based inventory row where the pagination buttons are located + * @param geometry the header [ViewHeaderGeometry] describing the slot grid */ -internal sealed class PaginationButtonGlyphComponent(private val row: Int) : - ViewContainerComponent { - override val positionalShift = 39 - override val textureWidth = 88 +internal sealed class PaginationButtonGlyphComponent( + private val row: Int, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, +) : ViewContainerComponent { + override val positionalShift = geometry.columnShift(PaginationButton.LEFT.column) + GLYPH_INSET + override val textureWidth = TEXTURE_WIDTH /** * Returns the glyph character for this button state at the given [rows] (1-based row index). @@ -58,26 +62,81 @@ internal sealed class PaginationButtonGlyphComponent(private val row: Int) : } /** Both navigation buttons are disabled (no previous and no next page). */ - class Disabled(row: Int) : PaginationButtonGlyphComponent(row) { + class Disabled( + row: Int, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + ) : PaginationButtonGlyphComponent(row, geometry) { override fun glyph(rows: Int): Char = PaginationButtonGlyphs.DISABLED.glyph(rows) } /** The right (next) button is disabled; only the left (previous) button is active. */ - class DisabledRight(row: Int) : PaginationButtonGlyphComponent(row) { + class DisabledRight( + row: Int, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + ) : PaginationButtonGlyphComponent(row, geometry) { override fun glyph(rows: Int): Char = PaginationButtonGlyphs.DISABLED_RIGHT.glyph(rows) } /** The left (previous) button is disabled; only the right (next) button is active. */ - class DisabledLeft(row: Int) : PaginationButtonGlyphComponent(row) { + class DisabledLeft( + row: Int, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + ) : PaginationButtonGlyphComponent(row, geometry) { override fun glyph(rows: Int): Char = PaginationButtonGlyphs.DISABLED_LEFT.glyph(rows) } /** Both navigation buttons are active (there are previous and next pages). */ - class Enabled(row: Int) : PaginationButtonGlyphComponent(row) { + class Enabled( + row: Int, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + ) : PaginationButtonGlyphComponent(row, geometry) { override fun glyph(rows: Int): Char = PaginationButtonGlyphs.ENABLED.glyph(rows) } companion object { + /** + * Pixels the button overlay glyph advances the render cursor by, measured off the glyph + * sheet of the pack: `menu/pagination/buttons.png` is 88x48, i.e. four 88x12 bands, and + * rendering a band one-to-one (`height: 12`) advances its 88 pixels plus the one the font + * renderer adds. + * + * This has to match what the glyph really advances - the container resets the cursor by + * `-(textureWidth + positionalShift)` after every component, so a wrong value shifts every + * component rendered afterwards. + */ + const val TEXTURE_WIDTH = 89 + + /** + * Pixels the overlay is moved relative to the left edge of the left button slot, tuned + * against the texture of the pack in-game. + * + * Note that the texture cannot line up with both buttons at once: the centres of its two + * arrows are 73 pixels apart (ink columns 0-14 and 73-87 of the 88 pixel bar), while the + * slot grid puts the two button slots 72 pixels apart. Whatever this offset is, one arrow + * ends up a pixel off its slot centre - move the right arrow one pixel left in + * `menu/pagination/buttons.png` to fix that at the source. + */ + const val GLYPH_INSET = 0 + + /** + * Pixel offset of the free area between the two arrows, measured from the left edge of the + * bar: the arrows of `menu/pagination/buttons.png` occupy its first and last 15 pixels. + */ + const val COUNTER_AREA_OFFSET = 15 + + /** Width in pixels of the free area between the two arrows. */ + const val COUNTER_AREA_WIDTH = 58 + + /** + * Returns the pixel offset of the free area between the two arrows, relative to the title + * origin - where the page counter of a paginated view belongs. + * + * @param geometry the header geometry describing the slot grid the bar is placed on + */ + fun counterAreaShift(geometry: ViewHeaderGeometry): Int = + geometry.columnShift(PaginationButton.LEFT.column) + GLYPH_INSET + COUNTER_AREA_OFFSET + + /** * Returns the appropriate [PaginationButtonGlyphComponent] for the given [pagination] state. * @@ -86,14 +145,19 @@ internal sealed class PaginationButtonGlyphComponent(private val row: Int) : * * @param row the 1-based row index where the buttons are located * @param pagination the current [Pagination] state + * @param geometry the header [ViewHeaderGeometry] describing the slot grid * @return the matching [PaginationButtonGlyphComponent] */ - fun getByPaginationState(row: Int, pagination: Pagination): PaginationButtonGlyphComponent = + fun getByPaginationState( + row: Int, + pagination: Pagination, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + ): PaginationButtonGlyphComponent = when { - !pagination.canBack() && !pagination.canAdvance() -> Disabled(row) - !pagination.canBack() && pagination.canAdvance() -> DisabledLeft(row) - pagination.canBack() && !pagination.canAdvance() -> DisabledRight(row) - pagination.canBack() && pagination.canAdvance() -> Enabled(row) + !pagination.canBack() && !pagination.canAdvance() -> Disabled(row, geometry) + !pagination.canBack() && pagination.canAdvance() -> DisabledLeft(row, geometry) + pagination.canBack() && !pagination.canAdvance() -> DisabledRight(row, geometry) + pagination.canBack() && pagination.canAdvance() -> Enabled(row, geometry) else -> throw MatchException( "Invalid pagination state: canBack=${pagination.canBack()}, canAdvance=${pagination.canAdvance()}", null diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator.kt new file mode 100644 index 000000000..c76468360 --- /dev/null +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicator.kt @@ -0,0 +1,45 @@ +package dev.slne.surf.api.paper.inventory.framework.view.pagination + +import dev.slne.surf.api.core.messages.Colors +import dev.slne.surf.api.core.messages.adventure.text +import dev.slne.surf.api.paper.inventory.framework.view.pagination.PaginationPageIndicator.Companion.Default +import net.kyori.adventure.text.Component + +/** + * Renders the page counter that a paginated view shows between its navigation buttons. + * + * Called every time the pagination state changes, with the one-based number of the page currently + * shown and the number of pages in total. Returning `null` hides the counter for that state — for + * example to show nothing while there is only a single page. + * + * ```kotlin + * paginatedSurfView("Shop") { + * settings { + * paginationPageIndicator { current, total -> + * if (total <= 1) null else text("Seite $current von $total", Colors.GRAY) + * } + * } + * } + * ``` + * + * @see Default + * @see dev.slne.surf.api.paper.inventory.framework.view.settings.PaginatedViewSettings.paginationPageIndicator + */ +fun interface PaginationPageIndicator { + + /** + * Returns the component to render as the page counter, or `null` to render nothing. + * + * @param currentPage the one-based number of the page currently shown, never below `1` + * @param totalPages the total number of pages, never below `1` - a pagination without any + * content is reported as page `1` of `1` + */ + fun render(currentPage: Int, totalPages: Int): Component? + + companion object { + /** Renders the counter as `current/total`, e.g. `2/7`. */ + val Default = PaginationPageIndicator { currentPage, totalPages -> + text("$currentPage/$totalPages", Colors.WHITE) + } + } +} diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicatorComponent.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicatorComponent.kt new file mode 100644 index 000000000..a5c8ce562 --- /dev/null +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/pagination/PaginationPageIndicatorComponent.kt @@ -0,0 +1,90 @@ +package dev.slne.surf.api.paper.inventory.framework.view.pagination + +import dev.slne.surf.api.core.inventory.framework.internal.TextAlignmentMath +import dev.slne.surf.api.core.inventory.framework.internal.layoutViewText +import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder +import dev.slne.surf.api.paper.inventory.framework.view.container.component.ViewContainerComponent +import dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettingsDefaults +import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewFontMetrics +import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewHeaderGeometry +import net.kyori.adventure.key.Key +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor + +/** + * The page counter a paginated view renders between its navigation buttons. + * + * It is centred in the free area of the button bar drawn by [PaginationButtonGlyphComponent] — the + * gap between the two arrows, which sit at the ends of that bar — rather than on the slot grid. The + * two centres are two pixels apart, so aligning on the slot grid would leave the counter looking + * slightly off inside the bar. See [PaginationButtonGlyphComponent.counterAreaShift]. + * + * The row itself comes from the [font], exactly like a row text: its `ascent` in the resource pack + * is what puts the counter on the button row. + * + * @param row the one-based row the navigation buttons are in + * @param text the counter component produced by the [PaginationPageIndicator] + * @param font the row font whose `ascent` centres the counter in [row] + * @param geometry the header [ViewHeaderGeometry] describing the slot grid the bar sits on + * @param defaultColor the colour applied to parts of [text] that do not set one + * @param metrics the [ViewFontMetrics] of [font] + */ +internal class PaginationPageIndicatorComponent( + val row: Int, + val text: Component, + private val font: Key, + geometry: ViewHeaderGeometry = ViewHeaderGeometry.DEFAULT, + private val defaultColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + metrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS, +) : ViewContainerComponent { + + private val laidOut = layoutViewText( + component = text, + charSpacing = metrics.charSpacing, + uppercase = metrics.uppercase, + spaceShift = metrics.spaceShift, + advance = metrics::advance + ) + + override val textureWidth = laidOut.width + + override val positionalShift = TextAlignmentMath.centerAlignedShift( + textWidth = textureWidth, + leftShift = PaginationButtonGlyphComponent.counterAreaShift(geometry), + padding = 0, + containerWidth = PaginationButtonGlyphComponent.COUNTER_AREA_WIDTH + ) + + /** + * `false`: the width is derived from a font-metrics table, so a glyph the client renders at an + * unexpected width would make it an estimate. Keeps the counter from displacing the header + * textures — see [ViewContainerComponent.hasExactWidth]. + */ + override val hasExactWidth = false + + override fun SurfComponentBuilder.renderComponent() { + append(laidOut.component) + font(font) + colorIfAbsent(defaultColor) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as PaginationPageIndicatorComponent + + if (row != other.row) return false + if (text != other.text) return false + if (font != other.font) return false + + return true + } + + override fun hashCode(): Int { + var result = row + result = 31 * result + text.hashCode() + result = 31 * result + font.hashCode() + return result + } +} diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/PaginatedViewSettings.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/PaginatedViewSettings.kt index 31ff9109f..480a3023b 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/PaginatedViewSettings.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/PaginatedViewSettings.kt @@ -1,41 +1,69 @@ package dev.slne.surf.api.paper.inventory.framework.view.settings +import dev.slne.surf.api.paper.inventory.framework.view.pagination.PaginationPageIndicator import dev.slne.surf.api.paper.inventory.framework.view.settings.align.TextAlignment +import it.unimi.dsi.fastutil.ints.Int2ObjectMap import net.kyori.adventure.key.Key +import net.kyori.adventure.sound.Sound +import net.kyori.adventure.text.format.TextColor /** * View settings for a paginated [AbstractPaginatedSurfView]. * * Extends [SurfViewSettings] with pagination-specific configuration: * - The number of visible content rows and the total inventory rows via [paginationViewRows] + * - The number of rows left empty next to the content via [paginationEmptyRows] * - The position (top or bottom) of the previous/next navigation buttons via [paginationButtonPosition] * * Create instances via the DSL builder [paginatedViewSettings] or through the * [PaginatedViewSettingsBuilder] within a `settings { }` DSL block. * * @property font the Adventure [Key] of the font used for the inventory title - * @property headerTextAlignment horizontal alignment of the title text in the header + * @property headerTextAlignment horizontal alignment of the title text + * @property headerTextColor colour for header text that does not carry one of its own + * @property headerFontMetrics glyph metrics of the font the title is rendered in + * @property rowFontMetrics glyph metrics of the fonts row text is rendered in + * @property headerGeometry the pixel geometry of the inventory and its slot grid + * @property backgroundGlyph whether the per-row background glyph of a custom inventory texture is + * rendered + * @property rowFonts the fonts that render header text on a slot row, keyed by one-based row in the header * @property cancelOnClick whether inventory click events should be cancelled by default * @property cancelOnDrag whether inventory drag events should be cancelled by default * @property cancelOnDrop whether item-drop events should be cancelled by default * @property cancelOnPickup whether item-pickup events should be cancelled by default * @property navigateBackOnOutsideClick whether an outside click navigates to the parent view * @property paginationViewRows the [PaginationViewRows] controlling the number of content rows + * @property paginationEmptyRows the [PaginationEmptyRows] left empty at the inventory edge opposite + * the navigation buttons; taken out of the content rows, not added to the inventory height * @property paginationButtonPosition the [PaginationButtonPosition] for navigation buttons + * @property paginationPageIndicator renders the page counter between the navigation buttons, or + * `null` to render none + * @property paginationSwitchSound the [Sound] played to the viewer when a navigation button + * switches to another page, or `null` to play none * @see SurfViewSettings * @see PaginationViewRows + * @see PaginationEmptyRows * @see PaginationButtonPosition */ data class PaginatedViewSettings( override val font: Key = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT, override val headerTextAlignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_HEADER_ALIGNMENT, + override val headerTextColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + override val headerFontMetrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT_METRICS, + override val rowFontMetrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS, + override val headerGeometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, + override val backgroundGlyph: Boolean = SurfViewSettingsDefaults.DEFAULT_BACKGROUND_GLYPH, + override val rowFonts: Int2ObjectMap = SurfViewSettingsDefaults.DEFAULT_ROW_FONTS, override val cancelOnClick: Boolean = SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_CLICK, override val cancelOnDrag: Boolean = SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_DRAG, override val cancelOnDrop: Boolean = SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_DROP, override val cancelOnPickup: Boolean = SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_PICKUP, override val navigateBackOnOutsideClick: Boolean = SurfViewSettingsDefaults.DEFAULT_NAVIGATE_BACK_ON_CLOSE, val paginationViewRows: PaginationViewRows = SurfViewSettingsDefaults.DEFAULT_PAGINATION_VIEW_ROWS, - val paginationButtonPosition: PaginationButtonPosition = SurfViewSettingsDefaults.DEFAULT_PAGINATION_BUTTON_POSITION + val paginationEmptyRows: PaginationEmptyRows = SurfViewSettingsDefaults.DEFAULT_PAGINATION_EMPTY_ROWS, + val paginationButtonPosition: PaginationButtonPosition = SurfViewSettingsDefaults.DEFAULT_PAGINATION_BUTTON_POSITION, + val paginationPageIndicator: PaginationPageIndicator? = SurfViewSettingsDefaults.DEFAULT_PAGINATION_PAGE_INDICATOR, + val paginationSwitchSound: Sound? = SurfViewSettingsDefaults.DEFAULT_PAGINATION_SWITCH_SOUND, ) : SurfViewSettings { override val rows: ViewRows = paginationViewRows.actualRows internal val paginationButtonRow = @@ -44,4 +72,26 @@ data class PaginatedViewSettings( } else { 1 } + + /** + * The one-based rows the pagination engine fills: every row except [paginationButtonRow] and + * the [paginationEmptyRows] rows kept free at the opposite inventory edge. + */ + internal val paginationContentRows: IntRange = + if (paginationButtonPosition == PaginationButtonPosition.BOTTOM) { + (1 + paginationEmptyRows.rows) until rows.rows + } else { + 2..(rows.rows - paginationEmptyRows.rows) + } + + /** How many rows the pagination engine actually fills with content. */ + val paginationContentRowCount: Int = rows.rows - 1 - paginationEmptyRows.rows + + init { + require(paginationContentRowCount >= 1) { + "$paginationViewRows leaves ${paginationViewRows.contentRows} content row(s) in a " + + "${rows.rows}-row inventory, which cannot hold $paginationEmptyRows empty " + + "row(s) as well - use fewer empty rows or more pagination rows" + } + } } \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows.kt new file mode 100644 index 000000000..cd8264db6 --- /dev/null +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/PaginationEmptyRows.kt @@ -0,0 +1,59 @@ +package dev.slne.surf.api.paper.inventory.framework.view.settings + +/** + * Defines how many rows of a paginated view are left empty instead of being filled by the + * pagination engine. + * + * The empty rows sit at the inventory edge opposite the navigation buttons, so they pad the + * pagination content away from that edge: with the buttons at the bottom + * ([PaginationButtonPosition.BOTTOM]) they are the topmost rows, with the buttons at the top + * ([PaginationButtonPosition.TOP]) they are the bottommost ones. + * + * The empty rows are taken out of the content rows - they do not enlarge the inventory. The total + * inventory height stays [PaginationViewRows.actualRows], so a view always keeps at least one + * content row: [PaginatedViewSettings] rejects a combination that would leave none. + * + * - [NONE] - no empty row; the content starts right at the inventory edge + * - [ONE] - 1 empty row + * - [TWO] - 2 empty rows + * - [THREE] - 3 empty rows + * + * @property rows the number of empty rows (0-3) + * @see PaginatedViewSettings + * @see PaginationViewRows + */ +enum class PaginationEmptyRows(val rows: Int) { + /** No empty row; the pagination content starts right at the inventory edge. */ + NONE(0), + + /** 1 empty row between the pagination content and the inventory edge. */ + ONE(1), + + /** 2 empty rows between the pagination content and the inventory edge. */ + TWO(2), + + /** 3 empty rows between the pagination content and the inventory edge. */ + THREE(3); + + companion object { + /** + * Returns the [PaginationEmptyRows] for the given [rows] count, or `null` if it is not in + * the range 0..3. + * + * @param rows the number of empty rows to look up (0..3) + * @return the matching [PaginationEmptyRows], or `null` + */ + fun byRowsOrNull(rows: Int): PaginationEmptyRows? = entries.getOrNull(rows) + + /** + * Returns the [PaginationEmptyRows] for the given [rows] count. + * + * @param rows the number of empty rows to look up (0..3) + * @return the matching [PaginationEmptyRows] + * @throws IllegalArgumentException if [rows] is not in the range 0..3 + */ + fun byRows(rows: Int): PaginationEmptyRows = requireNotNull(byRowsOrNull(rows)) { + "A paginated view supports 0 to 3 empty rows, but $rows were requested" + } + } +} diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows.kt index 4010e951b..be73d731b 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/PaginationViewRows.kt @@ -3,29 +3,50 @@ package dev.slne.surf.api.paper.inventory.framework.view.settings /** * Defines the number of pagination content rows and the total inventory size for a paginated view. * - * The total inventory has two additional rows compared to the content rows: one top row and one - * bottom row. The top and bottom rows are used for the inventory border/buttons, while the - * content rows are populated by the pagination engine. + * The total inventory has exactly one row more than the content rows: the row that carries the + * navigation buttons, which sits above or below the content depending on + * [PaginatedViewSettings.paginationButtonPosition]. Every other row is filled by the pagination + * engine - the topmost one included, so no row is left empty just to keep the buttons apart from + * the content. Set [PaginatedViewSettings.paginationEmptyRows] to keep one to three rows free + * again; those rows are taken out of the content rows, the inventory height stays [actualRows]. * - * - [ONE] — 1 content row, 3 total rows ([ViewRows.THREE]) - * - [TWO] — 2 content rows, 4 total rows ([ViewRows.FOUR]) - * - [THREE] — 3 content rows, 5 total rows ([ViewRows.FIVE]) - * - [FOUR] — 4 content rows, 6 total rows ([ViewRows.SIX]) + * - [ONE] - 1 content row, 2 total rows ([ViewRows.TWO]) + * - [TWO] - 2 content rows, 3 total rows ([ViewRows.THREE]) + * - [THREE] - 3 content rows, 4 total rows ([ViewRows.FOUR]) + * - [FOUR] - 4 content rows, 5 total rows ([ViewRows.FIVE]) + * - [FIVE] - 5 content rows, 6 total rows ([ViewRows.SIX]) * - * @property actualRows the total [ViewRows] value (including top and bottom rows) - * @property paginationContentRows the range of 1-based row indices used for pagination content + * @property actualRows the total [ViewRows] value: the content rows plus the button row * @see PaginatedViewSettings */ -enum class PaginationViewRows(val actualRows: ViewRows, val paginationContentRows: IntRange) { - /** 1 content row; 3 total inventory rows. */ - ONE(ViewRows.THREE, 2..2), +enum class PaginationViewRows(val actualRows: ViewRows) { + /** 1 content row; 2 total inventory rows. */ + ONE(ViewRows.TWO), - /** 2 content rows; 4 total inventory rows. */ - TWO(ViewRows.FOUR, 2..3), + /** 2 content rows; 3 total inventory rows. */ + TWO(ViewRows.THREE), - /** 3 content rows; 5 total inventory rows. */ - THREE(ViewRows.FIVE, 2..4), + /** 3 content rows; 4 total inventory rows. */ + THREE(ViewRows.FOUR), - /** 4 content rows; 6 total inventory rows. */ - FOUR(ViewRows.SIX, 2..5); -} \ No newline at end of file + /** 4 content rows; 5 total inventory rows. */ + FOUR(ViewRows.FIVE), + + /** 5 content rows; 6 total inventory rows. */ + FIVE(ViewRows.SIX); + + /** + * How many rows the pagination engine fills at most: one less than [actualRows], because a + * single row is reserved for the navigation buttons. Configured empty rows reduce it further, + * see [PaginatedViewSettings.paginationContentRowCount]. + */ + val contentRows: Int = actualRows.rows - 1 + + /** @suppress */ + @Deprecated( + "The content rows depend on the button position now, use PaginatedViewSettings instead", + level = DeprecationLevel.HIDDEN + ) + val paginationContentRows: IntRange + get() = 1..contentRows +} diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/SimpleViewSettings.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/SimpleViewSettings.kt index 52642ff03..db2d4a7fd 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/SimpleViewSettings.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/SimpleViewSettings.kt @@ -1,7 +1,9 @@ package dev.slne.surf.api.paper.inventory.framework.view.settings import dev.slne.surf.api.paper.inventory.framework.view.settings.align.TextAlignment +import it.unimi.dsi.fastutil.ints.Int2ObjectMap import net.kyori.adventure.key.Key +import net.kyori.adventure.text.format.TextColor /** * View settings for a simple (non-paginated) [AbstractSurfView]. @@ -11,6 +13,13 @@ import net.kyori.adventure.key.Key * * @property font the Adventure [Key] of the font used for the inventory title * @property headerTextAlignment horizontal alignment of the title text + * @property headerTextColor colour for header text that does not carry one of its own + * @property headerFontMetrics glyph metrics of the font the title is rendered in + * @property rowFontMetrics glyph metrics of the fonts row text is rendered in + * @property headerGeometry the pixel geometry of the inventory and its slot grid + * @property backgroundGlyph whether the per-row background glyph of a custom inventory texture is + * rendered + * @property rowFonts the fonts that render header text on a slot row, keyed by one-based row * @property cancelOnClick whether click events should be cancelled by default * @property cancelOnDrag whether drag events should be cancelled by default * @property cancelOnDrop whether drop events should be cancelled by default @@ -23,6 +32,12 @@ import net.kyori.adventure.key.Key data class SimpleViewSettings( override val font: Key = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT, override val headerTextAlignment: TextAlignment = SurfViewSettingsDefaults.DEFAULT_HEADER_ALIGNMENT, + override val headerTextColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR, + override val headerFontMetrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT_METRICS, + override val rowFontMetrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS, + override val headerGeometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY, + override val backgroundGlyph: Boolean = SurfViewSettingsDefaults.DEFAULT_BACKGROUND_GLYPH, + override val rowFonts: Int2ObjectMap = SurfViewSettingsDefaults.DEFAULT_ROW_FONTS, override val cancelOnClick: Boolean = SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_CLICK, override val cancelOnDrag: Boolean = SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_DRAG, override val cancelOnDrop: Boolean = SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_DROP, diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettings.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettings.kt index 67f94dd3b..424c2da9e 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettings.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettings.kt @@ -1,7 +1,9 @@ package dev.slne.surf.api.paper.inventory.framework.view.settings import dev.slne.surf.api.paper.inventory.framework.view.settings.align.TextAlignment +import it.unimi.dsi.fastutil.ints.Int2ObjectMap import net.kyori.adventure.key.Key +import net.kyori.adventure.text.format.TextColor /** * Sealed interface that defines the full configuration contract for all Surf view types. @@ -10,14 +12,22 @@ import net.kyori.adventure.key.Key * [PaginatedViewSettings] (for paginated views). All properties have sensible defaults * defined in [SurfViewSettingsDefaults]. * - * @property font the Adventure [Key] of the resource-pack font for the inventory title + * @property font the Adventure [Key] of the font for the inventory title; the client's own font + * by default * @property headerTextAlignment horizontal [TextAlignment] of the title in the header + * @property headerTextColor colour for header text that does not carry one of its own + * @property headerFontMetrics glyph metrics of the font the title is rendered in + * @property rowFontMetrics glyph metrics of the fonts row text is rendered in + * @property headerGeometry the pixel geometry of the inventory and its slot grid + * @property backgroundGlyph whether the per-row background glyph of a custom inventory texture is + * rendered + * @property rowFonts the fonts that render header text on a slot row, keyed by one-based row * @property cancelOnClick whether inventory click events are cancelled by default * @property cancelOnDrag whether inventory drag events are cancelled by default * @property cancelOnDrop whether item-drop events are cancelled by default * @property cancelOnPickup whether item-pickup events are cancelled by default * @property navigateBackOnOutsideClick whether clicking outside the inventory navigates - * back to the previous (parent) view + * back to the previous (parent) view; nothing is drawn in the header for it * @property rows the number of chest rows displayed in the inventory * @see SimpleViewSettings * @see PaginatedViewSettings @@ -26,10 +36,26 @@ import net.kyori.adventure.key.Key sealed interface SurfViewSettings { val font: Key val headerTextAlignment: TextAlignment + val headerTextColor: TextColor + val headerFontMetrics: ViewFontMetrics + val rowFontMetrics: ViewFontMetrics + val headerGeometry: ViewHeaderGeometry + val backgroundGlyph: Boolean + val rowFonts: Int2ObjectMap val cancelOnClick: Boolean val cancelOnDrag: Boolean val cancelOnDrop: Boolean val cancelOnPickup: Boolean val navigateBackOnOutsideClick: Boolean val rows: ViewRows + + /** + * Returns the font that renders header text on the one-based slot [row]. + * + * Falls back to [SurfViewSettingsDefaults.rowFont] when [rowFonts] carries no entry for [row]. + * + * @param row the one-based slot row (1-6) + */ + fun rowFont(@ViewRows.Companion.Rows row: Int): Key = + rowFonts[row] ?: SurfViewSettingsDefaults.rowFont(row) } \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettingsDefaults.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettingsDefaults.kt index 659a11ff9..fdf48b5d2 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettingsDefaults.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/SurfViewSettingsDefaults.kt @@ -1,7 +1,15 @@ package dev.slne.surf.api.paper.inventory.framework.view.settings +import dev.slne.surf.api.core.inventory.framework.internal.ViewSlotGeometry import dev.slne.surf.api.core.messages.adventure.key +import dev.slne.surf.api.core.util.freeze +import dev.slne.surf.api.paper.inventory.framework.view.pagination.PaginationPageIndicator import dev.slne.surf.api.paper.inventory.framework.view.settings.align.TextAlignment +import it.unimi.dsi.fastutil.ints.Int2ObjectMap +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import net.kyori.adventure.key.Key +import net.kyori.adventure.sound.Sound +import net.kyori.adventure.text.format.TextColor /** * Centralised default values for all [SurfViewSettings] properties. @@ -13,7 +21,7 @@ import dev.slne.surf.api.paper.inventory.framework.view.settings.align.TextAlign * All defaults can be overridden per-view via the `settings { }` DSL block. * * @see SurfViewSettings - * @see dev.slne.surf.api.paper.api.inventory.framework.view.settings.builder.SurfViewSettingsBuilder + * @see dev.slne.surf.api.paper.inventory.framework.view.settings.builder.SurfViewSettingsBuilder */ object SurfViewSettingsDefaults { /** Default value for [SurfViewSettings.cancelOnClick]: `true`. */ @@ -31,21 +39,130 @@ object SurfViewSettingsDefaults { /** Default value for [SurfViewSettings.navigateBackOnOutsideClick]: `true`. */ const val DEFAULT_NAVIGATE_BACK_ON_CLOSE = true - /** The Adventure [Key] for the default header font (`surf:menu_font_normal_ascended`). */ - val DEFAULT_HEADER_FONT = key("surf", "menu_font_normal_ascended") + /** + * The Adventure [Key] for the default header font: the client's own font + * (`minecraft:default`). + * + * The title is rendered like any other vanilla inventory title — plain, in the player's font, + * at the top of the header. Its horizontal position is computed from the proportional glyph + * widths of that font, see + * [DefaultFontWidths][dev.slne.surf.api.core.inventory.framework.internal.DefaultFontWidths]. + */ + val DEFAULT_HEADER_FONT = key("minecraft", "default") - /** The Adventure [Key] for the default menu font (`surf:menu`), used for the title component. */ + /** + * The Adventure [Key] of the ascended copy of the vanilla font (`surf:menu_font_normal_ascended`). + * + * Renders text one line below the title row. Pass it to + * [font][dev.slne.surf.api.paper.inventory.framework.view.settings.builder.SurfViewSettingsBuilder.font] + * for a view whose header text should sit lower than the vanilla title position. + */ + val ASCENDED_HEADER_FONT = key("surf", "menu_font_normal_ascended") + + /** The Adventure [Key] for the default menu font (`surf:menu`), used for the header textures. */ val DEFAULT_MENU_FONT = key("surf", "menu") /** Default [TextAlignment] for the header: [TextAlignment.CENTER]. */ val DEFAULT_HEADER_ALIGNMENT = TextAlignment.CENTER + /** + * Default colour for header text that does not carry one of its own: `#404040`, the colour + * vanilla draws inventory titles in. + * + * Both the title and any row text are drawn on the vanilla inventory background, which is light + * grey — white text would be all but invisible on it. Pass a coloured component (or set + * [SurfViewSettings.headerTextColor]) for anything else. + */ + val DEFAULT_HEADER_TEXT_COLOR: TextColor = TextColor.color(0x404040) + + /** + * Default value for [SurfViewSettings.backgroundGlyph]: `false`. + * + * Views render on the vanilla inventory background. Set it to `true` for a resource pack that + * draws its own inventory texture through the per-row glyphs of + * [ViewContainerGlyphComponent][dev.slne.surf.api.paper.inventory.framework.view.container.component.components.ViewContainerGlyphComponent], + * and re-measure [ViewHeaderGeometry] for that texture. + */ + const val DEFAULT_BACKGROUND_GLYPH = false + + /** Default [ViewHeaderGeometry]: the pixel layout of the current header texture. */ + val DEFAULT_HEADER_GEOMETRY = ViewHeaderGeometry.DEFAULT + + /** + * Default [ViewFontMetrics] for the inventory title: [ViewFontMetrics.VANILLA], matching the + * client font of [DEFAULT_HEADER_FONT]. + */ + val DEFAULT_HEADER_FONT_METRICS = ViewFontMetrics.VANILLA + + /** + * Default [ViewFontMetrics] for text rendered on a slot row: [ViewFontMetrics.VANILLA_ROW], + * because the row fonts of [rowFont] are copies of the client font glyph sheets, ascended onto + * their row - same widths as the client font, but without its space provider. + * + * Switch it to [ViewFontMetrics.SURF_MENU] for row fonts built from the fixed-width, + * capitals-only menu font texture instead. + */ + val DEFAULT_ROW_FONT_METRICS = ViewFontMetrics.VANILLA_ROW + + /** Default [TextAlignment] for text rendered on a slot row: [TextAlignment.CENTER]. */ + val DEFAULT_ROW_TEXT_ALIGNMENT = TextAlignment.CENTER + + /** Default column span for text rendered on a slot row: the full width of the slot grid. */ + val DEFAULT_ROW_TEXT_COLUMNS = 0 until ViewSlotGeometry.COLUMNS + /** Default number of rows for simple views: [ViewRows.FIVE]. */ val DEFAULT_VIEW_ROWS = ViewRows.FIVE - /** Default [PaginationViewRows] for paginated views: [PaginationViewRows.FOUR]. */ - val DEFAULT_PAGINATION_VIEW_ROWS = PaginationViewRows.FOUR + /** Default [PaginationViewRows] for paginated views: [PaginationViewRows.FIVE]. */ + val DEFAULT_PAGINATION_VIEW_ROWS = PaginationViewRows.FIVE + + /** + * Default [PaginationEmptyRows] for paginated views: [PaginationEmptyRows.NONE], so the + * pagination content fills every row except the navigation button row. + */ + val DEFAULT_PAGINATION_EMPTY_ROWS = PaginationEmptyRows.NONE /** Default [PaginationButtonPosition] for paginated views: [PaginationButtonPosition.BOTTOM]. */ val DEFAULT_PAGINATION_BUTTON_POSITION = PaginationButtonPosition.BOTTOM -} \ No newline at end of file + + /** + * Default [PaginationPageIndicator] for paginated views: [PaginationPageIndicator.Default], + * which renders `current/total` between the navigation buttons. + */ + val DEFAULT_PAGINATION_PAGE_INDICATOR: PaginationPageIndicator = PaginationPageIndicator.Default + + /** + * Default [Sound] played to the viewer when a paginated view switches to another page: + * `minecraft:item.book.page_turn`, the sound the client plays when a book page is turned. + */ + val DEFAULT_PAGINATION_SWITCH_SOUND: Sound = Sound.sound( + key("minecraft", "item.book.page_turn"), + Sound.Source.PLAYER, + 1f, + 1f + ) + + /** + * Returns the default font that renders header text on the one-based slot [row]: + * `surf:menu_font_row_1` … `surf:menu_font_row_6`. + * + * A glyph's vertical position is baked into the `ascent` of its font provider in the resource + * pack, so text can only be moved down a row by rendering it in a font that declares the + * matching `ascent`. Each of these fonts is therefore a copy of the vanilla font whose `ascent` + * puts its glyphs in the vertical centre of the row - not above its slots. + * + * @param row the one-based slot row (1–6) + */ + fun rowFont(@ViewRows.Companion.Rows row: Int): Key = key("surf", "menu_font_row_$row") + + /** + * Default [SurfViewSettings.rowFonts]: the [rowFont] of every slot row a chest inventory can + * have, keyed by its one-based row number. + */ + val DEFAULT_ROW_FONTS: Int2ObjectMap = + Int2ObjectOpenHashMap(ViewSlotGeometry.MAX_ROWS).apply { + for (row in 1..ViewSlotGeometry.MAX_ROWS) { + put(row, rowFont(row)) + } + }.freeze() +} diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics.kt new file mode 100644 index 000000000..9ca08eebe --- /dev/null +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/ViewFontMetrics.kt @@ -0,0 +1,112 @@ +package dev.slne.surf.api.paper.inventory.framework.view.settings + +import dev.slne.surf.api.core.inventory.framework.internal.DefaultFontWidths +import dev.slne.surf.api.core.util.int2IntMapOf +import it.unimi.dsi.fastutil.ints.Int2IntMap +import it.unimi.dsi.fastutil.ints.Int2IntMaps + +/** + * Metrics of the font a piece of header text is rendered in: how wide its glyphs are, how much + * spacing goes between them, and whether it only draws capitals. + * + * Header text is positioned by shifting the render cursor, so its width has to be known before it + * is drawn. That width can only be derived from the font: [VANILLA] describes the proportional + * client font (widths from [DefaultFontWidths]), [SURF_MENU] the fixed-width resource-pack menu + * font, and a custom instance any other font in the pack. + * + * ```kotlin + * settings { + * // A row font drawn 10px per glyph with no extra spacing, capitals and lowercase: + * rowFontMetrics(ViewFontMetrics(charSize = 10)) + * } + * ``` + * + * @property charSize pixel advance of a glyph that has no entry in [charWidths]; for a bitmap font + * this is its cell width including the one pixel the font renderer adds after every glyph + * @property charSpacing pixels of spacing inserted in front of every glyph — negative tightens the + * run, `0` renders the text as-is. The spacing is emitted as shift glyphs **inside** the text, so + * the font must provide them + * @property uppercase whether the text is uppercased before rendering, for a font that only draws + * capitals + * @property spaceAsShift whether a space is rendered as a shift glyph instead of as a space. A font + * built from a glyph sheet draws no space of its own, so the client would fall back to the + * missing-glyph box, which lands on the title line instead of in the row - rendering the space as + * a shift of [advance] pixels avoids depending on the font providing one + * @property charWidths per-code-point advance overrides for the glyphs the font does not render at + * [charSize] pixels + * @see SurfViewSettings.headerFontMetrics + * @see SurfViewSettings.rowFontMetrics + */ +data class ViewFontMetrics( + val charSize: Int = DefaultFontWidths.DEFAULT_ADVANCE, + val charSpacing: Int = 0, + val uppercase: Boolean = false, + val spaceAsShift: Boolean = false, + val charWidths: Int2IntMap = Int2IntMaps.EMPTY_MAP, +) { + /** + * Pixels a space advances when it is rendered as a shift glyph, or `null` when the font draws + * the space itself. + */ + val spaceShift: Int? get() = if (spaceAsShift) advance(' '.code) else null + + /** + * Returns the pixel advance of [codePoint] in this font. + * + * @param codePoint the Unicode code point to measure + * @param bold whether the glyph is rendered bold, which widens it by one pixel + */ + fun advance(codePoint: Int, bold: Boolean = false): Int { + val advance = + if (charWidths.containsKey(codePoint)) charWidths.get(codePoint) else charSize + + return advance + if (bold) DefaultFontWidths.BOLD_EXTRA_ADVANCE else 0 + } + + companion object { + /** + * Metrics of the proportional client font (`minecraft:default`): per-glyph widths from + * [DefaultFontWidths], no extra spacing, text rendered as written. The client font brings + * its own space provider, so spaces are emitted as spaces. + */ + val VANILLA = ViewFontMetrics( + charSize = DefaultFontWidths.DEFAULT_ADVANCE, + charWidths = DefaultFontWidths.advances + ) + + /** + * Metrics of a row font that copies the client font glyph sheets onto a slot row: the same + * widths as [VANILLA], but the space is rendered as a shift glyph, because such a copy + * carries only the glyph sheets and no space provider. + */ + val VANILLA_ROW = VANILLA.copy(spaceAsShift = true) + + /** + * Metrics of the fixed-width resource-pack menu font, including the per-row fonts of + * [SurfViewSettingsDefaults.rowFont]: 9-pixel cells tightened by one pixel — 8 pixels per + * glyph — and uppercased, because the font only draws capitals. + * + * The space is the one exception: the font draws no space glyph, so it is rendered as a + * shift of [MENU_SPACE_SIZE] pixels. + */ + val SURF_MENU = ViewFontMetrics( + charSize = MENU_CHAR_SIZE, + charSpacing = MENU_CHAR_SPACING, + uppercase = true, + spaceAsShift = true, + charWidths = int2IntMapOf(' '.code to MENU_SPACE_SIZE) + ) + + /** Pixel advance of one glyph cell in the resource-pack menu font. */ + const val MENU_CHAR_SIZE = 9 + + /** Spacing that tightens the menu font to 8 pixels per glyph. */ + const val MENU_CHAR_SPACING = -1 + + /** + * Advance of a space in the menu font, taken from the space provider of the pack rather than + * from the glyph texture. + */ + const val MENU_SPACE_SIZE = 4 + } +} diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry.kt new file mode 100644 index 000000000..772c1175d --- /dev/null +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/ViewHeaderGeometry.kt @@ -0,0 +1,147 @@ +package dev.slne.surf.api.paper.inventory.framework.view.settings + +import dev.slne.surf.api.core.inventory.framework.internal.DefaultFontWidths +import dev.slne.surf.api.core.inventory.framework.internal.ViewSlotGeometry +import dev.slne.surf.api.paper.inventory.framework.view.settings.align.TextAlignment +import dev.slne.surf.api.paper.inventory.framework.view.settings.align.TextAlignmentOptions +import it.unimi.dsi.fastutil.ints.Int2IntMap + +/** + * Pixel geometry of the inventory header: how wide the inventory is and where its slot grid starts. + * + * Everything the header draws is a glyph inside the inventory title, so all of these values are + * horizontal offsets measured from the **title origin** — the pixel the client starts drawing the + * inventory title at. Negative values are left of it. + * + * The defaults describe the vanilla chest inventory, whose screen is 176 pixels wide with the title + * drawn 8 pixels in from its left edge (hence [titleLeftShift] `= -8`) and whose slot cells are + * 18 pixels apart starting one pixel left of the title origin (hence [slotOriginX] `= -1`). With + * those numbers the centre of the inventory and the centre of the slot grid coincide, so a centred + * title sits exactly above a centred row text. Only override them for a resource pack that replaces + * the inventory background with a texture of a different size. + * + * ```kotlin + * settings { + * headerGeometry { copy(titleWidth = 223, titleLeftShift = -48) } + * } + * ``` + * + * @property titleLeftShift pixel offset of the left edge of the inventory, and therefore of the + * area the title is aligned in + * @property titlePadding horizontal padding kept free on each side of the title area + * @property titleWidth width of the inventory in pixels, i.e. the width the title is aligned in + * @property slotOriginX pixel offset of the left edge of slot column `0` + * @property slotSize horizontal pitch of the slot grid: one slot plus its one-pixel border + * @property slotGlyphInset transparent padding on the left of the slot overlay texture, so a + * texture that does not start at its very first pixel still lands on the slot border + * @property rowTextPadding horizontal padding kept free on each side when text is aligned inside a + * span of slot columns + * @see SurfViewSettings.headerGeometry + */ +data class ViewHeaderGeometry( + val titleLeftShift: Int = DEFAULT_TITLE_LEFT_SHIFT, + val titlePadding: Int = DEFAULT_TITLE_PADDING, + val titleWidth: Int = DEFAULT_TITLE_WIDTH, + val slotOriginX: Int = DEFAULT_SLOT_ORIGIN_X, + val slotSize: Int = ViewSlotGeometry.SLOT_SIZE, + val slotGlyphInset: Int = DEFAULT_SLOT_GLYPH_INSET, + val rowTextPadding: Int = DEFAULT_ROW_TEXT_PADDING, +) { + /** Width in pixels of the full nine-column slot grid. */ + val slotAreaWidth: Int get() = ViewSlotGeometry.spanWidth(ViewSlotGeometry.COLUMNS, slotSize) + + /** + * Returns the pixel offset of the left edge of [column]. + * + * @param column the zero-based column index (`0`–`8`) + */ + fun columnShift(column: Int): Int = ViewSlotGeometry.columnShift(column, slotOriginX, slotSize) + + /** + * Returns the pixel offset a slot overlay texture is rendered at in [column]: the left edge of + * the column, moved right by the [slotGlyphInset] the texture keeps free on its left. + * + * @param column the zero-based column index (`0`-`8`) + */ + fun slotGlyphShift(column: Int): Int = columnShift(column) + slotGlyphInset + + /** + * Returns the [TextAlignmentOptions] that align text across the full width of the inventory — + * the geometry the inventory title itself uses. + * + * The returned options measure text in the vanilla font. Pass [charWidths] for glyphs + * [DefaultFontWidths] does not cover (custom resource-pack glyphs, CJK from the Unifont + * fallback), or a [charSize]/[charSpacing] pair describing a fixed-width resource-pack font. + * + * @param charWidths per-code-point advance overrides + * @param charSize advance of a glyph without an override + * @param charSpacing extra spacing inserted between adjacent glyphs + */ + fun titleOptions( + charWidths: Int2IntMap = DefaultFontWidths.advances, + charSize: Int = DefaultFontWidths.DEFAULT_ADVANCE, + charSpacing: Int = 0, + ): TextAlignmentOptions = TextAlignmentOptions( + leftShift = titleLeftShift, + padding = titlePadding, + containerWidth = titleWidth, + charSize = charSize, + charSpacing = charSpacing, + charWidths = charWidths + ) + + /** + * Returns the [TextAlignmentOptions] that align text inside the span of slot [columns], so a + * [TextAlignment] positions it relative to those slots rather than to the whole inventory. + * + * @param columns the zero-based, inclusive range of slot columns to align inside + * @param charWidths per-code-point advance overrides + * @param charSize advance of a glyph without an override + * @param charSpacing extra spacing inserted between adjacent glyphs + */ + fun columnSpanOptions( + columns: IntRange, + charWidths: Int2IntMap = DefaultFontWidths.advances, + charSize: Int = DefaultFontWidths.DEFAULT_ADVANCE, + charSpacing: Int = 0, + ): TextAlignmentOptions { + require(!columns.isEmpty()) { "A column span must cover at least one column" } + + return TextAlignmentOptions( + leftShift = columnShift(columns.first), + padding = rowTextPadding, + containerWidth = ViewSlotGeometry.spanWidth(columns.count(), slotSize), + charSize = charSize, + charSpacing = charSpacing, + charWidths = charWidths + ) + } + + companion object { + /** Default [titleLeftShift]: the vanilla inventory starts 8 pixels left of the title origin. */ + const val DEFAULT_TITLE_LEFT_SHIFT = -8 + + /** Default [titlePadding]: the title may use the full width of the inventory. */ + const val DEFAULT_TITLE_PADDING = 0 + + /** Default [titleWidth]: the width of the vanilla chest inventory screen. */ + const val DEFAULT_TITLE_WIDTH = 176 + + /** Default [slotOriginX]: slot column `0` starts one pixel left of the title origin. */ + const val DEFAULT_SLOT_ORIGIN_X = -1 + + /** + * Default [slotGlyphInset]: none. The slot overlay glyphs of the pack are 18x18 textures that + * start at their first pixel, so they land on the cell border the grid is measured from. + * Set it to the number of transparent columns your texture carries on its left instead of + * re-cutting the texture. + */ + const val DEFAULT_SLOT_GLYPH_INSET = 0 + + /** Default [rowTextPadding]: one pixel of breathing room inside a column span. */ + const val DEFAULT_ROW_TEXT_PADDING = 1 + + /** The geometry of the vanilla chest inventory. */ + val DEFAULT = ViewHeaderGeometry() + } +} diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/builder/PaginatedViewSettingsBuilder.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/builder/PaginatedViewSettingsBuilder.kt index 9c2569377..11f9af0aa 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/builder/PaginatedViewSettingsBuilder.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/builder/PaginatedViewSettingsBuilder.kt @@ -1,16 +1,20 @@ package dev.slne.surf.api.paper.inventory.framework.view.settings.builder import dev.slne.surf.api.paper.inventory.framework.view.InventoryFrameworkDSL +import dev.slne.surf.api.paper.inventory.framework.view.pagination.PaginationPageIndicator import dev.slne.surf.api.paper.inventory.framework.view.settings.PaginatedViewSettings import dev.slne.surf.api.paper.inventory.framework.view.settings.PaginationButtonPosition +import dev.slne.surf.api.paper.inventory.framework.view.settings.PaginationEmptyRows import dev.slne.surf.api.paper.inventory.framework.view.settings.PaginationViewRows import dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettingsDefaults +import net.kyori.adventure.sound.Sound /** * DSL builder for [PaginatedViewSettings]. * * Extends [SurfViewSettingsBuilder] with pagination-specific properties: * - [paginationViewRows] — controls the number of visible item rows and total inventory height + * - [paginationEmptyRows] — controls how many rows stay empty next to the pagination content * - [paginationButtonPosition] — controls whether the prev/next buttons are at the top or bottom * * Create instances via [paginatedViewSettings] or the `settings { }` DSL function in a @@ -51,6 +55,40 @@ class PaginatedViewSettingsBuilder @PublishedApi internal constructor() : this.paginationViewRows = rows } + /** + * The [PaginationEmptyRows] controlling how many rows stay empty at the inventory edge opposite + * the navigation buttons. They are taken out of the content rows, so the inventory height stays + * the one of [paginationViewRows]. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_PAGINATION_EMPTY_ROWS]. + */ + var paginationEmptyRows: PaginationEmptyRows = + SurfViewSettingsDefaults.DEFAULT_PAGINATION_EMPTY_ROWS + private set + + /** + * Sets the [PaginationEmptyRows]. + * + * @param emptyRows the desired [PaginationEmptyRows] + */ + fun paginationEmptyRows(emptyRows: PaginationEmptyRows) { + this.paginationEmptyRows = emptyRows + } + + /** + * Sets the number of rows that stay empty next to the pagination content. + * + * @param emptyRows the number of empty rows (0..3) + * @throws IllegalArgumentException if [emptyRows] is not in the range 0..3 + */ + fun paginationEmptyRows(emptyRows: Int) { + paginationEmptyRows(PaginationEmptyRows.byRows(emptyRows)) + } + + /** Shorthand for `paginationEmptyRows(PaginationEmptyRows.NONE)`. */ + fun noPaginationEmptyRows() { + paginationEmptyRows(PaginationEmptyRows.NONE) + } + /** * The [PaginationButtonPosition] controlling whether navigation buttons are at the top or bottom. * Defaults to [SurfViewSettingsDefaults.DEFAULT_PAGINATION_BUTTON_POSITION]. @@ -68,6 +106,58 @@ class PaginatedViewSettingsBuilder @PublishedApi internal constructor() : this.paginationButtonPosition = position } + /** + * Renders the page counter between the navigation buttons, or `null` to render none. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_PAGINATION_PAGE_INDICATOR], which renders + * `current/total`. + */ + var paginationPageIndicator: PaginationPageIndicator? = + SurfViewSettingsDefaults.DEFAULT_PAGINATION_PAGE_INDICATOR + private set + + /** + * Sets the [PaginationPageIndicator] that renders the page counter. + * + * ```kotlin + * settings { + * paginationPageIndicator { current, total -> text("Seite $current von $total") } + * } + * ``` + * + * @param indicator renders the counter, or `null` to render none + */ + fun paginationPageIndicator(indicator: PaginationPageIndicator?) { + this.paginationPageIndicator = indicator + } + + /** Renders no page counter between the navigation buttons. */ + fun noPaginationPageIndicator() { + paginationPageIndicator(null) + } + + /** + * The [Sound] played to the viewer when a navigation button switches to another page, or `null` + * to play none. Defaults to [SurfViewSettingsDefaults.DEFAULT_PAGINATION_SWITCH_SOUND], the + * vanilla book page-turn sound. + */ + var paginationSwitchSound: Sound? = + SurfViewSettingsDefaults.DEFAULT_PAGINATION_SWITCH_SOUND + private set + + /** + * Sets the [Sound] played when a navigation button switches to another page. + * + * @param sound the sound to play, or `null` to play none + */ + fun paginationSwitchSound(sound: Sound?) { + this.paginationSwitchSound = sound + } + + /** Plays no sound when a navigation button switches to another page. */ + fun noPaginationSwitchSound() { + paginationSwitchSound(null) + } + /** Shorthand for `paginationButtonPosition(PaginationButtonPosition.BOTTOM)`. */ fun paginationButtonsAtBottom() { paginationButtonPosition(PaginationButtonPosition.BOTTOM) @@ -82,13 +172,22 @@ class PaginatedViewSettingsBuilder @PublishedApi internal constructor() : override fun build(): PaginatedViewSettings = PaginatedViewSettings( font = font, headerTextAlignment = headerTextAlignment, + headerTextColor = headerTextColor, + headerFontMetrics = headerFontMetrics, + rowFontMetrics = rowFontMetrics, + headerGeometry = headerGeometry, + backgroundGlyph = backgroundGlyph, + rowFonts = rowFonts, cancelOnClick = cancelOnClick, cancelOnDrag = cancelOnDrag, cancelOnDrop = cancelOnDrop, cancelOnPickup = cancelOnPickup, navigateBackOnOutsideClick = navigateBackOnOutsideClick, paginationViewRows = paginationViewRows, + paginationEmptyRows = paginationEmptyRows, paginationButtonPosition = paginationButtonPosition, + paginationPageIndicator = paginationPageIndicator, + paginationSwitchSound = paginationSwitchSound, ) } diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/builder/SimpleViewSettingsBuilder.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/builder/SimpleViewSettingsBuilder.kt index a1d6d373e..80f3f1b31 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/builder/SimpleViewSettingsBuilder.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/builder/SimpleViewSettingsBuilder.kt @@ -61,6 +61,12 @@ class SimpleViewSettingsBuilder @PublishedApi internal constructor() : SurfViewS override fun build(): SimpleViewSettings = SimpleViewSettings( font = font, headerTextAlignment = headerTextAlignment, + headerTextColor = headerTextColor, + headerFontMetrics = headerFontMetrics, + rowFontMetrics = rowFontMetrics, + headerGeometry = headerGeometry, + backgroundGlyph = backgroundGlyph, + rowFonts = rowFonts, cancelOnClick = cancelOnClick, cancelOnDrag = cancelOnDrag, cancelOnDrop = cancelOnDrop, diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/builder/SurfViewSettingsBuilder.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/builder/SurfViewSettingsBuilder.kt index 1793102ec..e6b732ad7 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/builder/SurfViewSettingsBuilder.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/inventory/framework/view/settings/builder/SurfViewSettingsBuilder.kt @@ -1,10 +1,16 @@ package dev.slne.surf.api.paper.inventory.framework.view.settings.builder +import dev.slne.surf.api.core.util.freeze import dev.slne.surf.api.paper.inventory.framework.view.InventoryFrameworkDSL import dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettings import dev.slne.surf.api.paper.inventory.framework.view.settings.SurfViewSettingsDefaults +import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewFontMetrics +import dev.slne.surf.api.paper.inventory.framework.view.settings.ViewHeaderGeometry import dev.slne.surf.api.paper.inventory.framework.view.settings.align.TextAlignment +import it.unimi.dsi.fastutil.ints.Int2ObjectMap +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import net.kyori.adventure.key.Key +import net.kyori.adventure.text.format.TextColor /** * Abstract base builder for all [SurfViewSettings] implementations. @@ -55,6 +61,144 @@ sealed class SurfViewSettingsBuilder { this.headerTextAlignment = alignment } + /** + * Colour for header text that does not carry a colour of its own. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR]. + */ + var headerTextColor: TextColor = SurfViewSettingsDefaults.DEFAULT_HEADER_TEXT_COLOR + private set + + /** + * Sets the colour used for header text that does not carry one of its own. + * + * @param color the colour to fall back to + */ + fun headerTextColor(color: TextColor) { + this.headerTextColor = color + } + + /** + * Glyph metrics of the font the title is rendered in. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_HEADER_FONT_METRICS]. + */ + var headerFontMetrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_HEADER_FONT_METRICS + private set + + /** + * Sets the glyph metrics of the title font. + * + * Has to match the font set through [font], otherwise the title is measured wrong and lands off + * centre. + * + * @param metrics the metrics of the title font + */ + fun headerFontMetrics(metrics: ViewFontMetrics) { + this.headerFontMetrics = metrics + } + + /** + * Glyph metrics of the fonts row text is rendered in. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS]. + */ + var rowFontMetrics: ViewFontMetrics = SurfViewSettingsDefaults.DEFAULT_ROW_FONT_METRICS + private set + + /** + * Sets the glyph metrics of the row fonts. + * + * Has to match the fonts set through [rowFont], otherwise row text is measured wrong and lands + * off centre. Pass [ViewFontMetrics.VANILLA] for row fonts that are copies of the client font. + * + * @param metrics the metrics of the row fonts + */ + fun rowFontMetrics(metrics: ViewFontMetrics) { + this.rowFontMetrics = metrics + } + + /** + * Whether the per-row background glyph of a custom inventory texture is rendered. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_BACKGROUND_GLYPH]. + */ + var backgroundGlyph: Boolean = SurfViewSettingsDefaults.DEFAULT_BACKGROUND_GLYPH + private set + + /** + * Sets whether the view draws its own inventory background through the per-row glyphs of + * [ViewContainerGlyphComponent][dev.slne.surf.api.paper.inventory.framework.view.container.component.components.ViewContainerGlyphComponent]. + * + * Leave it off for a view rendering on the vanilla inventory texture. When enabling it, also + * re-measure [headerGeometry] for the custom texture. + * + * @param render `true` to render the background glyph; defaults to `true` + */ + fun backgroundGlyph(render: Boolean = true) { + this.backgroundGlyph = render + } + + /** + * The [ViewHeaderGeometry] describing the pixel layout of the inventory. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY]. + */ + var headerGeometry: ViewHeaderGeometry = SurfViewSettingsDefaults.DEFAULT_HEADER_GEOMETRY + private set + + /** + * Sets the [ViewHeaderGeometry] of the header. + * + * @param geometry the geometry of the header texture in use + */ + fun headerGeometry(geometry: ViewHeaderGeometry) { + this.headerGeometry = geometry + } + + /** + * Derives the [ViewHeaderGeometry] of the header from the current one. + * + * ```kotlin + * settings { + * headerGeometry { copy(titleWidth = 240) } + * } + * ``` + * + * @param block returns the geometry to use, with the current one as its receiver + */ + fun headerGeometry(block: ViewHeaderGeometry.() -> ViewHeaderGeometry) { + this.headerGeometry = headerGeometry.block() + } + + /** + * The fonts that render header text on a slot row, keyed by one-based row number. + * Defaults to [SurfViewSettingsDefaults.DEFAULT_ROW_FONTS]. + */ + var rowFonts: Int2ObjectMap = SurfViewSettingsDefaults.DEFAULT_ROW_FONTS + private set + + /** + * Replaces the whole row-font mapping. + * + * Rows without an entry fall back to [SurfViewSettingsDefaults.rowFont]. + * + * @param fonts the fonts to use, keyed by one-based row number + */ + fun rowFonts(fonts: Int2ObjectMap) { + this.rowFonts = fonts + } + + /** + * Overrides the font that renders header text on a single slot [row]. + * + * Each row needs its own font because a glyph's vertical position comes from the `ascent` its + * font provider declares in the resource pack — see [SurfViewSettingsDefaults.rowFont]. + * + * @param row the one-based slot row (1-6) + * @param font the Adventure [Key] of the font ascended onto that row + */ + fun rowFont(row: Int, font: Key) { + val fonts = Int2ObjectOpenHashMap(rowFonts) + fonts.put(row, font) + this.rowFonts = fonts.freeze() + } + /** * Whether click events in the inventory are cancelled by default. * Defaults to [SurfViewSettingsDefaults.DEFAULT_CANCEL_ON_CLICK].