Skip to content

Commit 200aeb2

Browse files
committed
Add pagination support to class selection UI
1 parent 4ff6c67 commit 200aeb2

7 files changed

Lines changed: 320 additions & 75 deletions

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ mod_credits = AzureDoom
2323
mod_url = https://modtale.net/mod/classescore
2424
mod_issues = https://github.com/AzureDoom/ClassesCore/issues
2525
mod_sources = https://github.com/AzureDoom/ClassesCore/
26-
version = 0.0.1-alpha.2
26+
version = 0.0.1-beta.1
2727
includes_pack = true
2828
disabled_by_default = false
2929
patchline = release

src/main/java/com/azuredoom/classescore/ui/ClassSelectionPage.java

Lines changed: 166 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import au.ellie.hyui.builders.*;
44
import au.ellie.hyui.events.UIContext;
5+
import au.ellie.hyui.types.ScrollbarStyle;
56
import com.hypixel.hytale.component.Ref;
67
import com.hypixel.hytale.component.Store;
78
import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime;
@@ -10,6 +11,7 @@
1011
import com.hypixel.hytale.server.core.universe.PlayerRef;
1112
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
1213

14+
import java.util.List;
1315
import java.util.Optional;
1416
import javax.annotation.Nonnull;
1517
import javax.annotation.Nullable;
@@ -30,6 +32,8 @@ public final class ClassSelectionPage {
3032
@Nullable
3133
private String statusMessage;
3234

35+
private int currentPage = 0;
36+
3337
public ClassSelectionPage(@Nonnull PlayerRef playerRef) {
3438
this.playerRef = playerRef;
3539
}
@@ -69,13 +73,23 @@ private void openInternal(
6973
CustomUIEventBindingType.Activating,
7074
(ignored, ctx) -> ctx.getPage().ifPresent(HyUIPage::close)
7175
)
76+
.addEventListener(
77+
"prev-btn",
78+
CustomUIEventBindingType.Activating,
79+
(ignored, ctx) -> handlePreviousPage(ctx)
80+
)
81+
.addEventListener(
82+
"next-btn",
83+
CustomUIEventBindingType.Activating,
84+
(ignored, ctx) -> handleNextPage(ctx)
85+
)
7286
.addEventListener(
7387
"confirm-btn",
7488
CustomUIEventBindingType.Activating,
7589
(ignored, ctx) -> handleConfirm(ref, store, ctx)
7690
);
7791

78-
for (var i = 1; i <= UIUtil.MAX_ROWS; i++) {
92+
for (var i = 1; i <= UIUtil.ROWS_PER_PAGE; i++) {
7993
final var rowIndex = i;
8094
page.addEventListener(
8195
"preview-" + rowIndex,
@@ -194,23 +208,26 @@ private GroupBuilder buildClassPanel(PageState state) {
194208
.withOutlineSize(1.0f)
195209
.withPadding(new HyUIPadding().setLeft(0).setRight(0).setTop(12).setBottom(0));
196210

197-
panel.addChild(spacerH(8));
211+
panel.addChild(spacerH(4));
198212

199213
var list = GroupBuilder.group()
200214
.withId("class-list")
201215
.withLayoutMode("TopScrolling")
202216
.withAnchor(size(510, 480))
203217
.withKeepScrollPosition(true)
204-
.withPadding(new HyUIPadding().setRight(0).setBottom(10));
218+
.withPadding(new HyUIPadding().setRight(0))
219+
.withScrollbarStyle(ScrollbarStyle.defaultStyle());
205220

206-
for (var i = 1; i <= UIUtil.MAX_ROWS; i++) {
221+
for (var i = 1; i <= UIUtil.ROWS_PER_PAGE; i++) {
207222
list.addChild(buildRow(i, state));
208-
if (i < UIUtil.MAX_ROWS) {
223+
if (i < UIUtil.ROWS_PER_PAGE) {
209224
list.addChild(spacerH(8));
210225
}
211226
}
212227

213228
panel.addChild(list);
229+
panel.addChild(spacerH(-4));
230+
panel.addChild(buildPaginationBar(state));
214231
return panel;
215232
}
216233

@@ -254,9 +271,13 @@ private GroupBuilder buildRow(int index, PageState state) {
254271

255272
name = UIUtil.safe(def.displayName());
256273
description = UIUtil.safe(def.description());
257-
status = isCurrent ? "SELECTED" : (isPreview ? "PREVIEWING" : "");
274+
status = isCurrent
275+
? BaseLangMessages.UI_SELECTED.getAnsiMessage()
276+
: (isPreview ? BaseLangMessages.UI_PREVIEWING.getAnsiMessage() : "");
258277
disableButton = isPreview;
259-
buttonText = isPreview ? "Viewing" : "View";
278+
buttonText = isPreview
279+
? BaseLangMessages.UI_VIEWING.getAnsiMessage()
280+
: BaseLangMessages.UI_VIEW.getAnsiMessage();
260281
}
261282

262283
row.withVisible(visible);
@@ -544,29 +565,27 @@ private static HyUIStyle statusStyle() {
544565
}
545566

546567
/**
547-
* Handles the previewing of a class based on the specified row index. This method validates the provided row index,
548-
* determines if it corresponds to a valid class in the sorted list of available classes, and updates the user
549-
* interface to reflect the previewed class. If the index is invalid, an appropriate status message is displayed.
568+
* Handles the preview of a specific item based on its index within a paginated list. Determines the absolute index
569+
* of the item to preview, validates the index, and invokes the preview action if valid. If the index is invalid, an
570+
* error message is displayed, and the system state is updated accordingly.
550571
*
551-
* @param rowIndex The zero-based index of the class in the list to be previewed. Must be within the bounds of the
552-
* sorted list of available classes.
553-
* @param ctx The {@link UIContext} used to update the user interface. It provides mechanisms to apply changes
554-
* and update the visual state of the page.
572+
* @param rowIndex the 1-based index of the row on the current page that is being selected for preview
573+
* @param ctx the UI context used to manage and apply state or actions on the user interface
555574
*/
556575
private void handlePreviewByIndex(
557576
int rowIndex,
558577
@Nonnull UIContext ctx
559578
) {
560579
var classes = UIUtil.getSortedClasses();
561-
int index = rowIndex - 1;
580+
int absoluteIndex = (currentPage * UIUtil.ROWS_PER_PAGE) + (rowIndex - 1);
562581

563-
if (index < 0 || index >= classes.size()) {
582+
if (absoluteIndex < 0 || absoluteIndex >= classes.size()) {
564583
statusMessage = BaseLangMessages.UI_INVALID_CLASS_SELECTION.getAnsiMessage();
565584
applyState(ctx, getPageState());
566585
return;
567586
}
568587

569-
handlePreview(classes.get(index).id(), ctx);
588+
handlePreview(classes.get(absoluteIndex).id(), ctx);
570589
}
571590

572591
/**
@@ -675,28 +694,25 @@ private void handleConfirm(
675694
*/
676695
private void applyState(@Nonnull UIContext ctx, @Nonnull PageState state) {
677696
ctx.getById("current-class", LabelBuilder.class)
678-
.ifPresent(label -> {
679-
label.withText(
697+
.ifPresent(
698+
label -> label.withText(
680699
BaseLangMessages.UI_CURRENT_CLASS.param(
681700
"className",
682701
Optional.ofNullable(state.currentClassName()).orElse(BaseLangMessages.UI_NONE.getAnsiMessage())
683702
).getAnsiMessage()
684-
);
685-
});
703+
)
704+
);
686705

687706
ctx.getById("class-count", LabelBuilder.class)
688707
.ifPresent(
689708
label -> label.withText(
690-
BaseLangMessages.UI_AVAILABLE_CLASSES.param("count", state.classes().size()).getAnsiMessage()
709+
BaseLangMessages.UI_AVAILABLE_CLASSES.param("count", state.totalClassCount()).getAnsiMessage()
691710
)
692711
);
693712

694713
ctx.getById("preview-name", LabelBuilder.class)
695714
.ifPresent(label -> label.withText(state.previewName()));
696715

697-
ctx.getById("preview-badge", LabelBuilder.class)
698-
.ifPresent(label -> label.withText(state.badgeText()));
699-
700716
ctx.getById("preview-description", LabelBuilder.class)
701717
.ifPresent(label -> label.withText(state.previewDescription()));
702718

@@ -713,7 +729,16 @@ private void applyState(@Nonnull UIContext ctx, @Nonnull PageState state) {
713729
button.withDisabled(state.confirmDisabled());
714730
});
715731

716-
for (var i = 1; i <= UIUtil.MAX_ROWS; i++) {
732+
ctx.getById("page-label", LabelBuilder.class)
733+
.ifPresent(label -> label.withText("Page " + (state.currentPage() + 1) + " / " + state.totalPages()));
734+
735+
ctx.getById("prev-btn", ButtonBuilder.class)
736+
.ifPresent(button -> button.withDisabled(!state.hasPreviousPage()));
737+
738+
ctx.getById("next-btn", ButtonBuilder.class)
739+
.ifPresent(button -> button.withDisabled(!state.hasNextPage()));
740+
741+
for (var i = 1; i <= UIUtil.ROWS_PER_PAGE; i++) {
717742
var classIndex = i - 1;
718743
var visible = classIndex < state.classes().size();
719744

@@ -771,17 +796,120 @@ private void applyState(@Nonnull UIContext ctx, @Nonnull PageState state) {
771796
}
772797

773798
/**
774-
* Computes and returns the current state of the class selection page, encapsulating the available classes, the
775-
* currently selected class, the class being previewed, and the corresponding status and user feedback information.
799+
* Handles the action of navigating to the previous page in the UI context.
800+
* Decrements the current page index by 1, ensuring it does not go below 0,
801+
* resets the status message, and applies the updated state to the UI context.
802+
*
803+
* @param ctx the UI context in which the page navigation is performed; must not be null
804+
*/
805+
private void handlePreviousPage(@Nonnull UIContext ctx) {
806+
currentPage = Math.max(0, currentPage - 1);
807+
statusMessage = null;
808+
applyState(ctx, getPageState());
809+
}
810+
811+
/**
812+
* Handles navigation to the next page in a paginated context. Updates the current page index
813+
* to the next page, ensuring it does not exceed the total number of available pages. Resets
814+
* the status message and applies the updated state to the given UI context.
815+
*
816+
* @param ctx the UI context to which the updated state will be applied; cannot be null
817+
*/
818+
private void handleNextPage(@Nonnull UIContext ctx) {
819+
var totalPages = getTotalPages(UIUtil.getSortedClasses().size());
820+
currentPage = Math.min(totalPages - 1, currentPage + 1);
821+
statusMessage = null;
822+
applyState(ctx, getPageState());
823+
}
824+
825+
/**
826+
* Retrieves a subset of class definitions corresponding to the specified page.
827+
* The classes are paginated based on a fixed number of rows per page.
776828
*
777-
* @return a {@link PageState} object containing the following: - The list of available {@link ClassDefinition}
778-
* objects. - The ID and display name of the currently selected class. - The ID, display name, description,
779-
* and badge text of the previewed class. - A status message for the user interface. - A flag indicating
780-
* whether the confirm button is disabled.
829+
* @param allClasses The list of all available class definitions. Must not be null.
830+
* @param page The page index (0-based) for which class definitions are to be retrieved.
831+
* If the value is less than zero or beyond the total number of pages, an empty list is returned.
832+
* @return A list of class definitions for the given page. The list may be empty if the page index is invalid or out of range.
833+
*/
834+
private static List<ClassDefinition> getPageClasses(List<ClassDefinition> allClasses, int page) {
835+
var start = page * UIUtil.ROWS_PER_PAGE;
836+
var end = Math.min(start + UIUtil.ROWS_PER_PAGE, allClasses.size());
837+
838+
if (start >= allClasses.size() || start < 0) {
839+
return List.of();
840+
}
841+
842+
return allClasses.subList(start, end);
843+
}
844+
845+
/**
846+
* Calculates the total number of pages required to display a specified number of classes.
847+
* The calculation is based on a fixed number of rows per page.
848+
*
849+
* @param totalClasses The total number of classes to be displayed. Must be a non-negative integer.
850+
* @return The total number of pages required, with a minimum value of 1.
851+
*/
852+
private static int getTotalPages(int totalClasses) {
853+
return Math.max(1, (int) Math.ceil((double) totalClasses / UIUtil.ROWS_PER_PAGE));
854+
}
855+
856+
private GroupBuilder buildPaginationBar(PageState state) {
857+
var bar = GroupBuilder.group()
858+
.withId("pagination-bar")
859+
.withLayoutMode("Left")
860+
.withAnchor(size(510, 34));
861+
862+
bar.addChild(
863+
ButtonBuilder.secondaryTextButton()
864+
.withId("prev-btn")
865+
.withText("Previous")
866+
.withDisabled(!state.hasPreviousPage())
867+
.withAnchor(size(110, 34))
868+
);
869+
870+
bar.addChild(spacerW(12));
871+
872+
bar.addChild(
873+
LabelBuilder.label()
874+
.withId("page-label")
875+
.withText("Page " + (state.currentPage() + 1) + " / " + state.totalPages())
876+
.withStyle(new HyUIStyle().setFontSize(13).setRenderBold(true).setTextColor("#dbe7f5"))
877+
.withAnchor(size(140, 34))
878+
);
879+
880+
bar.addChild(spacerW(12));
881+
882+
bar.addChild(
883+
ButtonBuilder.secondaryTextButton()
884+
.withId("next-btn")
885+
.withText("Next")
886+
.withDisabled(!state.hasNextPage())
887+
.withAnchor(size(110, 34))
888+
);
889+
890+
return bar;
891+
}
892+
893+
/**
894+
* Retrieves the current state of the page, including information about available classes,
895+
* pagination, and the preview of the selected or highlighted class.
896+
*
897+
* @return an instance of {@code PageState} containing the current page details, class information,
898+
* and other necessary data for displaying the UI state.
781899
*/
782900
private PageState getPageState() {
783901
var playerId = playerRef.getUuid();
784-
var classes = UIUtil.getSortedClasses();
902+
var allClasses = UIUtil.getSortedClasses();
903+
var totalPages = getTotalPages(allClasses.size());
904+
905+
if (currentPage >= totalPages) {
906+
currentPage = totalPages - 1;
907+
}
908+
if (currentPage < 0) {
909+
currentPage = 0;
910+
}
911+
912+
var classes = getPageClasses(allClasses, currentPage);
785913
var currentClassId = ClassesCore.getClassServiceIfPresent()
786914
.flatMap(s -> s.getPlayerState(playerId))
787915
.map(PlayerClassState::classId)
@@ -840,6 +968,11 @@ private PageState getPageState() {
840968

841969
return new PageState(
842970
classes,
971+
currentPage,
972+
totalPages,
973+
allClasses.size(),
974+
currentPage > 0,
975+
currentPage < totalPages - 1,
843976
currentClassId,
844977
currentClassName,
845978
previewClassId,

0 commit comments

Comments
 (0)