22
33import au .ellie .hyui .builders .*;
44import au .ellie .hyui .events .UIContext ;
5+ import au .ellie .hyui .types .ScrollbarStyle ;
56import com .hypixel .hytale .component .Ref ;
67import com .hypixel .hytale .component .Store ;
78import com .hypixel .hytale .protocol .packets .interface_ .CustomPageLifetime ;
1011import com .hypixel .hytale .server .core .universe .PlayerRef ;
1112import com .hypixel .hytale .server .core .universe .world .storage .EntityStore ;
1213
14+ import java .util .List ;
1315import java .util .Optional ;
1416import javax .annotation .Nonnull ;
1517import 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