From 9b1e591387a337fc32d83fd05637aca8ce6b901c Mon Sep 17 00:00:00 2001 From: OneLiteFeather Date: Sat, 13 Jun 2026 14:23:49 +0200 Subject: [PATCH 1/3] chore(build): update Java version to 25 and paper dependencies to 26.1.2.build.69-stable --- build.gradle.kts | 2 +- settings.gradle.kts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 939da08..16eaa7c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,7 +46,7 @@ allprojects { configure { toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) + languageVersion.set(JavaLanguageVersion.of(25)) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index f327276..c20a94b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,12 +18,12 @@ dependencyResolutionManagement { create("libs") { version("modrinth", "2.+") version("hangar", "0.1.4") - version("paper.yml", "0.6.0") + version("paper.yml", "0.9.0") version("paper.run", "3.0.2") version("shadowJar", "9.3.0") version("cyclonedx", "3.1.0") - version("paper", "1.21.8-R0.1-SNAPSHOT") + version("paper", "26.1.2.build.69-stable") version("bstats", "3.1.0") version("customblockdata", "2.2.5") @@ -93,7 +93,7 @@ dependencyResolutionManagement { plugin("modrinth", "com.modrinth.minotaur").versionRef("modrinth") plugin("hangar", "io.papermc.hangar-publish-plugin").versionRef("hangar") - plugin("paper.yml", "net.minecrell.plugin-yml.paper").versionRef("paper.yml") + plugin("paper.yml", "de.eldoria.plugin-yml.paper").versionRef("paper.yml") plugin("paper.run", "xyz.jpenilla.run-paper").versionRef("paper.run") plugin("shadowJar", "com.gradleup.shadow").versionRef("shadowJar") plugin("cyclonedx", "org.cyclonedx.bom").versionRef("cyclonedx") From 331fd47773fdb9cae221b3be84cdb8afdc3c4d07 Mon Sep 17 00:00:00 2001 From: OneLiteFeather Date: Sat, 13 Jun 2026 14:32:48 +0200 Subject: [PATCH 2/3] chore(dependencies): update Paper and ShadowJar versions in build configuration --- build.gradle.kts | 6 +++++- settings.gradle.kts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 16eaa7c..681ffbe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,7 +37,11 @@ val supportedMinecraftVersions = listOf( "1.21.7", "1.21.8", "1.21.9", - "1.21.10" + "1.21.10", + "1.21.11", + "26.0", + "26.1", + "26.1.2.build.69-stable" ) allprojects { apply { diff --git a/settings.gradle.kts b/settings.gradle.kts index c20a94b..dcf8091 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,7 +20,7 @@ dependencyResolutionManagement { version("hangar", "0.1.4") version("paper.yml", "0.9.0") version("paper.run", "3.0.2") - version("shadowJar", "9.3.0") + version("shadowJar", "9.4.2") version("cyclonedx", "3.1.0") version("paper", "26.1.2.build.69-stable") From d78d8ce30bf570cfe4086c63e09e33b7464b5895 Mon Sep 17 00:00:00 2001 From: OneLiteFeather Date: Sat, 13 Jun 2026 17:48:02 +0200 Subject: [PATCH 3/3] feat(pagination): implement pagination feature from adventure with customizable options and use right version of paper --- build.gradle.kts | 4 +- settings.gradle.kts | 8 +- .../commands/DisplayActiveClocksCommand.java | 3 +- .../text/Pagination.java | 324 ++++++++++++++++++ .../text/PaginationBuilder.java | 135 ++++++++ .../text/PaginationImpl.java | 225 ++++++++++++ .../text/Paginator.java | 56 +++ 7 files changed, 746 insertions(+), 9 deletions(-) create mode 100644 src/main/java/net/onelitefeather/antiredstoneclockremastered/text/Pagination.java create mode 100644 src/main/java/net/onelitefeather/antiredstoneclockremastered/text/PaginationBuilder.java create mode 100644 src/main/java/net/onelitefeather/antiredstoneclockremastered/text/PaginationImpl.java create mode 100644 src/main/java/net/onelitefeather/antiredstoneclockremastered/text/Paginator.java diff --git a/build.gradle.kts b/build.gradle.kts index 681ffbe..89378d7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -41,7 +41,8 @@ val supportedMinecraftVersions = listOf( "1.21.11", "26.0", "26.1", - "26.1.2.build.69-stable" + "26.1.1", + "26.1.2" ) allprojects { apply { @@ -68,7 +69,6 @@ dependencies { implementation(libs.cloud.command.extras) implementation(libs.cloud.command.annotations) implementation(libs.semver) - implementation(libs.adventure.text.feature.pagination) implementation(libs.adventure.text.discord) implementation(libs.jda.webhook) implementation(libs.customblockdata) diff --git a/settings.gradle.kts b/settings.gradle.kts index dcf8091..b43a12c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,7 +23,7 @@ dependencyResolutionManagement { version("shadowJar", "9.4.2") version("cyclonedx", "3.1.0") - version("paper", "26.1.2.build.69-stable") + version("paper", "26.1.2.build.+") version("bstats", "3.1.0") version("customblockdata", "2.2.5") @@ -32,7 +32,6 @@ dependencyResolutionManagement { version("cloudcommandAnnotations", "2.0.0") version("cloudcommandExtras", "2.0.0-SNAPSHOT") - version("adventure-text-feature-pagination", "4.0.0-SNAPSHOT") version("adventure-text-discord-serializer", "4.4.0") version("jda", "5.6.1") version("jda-webhook", "0.8.4") @@ -49,14 +48,14 @@ dependencyResolutionManagement { // WorldGuard version("wgv6", "6.2") - version("wgv7", "7.0.14") + version("wgv7", "7.0.17") library("wg6", "com.sk89q.worldguard", "worldguard-legacy").versionRef("wgv6") library("wg7", "com.sk89q.worldguard", "worldguard-bukkit").versionRef("wgv7") // PlotSquared version("psv4", "4.453") version("psv6", "1.52") - version("psv7", "1.55") + version("psv7", "1.56") library("psv4.core", "com.github.IntellectualSites.PlotSquared","Core").versionRef("psv4") library("psv4.bukkit", "com.github.IntellectualSites.PlotSquared","Bukkit").versionRef("psv4") @@ -69,7 +68,6 @@ dependencyResolutionManagement { library("psv7.bukkit", "com.intellectualsites.plotsquared","plotsquared-bukkit").withoutVersion() library("paper", "io.papermc.paper", "paper-api").versionRef("paper") - library("adventure.text.feature.pagination", "net.kyori", "adventure-text-feature-pagination").versionRef("adventure-text-feature-pagination") library("adventure.text.discord", "dev.vankka", "mcdiscordreserializer").versionRef("adventure-text-discord-serializer") library("jda-webhook", "club.minnced", "discord-webhooks").versionRef("jda-webhook") library("bstats", "org.bstats", "bstats-bukkit").versionRef("bstats") diff --git a/src/main/java/net/onelitefeather/antiredstoneclockremastered/commands/DisplayActiveClocksCommand.java b/src/main/java/net/onelitefeather/antiredstoneclockremastered/commands/DisplayActiveClocksCommand.java index 3fc46e8..bd4d447 100644 --- a/src/main/java/net/onelitefeather/antiredstoneclockremastered/commands/DisplayActiveClocksCommand.java +++ b/src/main/java/net/onelitefeather/antiredstoneclockremastered/commands/DisplayActiveClocksCommand.java @@ -1,12 +1,11 @@ package net.onelitefeather.antiredstoneclockremastered.commands; import jakarta.inject.Inject; -import jakarta.inject.Named; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.feature.pagination.Pagination; import net.onelitefeather.antiredstoneclockremastered.AntiRedstoneClockRemastered; import net.onelitefeather.antiredstoneclockremastered.model.RedstoneClock; import net.onelitefeather.antiredstoneclockremastered.service.api.RedstoneTrackingService; +import net.onelitefeather.antiredstoneclockremastered.text.Pagination; import org.bukkit.command.CommandSender; import org.incendo.cloud.annotation.specifier.Greedy; import org.incendo.cloud.annotations.Argument; diff --git a/src/main/java/net/onelitefeather/antiredstoneclockremastered/text/Pagination.java b/src/main/java/net/onelitefeather/antiredstoneclockremastered/text/Pagination.java new file mode 100644 index 0000000..878ca86 --- /dev/null +++ b/src/main/java/net/onelitefeather/antiredstoneclockremastered/text/Pagination.java @@ -0,0 +1,324 @@ +/* + * This file is part of adventure-text-feature-pagination, licensed under the MIT License. + * + * Copyright (c) 2017-2020 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.onelitefeather.antiredstoneclockremastered.text; + +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Range; + +/** + * Pagination. + * + *

Vendored from {@code net.kyori:adventure-text-feature-pagination:4.0.0-SNAPSHOT} and + * adjusted for Adventure 5.x (generic {@link ClickEvent}, examination removed).

+ * + * @param the content type + */ +@FunctionalInterface +public interface Pagination { + /** + * The default interface width. + */ + int WIDTH = 55; + /** + * The default number of results per page. + */ + int RESULTS_PER_PAGE = 6; + /** + * The default line character. + */ + char LINE_CHARACTER = '-'; + /** + * The default line style. + */ + Style LINE_STYLE = Style.style(NamedTextColor.DARK_GRAY); + /** + * The default character for the previous page button. + */ + char PREVIOUS_PAGE_BUTTON_CHARACTER = '\u00AB'; // « + /** + * The default style for the next page button. + */ + Style PREVIOUS_PAGE_BUTTON_STYLE = Style.style( + NamedTextColor.RED, + HoverEvent.showText(Component.text("Previous Page", NamedTextColor.RED)) + ); + /** + * The default character for the next page button. + */ + char NEXT_PAGE_BUTTON_CHARACTER = '\u00BB'; // » + /** + * The default style for the next page button. + */ + Style NEXT_PAGE_BUTTON_STYLE = Style.style( + NamedTextColor.GREEN, + HoverEvent.showText(Component.text("Next Page", NamedTextColor.GREEN)) + ); + /** + * The default interface renderer. + */ + Renderer DEFAULT_RENDERER = new Renderer() { + @Override + public String toString() { + return "Pagination.DEFAULT_RENDERER"; + } + }; + + /** + * Creates a pagination builder. + * + * @return a builder + */ + static @NotNull Builder builder() { + return new PaginationBuilder(); + } + + /** + * Renders. + * + * @param content the content to render + * @param page the page number + * @return the rendered results + */ + @NotNull List render(final @NotNull Collection content, final int page); + + /** + * A pagination renderer. + */ + interface Renderer { + Component GRAY_LEFT_ROUND_BRACKET = Component.text("(", NamedTextColor.GRAY); + Component GRAY_LEFT_SQUARE_BRACKET = Component.text("[", NamedTextColor.GRAY); + Component GRAY_RIGHT_ROUND_BRACKET = Component.text(")", NamedTextColor.GRAY); + Component GRAY_RIGHT_SQUARE_BRACKET = Component.text("]", NamedTextColor.GRAY); + Component GRAY_FORWARD_SLASH = Component.text("/", NamedTextColor.GRAY); + + /** + * Renders an empty result. + * + *

No header or footer are rendered.

+ * + * @return the rendered component + */ + default @NotNull Component renderEmpty() { + return Component.text("No results match.", NamedTextColor.GRAY); + } + + /** + * Renders an unknown page. + * + *

No header or footer are rendered.

+ * + * @param page the unknown page + * @param pages the total number of pages + * @return the rendered component + */ + default @NotNull Component renderUnknownPage(final int page, final int pages) { + return Component.text("Unknown page selected. " + pages + " total pages.", NamedTextColor.GRAY); + } + + /** + * Renders a header. + * + * @param title the title + * @param page the page + * @param pages the total number of pages + * @return the rendered component + */ + default @NotNull Component renderHeader(final @NotNull Component title, final int page, final int pages) { + return Component.text() + .append(Component.space()) + .append(title) + .append(Component.space()) + .append(GRAY_LEFT_ROUND_BRACKET) + .append(Component.text(page, NamedTextColor.WHITE)) + .append(GRAY_FORWARD_SLASH) + .append(Component.text(pages, NamedTextColor.WHITE)) + .append(GRAY_RIGHT_ROUND_BRACKET) + .append(Component.space()) + .build(); + } + + /** + * Renders a previous page button. + * + * @param character the button character + * @param style the button style + * @param clickEvent the click event for the button + * @return the rendered component + */ + default @NotNull Component renderPreviousPageButton(final char character, final @NotNull Style style, final @NotNull ClickEvent clickEvent) { + return Component.text() + .append(Component.space()) + .append(GRAY_LEFT_SQUARE_BRACKET) + .append(Component.text(character, style.clickEvent(clickEvent))) + .append(GRAY_RIGHT_SQUARE_BRACKET) + .append(Component.space()) + .build(); + } + + /** + * Renders a next page button. + * + * @param character the button character + * @param style the button style + * @param clickEvent the click event for the button + * @return the rendered component + */ + default @NotNull Component renderNextPageButton(final char character, final @NotNull Style style, final @NotNull ClickEvent clickEvent) { + return Component.text() + .append(Component.space()) + .append(GRAY_LEFT_SQUARE_BRACKET) + .append(Component.text(character, style.clickEvent(clickEvent))) + .append(GRAY_RIGHT_SQUARE_BRACKET) + .append(Component.space()) + .build(); + } + + /** + * A row renderer. + * + * @param the content type + */ + @FunctionalInterface + interface RowRenderer { + /** + * Renders a row. + * + * @param value the value + * @param index the index + * @return the rendered row + */ + @NotNull Collection renderRow(final @Nullable T value, final int index); + } + } + + /** + * A page command function. + */ + @FunctionalInterface + interface PageCommandFunction { + /** + * Gets the command to display the page. + * + * @param page the page + * @return the command + */ + @NotNull String pageCommand(final int page); + } + + /** + * A pagination builder. + */ + interface Builder { + /** + * Sets the width. + * + * @param width the width + * @return this builder + */ + @NotNull Builder width(final int width); + + /** + * Sets the number of results per page. + * + * @param resultsPerPage the number of results per page + * @return this builder + */ + @NotNull Builder resultsPerPage(final @Range(from = 0, to = Integer.MAX_VALUE) int resultsPerPage); + + /** + * Sets the renderer. + * + * @param renderer the renderer + * @return this builder + */ + @NotNull Builder renderer(final @NotNull Renderer renderer); + + /** + * Sets the line character and style. + * + * @param line the line consumer + * @return this builder + */ + @NotNull Builder line(final @NotNull Consumer line); + + /** + * Sets the previous button. + * + * @param previousButton the button consumer + * @return this builder + */ + @NotNull Builder previousButton(final @NotNull Consumer previousButton); + + /** + * Sets the next button. + * + * @param nextButton the button consumer + * @return this builder + */ + @NotNull Builder nextButton(final @NotNull Consumer nextButton); + + /** + * Builds. + * + * @param title the title + * @param rowRenderer the row renderer + * @param pageCommand the page command + * @param the content type + * @return pagination + * @throws IllegalStateException if the title has not been set + * @throws IllegalStateException if the row renderer has not been set + */ + @NotNull Pagination build(final @NotNull Component title, final Renderer.@NotNull RowRenderer rowRenderer, final @NotNull PageCommandFunction pageCommand); + + /** + * A builder for a character and style pair. + */ + interface CharacterAndStyle { + /** + * Sets the character. + * + * @param character the character + * @return this builder + */ + @NotNull CharacterAndStyle character(final char character); + + /** + * Sets the style. + * + * @param style the style + * @return this builder + */ + @NotNull CharacterAndStyle style(final @NotNull Style style); + } + } +} diff --git a/src/main/java/net/onelitefeather/antiredstoneclockremastered/text/PaginationBuilder.java b/src/main/java/net/onelitefeather/antiredstoneclockremastered/text/PaginationBuilder.java new file mode 100644 index 0000000..c2f67ce --- /dev/null +++ b/src/main/java/net/onelitefeather/antiredstoneclockremastered/text/PaginationBuilder.java @@ -0,0 +1,135 @@ +/* + * This file is part of adventure-text-feature-pagination, licensed under the MIT License. + * + * Copyright (c) 2017-2020 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.onelitefeather.antiredstoneclockremastered.text; + +import java.util.function.Consumer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.Style; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Range; + +final class PaginationBuilder implements Pagination.Builder { + private int width = Pagination.WIDTH; + private int resultsPerPage = Pagination.RESULTS_PER_PAGE; + + private char lineCharacter = Pagination.LINE_CHARACTER; + private Style lineStyle = Pagination.LINE_STYLE; + + private Pagination.Renderer renderer = Pagination.DEFAULT_RENDERER; + + private char previousPageButtonCharacter = Pagination.PREVIOUS_PAGE_BUTTON_CHARACTER; + private Style previousPageButtonStyle = Pagination.PREVIOUS_PAGE_BUTTON_STYLE; + private char nextPageButtonCharacter = Pagination.NEXT_PAGE_BUTTON_CHARACTER; + private Style nextPageButtonStyle = Pagination.NEXT_PAGE_BUTTON_STYLE; + + @Override + public Pagination.@NotNull Builder width(final int width) { + this.width = width; + return this; + } + + @Override + public Pagination.@NotNull Builder resultsPerPage(final @Range(from = 0, to = Integer.MAX_VALUE) int resultsPerPage) { + this.resultsPerPage = resultsPerPage; + return this; + } + + @Override + public Pagination.@NotNull Builder renderer(final Pagination.@NotNull Renderer renderer) { + this.renderer = renderer; + return this; + } + + @Override + public Pagination.@NotNull Builder line(final @NotNull Consumer line) { + line.accept(new CharacterAndStyle() { + @Override + public @NotNull CharacterAndStyle character(final char character) { + PaginationBuilder.this.lineCharacter = character; + return this; + } + + @Override + public @NotNull CharacterAndStyle style(final @NotNull Style style) { + PaginationBuilder.this.lineStyle = style; + return this; + } + }); + return this; + } + + @Override + public Pagination.@NotNull Builder previousButton(final @NotNull Consumer previousButton) { + previousButton.accept(new CharacterAndStyle() { + @Override + public @NotNull CharacterAndStyle character(final char character) { + PaginationBuilder.this.previousPageButtonCharacter = character; + return this; + } + + @Override + public @NotNull CharacterAndStyle style(final @NotNull Style style) { + PaginationBuilder.this.previousPageButtonStyle = style; + return this; + } + }); + return this; + } + + @Override + public Pagination.@NotNull Builder nextButton(final @NotNull Consumer nextButton) { + nextButton.accept(new CharacterAndStyle() { + @Override + public @NotNull CharacterAndStyle character(final char character) { + PaginationBuilder.this.nextPageButtonCharacter = character; + return this; + } + + @Override + public @NotNull CharacterAndStyle style(final @NotNull Style style) { + PaginationBuilder.this.nextPageButtonStyle = style; + return this; + } + }); + return this; + } + + @Override + public @NotNull Pagination build(final @NotNull Component title, final Pagination.Renderer.@NotNull RowRenderer rowRenderer, final Pagination.@NotNull PageCommandFunction pageCommand) { + return new PaginationImpl<>( + this.width, + this.resultsPerPage, + this.renderer, + this.lineCharacter, + this.lineStyle, + this.previousPageButtonCharacter, + this.previousPageButtonStyle, + this.nextPageButtonCharacter, + this.nextPageButtonStyle, + title, + rowRenderer, + pageCommand + ); + } +} diff --git a/src/main/java/net/onelitefeather/antiredstoneclockremastered/text/PaginationImpl.java b/src/main/java/net/onelitefeather/antiredstoneclockremastered/text/PaginationImpl.java new file mode 100644 index 0000000..d7d53a0 --- /dev/null +++ b/src/main/java/net/onelitefeather/antiredstoneclockremastered/text/PaginationImpl.java @@ -0,0 +1,225 @@ +/* + * This file is part of adventure-text-feature-pagination, licensed under the MIT License. + * + * Copyright (c) 2017-2020 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.onelitefeather.antiredstoneclockremastered.text; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.Style; +import org.jetbrains.annotations.NotNull; + +final class PaginationImpl implements Pagination { + private static final int LINE_CHARACTER_LENGTH = 1; + + private final int width; + private final int resultsPerPage; + + private final Renderer renderer; + + private final char lineCharacter; + private final Style lineStyle; + + private final char previousPageButtonCharacter; + private final Style previousPageButtonStyle; + private final char nextPageButtonCharacter; + private final Style nextPageButtonStyle; + + private final Component title; + private final Renderer.RowRenderer rowRenderer; + private final PageCommandFunction pageCommand; + + PaginationImpl(final int width, final int resultsPerPage, final @NotNull Renderer renderer, final char lineCharacter, final @NotNull Style lineStyle, final char previousPageButtonCharacter, final @NotNull Style previousPageButtonStyle, final char nextPageButtonCharacter, final @NotNull Style nextPageButtonStyle, final @NotNull Component title, final Renderer.@NotNull RowRenderer rowRenderer, final @NotNull PageCommandFunction pageCommand) { + this.width = width; + this.resultsPerPage = resultsPerPage; + this.renderer = renderer; + this.lineCharacter = lineCharacter; + this.lineStyle = lineStyle; + this.previousPageButtonCharacter = previousPageButtonCharacter; + this.previousPageButtonStyle = previousPageButtonStyle; + this.nextPageButtonCharacter = nextPageButtonCharacter; + this.nextPageButtonStyle = nextPageButtonStyle; + this.title = title; + this.rowRenderer = rowRenderer; + this.pageCommand = pageCommand; + } + + @Override + public @NotNull List render(final @NotNull Collection content, final int page) { + if(content.isEmpty()) { + return Collections.singletonList(this.renderer.renderEmpty()); + } + + final int pages = pages(this.resultsPerPage, content.size()); + + if(!pageInRange(page, pages)) { + return Collections.singletonList(this.renderer.renderUnknownPage(page, pages)); + } + + final List components = new ArrayList<>(); + components.add(this.renderHeader(page, pages)); + Paginator.forEachPageEntry(content, this.resultsPerPage, page, (value, index) + -> components.addAll(this.rowRenderer.renderRow(value, index))); + components.add(this.renderFooter(page, pages)); + return Collections.unmodifiableList(components); + } + + private Component renderHeader(final int page, final int pages) { + final Component header = this.renderer.renderHeader(this.title, page, pages); + final Component dashes = this.line(header); + + return Component.text() + .append(dashes) + .append(header) + .append(dashes) + .build(); + } + + private Component renderFooter(final int page, final int pages) { + if(page == 1 && page == pages) { + return this.line(this.width); + } + + final Component buttons = this.renderFooterButtons(page, pages); + final Component dashes = this.line(buttons); + + return Component.text() + .append(dashes) + .append(buttons) + .append(dashes) + .build(); + } + + private Component renderFooterButtons(final int page, final int pages) { + final boolean hasPreviousPage = page > 1 && pages > 1; + final boolean hasNextPage = page < pages && page == 1 || hasPreviousPage && page != pages; + + final TextComponent.Builder buttons = Component.text(); + if(hasPreviousPage) { + buttons.append(this.renderer.renderPreviousPageButton(this.previousPageButtonCharacter, this.previousPageButtonStyle, ClickEvent.runCommand(this.pageCommand.pageCommand(page - 1)))); + + if(hasNextPage) { + buttons.append(this.line(8)); + } + } + + if(hasNextPage) { + buttons.append(this.renderer.renderNextPageButton(this.nextPageButtonCharacter, this.nextPageButtonStyle, ClickEvent.runCommand(this.pageCommand.pageCommand(page + 1)))); + } + + return buttons.build(); + } + + private @NotNull Component line(final @NotNull Component component) { + return this.line((this.width - length(component)) / (LINE_CHARACTER_LENGTH * 2)); + } + + private @NotNull Component line(final int characters) { + return Component.text(repeat(String.valueOf(this.lineCharacter), characters), this.lineStyle); + } + + static int length(final @NotNull Component component) { + int length = 0; + if(component instanceof TextComponent textComponent) { + length += textComponent.content().length(); + } + for(final Component child : component.children()) { + length += length(child); + } + return length; + } + + static @NotNull String repeat(final @NotNull String character, final int count) { + return String.join("", Collections.nCopies(count, character)); + } + + static int pages(final int pageSize, final int count) { + final int pages = count / pageSize + 1; + if(count % pageSize == 0) { + return pages - 1; + } + return pages; + } + + static boolean pageInRange(final int page, final int pages) { + return page > 0 && page <= pages; + } + + @Override + public String toString() { + return "PaginationImpl{" + + "width=" + this.width + + ", resultsPerPage=" + this.resultsPerPage + + ", renderer=" + this.renderer + + ", lineCharacter=" + this.lineCharacter + + ", lineStyle=" + this.lineStyle + + ", previousPageButtonCharacter=" + this.previousPageButtonCharacter + + ", previousPageButtonStyle=" + this.previousPageButtonStyle + + ", nextPageButtonCharacter=" + this.nextPageButtonCharacter + + ", nextPageButtonStyle=" + this.nextPageButtonStyle + + ", title=" + this.title + + ", rowRenderer=" + this.rowRenderer + + ", pageCommand=" + this.pageCommand + + '}'; + } + + @Override + public boolean equals(final Object other) { + if(this == other) return true; + if(other == null || this.getClass() != other.getClass()) return false; + final PaginationImpl that = (PaginationImpl) other; + if(this.width != that.width) return false; + if(this.resultsPerPage != that.resultsPerPage) return false; + if(this.lineCharacter != that.lineCharacter) return false; + if(this.previousPageButtonCharacter != that.previousPageButtonCharacter) return false; + if(this.nextPageButtonCharacter != that.nextPageButtonCharacter) return false; + if(!this.renderer.equals(that.renderer)) return false; + if(!this.lineStyle.equals(that.lineStyle)) return false; + if(!this.previousPageButtonStyle.equals(that.previousPageButtonStyle)) return false; + if(!this.nextPageButtonStyle.equals(that.nextPageButtonStyle)) return false; + if(!this.title.equals(that.title)) return false; + if(!this.rowRenderer.equals(that.rowRenderer)) return false; + return this.pageCommand.equals(that.pageCommand); + } + + @Override + public int hashCode() { + int result = this.width; + result = (31 * result) + this.resultsPerPage; + result = (31 * result) + this.renderer.hashCode(); + result = (31 * result) + this.lineCharacter; + result = (31 * result) + this.lineStyle.hashCode(); + result = (31 * result) + this.previousPageButtonCharacter; + result = (31 * result) + this.previousPageButtonStyle.hashCode(); + result = (31 * result) + this.nextPageButtonCharacter; + result = (31 * result) + this.nextPageButtonStyle.hashCode(); + result = (31 * result) + this.title.hashCode(); + result = (31 * result) + this.rowRenderer.hashCode(); + result = (31 * result) + this.pageCommand.hashCode(); + return result; + } +} diff --git a/src/main/java/net/onelitefeather/antiredstoneclockremastered/text/Paginator.java b/src/main/java/net/onelitefeather/antiredstoneclockremastered/text/Paginator.java new file mode 100644 index 0000000..26959f4 --- /dev/null +++ b/src/main/java/net/onelitefeather/antiredstoneclockremastered/text/Paginator.java @@ -0,0 +1,56 @@ +/* + * This file is part of adventure-text-feature-pagination, licensed under the MIT License. + * + * Copyright (c) 2017-2020 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.onelitefeather.antiredstoneclockremastered.text; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.RandomAccess; +import java.util.function.ObjIntConsumer; +import org.jetbrains.annotations.NotNull; + +interface Paginator { + static void forEachPageEntry(final @NotNull Collection content, final int pageSize, final int page, final @NotNull ObjIntConsumer consumer) { + final int size = content.size(); + final int start = pageSize * (page - 1); + final int end = pageSize * page; + if(content instanceof List && content instanceof RandomAccess) { + final List list = (List) content; + for(int i = start; i < end && i < size; i++) { + consumer.accept(list.get(i), i); + } + } else { + final Iterator it = content.iterator(); + + // skip entries on previous pages + for(int i = 0; i < start; i++) { + it.next(); + } + + for(int i = start; i < end && i < size; i++) { + consumer.accept(it.next(), i); + } + } + } +}