From acf88878145031c7641f6683ddc8f47cc191b958 Mon Sep 17 00:00:00 2001 From: Natan Date: Thu, 27 Nov 2025 13:11:47 -0300 Subject: [PATCH 1/7] feat: pagination orientation --- .../component/ComponentBuilder.java | 72 +++++++++---------- .../component/ItemComponent.java | 3 +- .../component/Pagination.java | 8 +++ .../component/PaginationStateBuilder.java | 24 +++++++ .../context/IFSlotContext.java | 16 ++--- .../component/PaginationImpl.java | 19 +++-- .../state/StateAccessImpl.java | 3 +- .../component/TestItemComponentBuilder.java | 4 +- 8 files changed, 96 insertions(+), 53 deletions(-) diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/ComponentBuilder.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/ComponentBuilder.java index 32954a5ca..f97c8029d 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/ComponentBuilder.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/ComponentBuilder.java @@ -165,48 +165,48 @@ public interface ComponentBuilder, C extends IF */ S hideIf(Predicate condition); - /** - * Identifies this component with a constant key. - *

- * Components with explicit keys are only re-rendered when their key changes. - * This can be used to prevent unnecessary re-renders during updates. - * - *

This API is experimental and is not subject to the general compatibility guarantees. - * It may be changed or removed completely in any further release. - * - * @param key The constant key to identify this component - * @return This component builder - */ + /** + * Identifies this component with a constant key. + *

+ * Components with explicit keys are only re-rendered when their key changes. + * This can be used to prevent unnecessary re-renders during updates. + * + *

This API is experimental and is not subject to the general compatibility guarantees. + * It may be changed or removed completely in any further release. + * + * @param key The constant key to identify this component + * @return This component builder + */ @ApiStatus.Experimental S identifiedBy(String key); - /** - * Identifies this component with a key provided by a {@link Supplier}. - *

- * Components with explicit keys are only re-rendered when their key changes. - * This can be used to prevent unnecessary re-renders during scheduled updates. - * - *

This API is experimental and is not subject to the general compatibility guarantees. - * It may be changed or removed completely in any further release. - * - * @param keyProvider A supplier that provides the key to identify this component. - * @return This component builder. - */ + /** + * Identifies this component with a key provided by a {@link Supplier}. + *

+ * Components with explicit keys are only re-rendered when their key changes. + * This can be used to prevent unnecessary re-renders during scheduled updates. + * + *

This API is experimental and is not subject to the general compatibility guarantees. + * It may be changed or removed completely in any further release. + * + * @param keyProvider A supplier that provides the key to identify this component. + * @return This component builder. + */ @ApiStatus.Experimental S identifiedBy(Supplier keyProvider); - /** - * Identifies this component with a key provided by a {@link Function} based on the context. - *

- * Components with explicit keys are only re-rendered when their key changes. - * This can be used to prevent unnecessary re-renders during scheduled updates. - * - *

This API is experimental and is not subject to the general compatibility guarantees. - * It may be changed or removed completely in any further release. - * - * @param keyProvider A function that provides the key to identify this component based on the context. - * @return This component builder. - */ + /** + * Identifies this component with a key provided by a {@link Function} based on the context. + *

+ * Components with explicit keys are only re-rendered when their key changes. + * This can be used to prevent unnecessary re-renders during scheduled updates. + * + *

This API is experimental and is not subject to the general compatibility guarantees. + * It may be changed or removed completely in any further release. + * + * @param keyProvider A function that provides the key to identify this component based on the context. + * @return This component builder. + */ @ApiStatus.Experimental S identifiedBy(Function keyProvider); } diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/ItemComponent.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/ItemComponent.java index d09772cac..daa203b04 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/ItemComponent.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/ItemComponent.java @@ -189,8 +189,7 @@ public void render(@NotNull IFSlotRenderContext context) { public void updated(@NotNull IFSlotRenderContext context) { if (context.isCancelled()) return; // Key-based skip optimization should always take precedence - if (keyFactory != null - && lastKey != null) { + if (keyFactory != null && lastKey != null) { String currentKey = keyFactory.apply(context); if (Objects.equals(lastKey, currentKey)) return; } diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java index 06c3439ef..9fe78ed64 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java @@ -235,4 +235,12 @@ static List splitSourceForPage(int index, int pageSize, int pagesCount, List< int toIndex = Math.min(fromIndex + pageSize, src.size()); return src.subList(fromIndex, toIndex); } + + Orientation getOrientation(); + + public enum Orientation { + VERTICAL, + + HORIZONTAL, + } } diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationStateBuilder.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationStateBuilder.java index 38cb6cbce..72dd417d0 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationStateBuilder.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationStateBuilder.java @@ -7,6 +7,7 @@ import me.devnatan.inventoryframework.internal.ElementFactory; import me.devnatan.inventoryframework.internal.LayoutSlot; import me.devnatan.inventoryframework.state.State; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; public final class PaginationStateBuilder< @@ -19,6 +20,7 @@ public final class PaginationStateBuilder< private PaginationElementFactory paginationElementFactory; private BiConsumer pageSwitchHandler; private final boolean async, computed; + private Pagination.Orientation orientation; public PaginationStateBuilder( Supplier internalElementFactoryProvider, @@ -31,6 +33,7 @@ public PaginationStateBuilder( this.sourceProvider = sourceProvider; this.async = async; this.computed = computed; + this.orientation = Pagination.Orientation.HORIZONTAL; } /** @@ -106,6 +109,23 @@ public PaginationStateBuilder onPageSwitch( return this; } + /** + * Defines the pagination iteration order. + * Default value is {@link Pagination.Orientation#HORIZONTAL}. + * + *

This API is experimental and is not subject to the general compatibility guarantees + * such API may be changed or may be removed completely in any further release. + * + * @param orientation The pagination orientation. + * @return This pagination builder. + * @see Pagination Orientation on Wiki + */ + @ApiStatus.Experimental + public PaginationStateBuilder orientation(Pagination.Orientation orientation) { + this.orientation = orientation; + return this; + } + /** * Builds a pagination state based on this builder values. * @@ -144,4 +164,8 @@ public BiConsumer getPageSwitchHandler() { public PaginationElementFactory getPaginationElementFactory() { return paginationElementFactory; } + + public Pagination.Orientation getOrientation() { + return orientation; + } } diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotContext.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotContext.java index 46182c6d7..84f8ff1ba 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotContext.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotContext.java @@ -62,14 +62,14 @@ public interface IFSlotContext extends IFContext { @NotNull ViewContainer getContainer(); - /** - * The component associated with this slot context. - * - *

This is an internal inventory-framework API that should not be used from outside of - * this library. No compatibility guarantees are provided. - * - * @return The component associated with this slot context. - */ + /** + * The component associated with this slot context. + * + *

This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + * + * @return The component associated with this slot context. + */ @ApiStatus.Internal @UnknownNullability Component getComponent(); diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java index 2237c51b4..d58b6d773 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java @@ -41,6 +41,7 @@ public class PaginationImpl extends AbstractStateValue implements Pagination, In private final Object sourceProvider; private final PaginationElementFactory elementFactory; private final BiConsumer pageSwitchHandler; + private final Pagination.Orientation orientation; // --- Internal --- private int currPageIndex; @@ -76,7 +77,8 @@ public PaginationImpl( PaginationElementFactory elementFactory, BiConsumer pageSwitchHandler, boolean isAsync, - boolean isComputed) { + boolean isComputed, + Pagination.Orientation orientation) { super(state); this.host = host; this.layoutTarget = layoutTarget; @@ -89,6 +91,7 @@ public PaginationImpl( this.isStatic = sourceProvider instanceof Collection; this.isLazy = !isStatic && !isComputed && (sourceProvider instanceof Function || sourceProvider instanceof Supplier); + this.orientation = orientation; } /** @@ -730,6 +733,11 @@ public List source() { return currSource; } + @Override + public Orientation getOrientation() { + return orientation; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -741,7 +749,8 @@ && getPageSize() == that.getPageSize() && isLazy() == that.isLazy() && pageWasChanged == that.pageWasChanged && Objects.equals(sourceProvider, that.sourceProvider) - && Objects.equals(pageSwitchHandler, that.pageSwitchHandler); + && Objects.equals(pageSwitchHandler, that.pageSwitchHandler) + && Objects.equals(orientation, that.orientation); } @Override @@ -753,7 +762,8 @@ public int hashCode() { currPageIndex, getPageSize(), isLazy(), - pageWasChanged); + pageWasChanged, + orientation); } @Override @@ -769,7 +779,8 @@ public String toString() { + isLazy + ", pageWasChanged=" + pageWasChanged + ", _srcFactory=" + _srcFactory + ", currSource=" - + currSource + "} " + + currSource + ", orientation=" + + orientation + "} " + super.toString(); } } diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/state/StateAccessImpl.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/state/StateAccessImpl.java index c1783fb87..66d74f12f 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/state/StateAccessImpl.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/state/StateAccessImpl.java @@ -237,7 +237,8 @@ protected final State createPaginationState( (PaginationElementFactory) builder.getPaginationElementFactory(), (BiConsumer) builder.getPageSwitchHandler(), builder.isAsync(), - builder.isComputed()); + builder.isComputed(), + builder.getOrientation()); final State state = new PaginationState(id, factory); this.stateRegistry.registerState(state, caller); diff --git a/inventory-framework-platform/src/test/java/me/devnatan/inventoryframework/component/TestItemComponentBuilder.java b/inventory-framework-platform/src/test/java/me/devnatan/inventoryframework/component/TestItemComponentBuilder.java index 200e5dc80..3b4cf9fac 100644 --- a/inventory-framework-platform/src/test/java/me/devnatan/inventoryframework/component/TestItemComponentBuilder.java +++ b/inventory-framework-platform/src/test/java/me/devnatan/inventoryframework/component/TestItemComponentBuilder.java @@ -19,7 +19,7 @@ public TestItemComponentBuilder() { } protected TestItemComponentBuilder( - Function keyFactory, + Function keyFactory, Ref referenceKey, Map data, boolean cancelOnClick, @@ -29,7 +29,7 @@ protected TestItemComponentBuilder( boolean isManagedExternally, Predicate displayCondition) { super( - keyFactory, + keyFactory, referenceKey, data, cancelOnClick, From b578581c406e68083272830867971006c7c8908e Mon Sep 17 00:00:00 2001 From: Natan Date: Thu, 27 Nov 2025 13:43:25 -0300 Subject: [PATCH 2/7] feat: vertical orientation support --- .../component/Pagination.java | 90 ++++++++++++++++++- .../component/PaginationStateBuilder.java | 25 ++++-- .../internal/LayoutSlot.java | 4 + .../component/PaginationImpl.java | 43 ++++++++- 4 files changed, 151 insertions(+), 11 deletions(-) diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java index 9fe78ed64..d13655db4 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java @@ -236,11 +236,99 @@ static List splitSourceForPage(int index, int pageSize, int pagesCount, List< return src.subList(fromIndex, toIndex); } + /** + * Returns the orientation used to determine how pagination slot positions + * are traversed when generating paginated components. + * + *

The orientation affects only the ordering of slot positions, + * not the page size. It controls whether items fill the pagination + * horizontally, vertically, or in one of the mixed cluster-based modes.

+ * + * This API is experimental and is not subject to the general compatibility guarantees + * such API may be changed or may be removed completely in any further release. + * + * @return the currently configured {@link Orientation} + */ + @ApiStatus.Experimental Orientation getOrientation(); - public enum Orientation { + /** + * Sets the orientation that defines how layout slots will be traversed + * when producing paginated components. + * + *

The orientation determines the fill direction of the layout grid: + *

    + *
  • {@link Orientation#HORIZONTAL} → row-major ordering
  • + *
  • {@link Orientation#VERTICAL} → column-major ordering
  • + *
  • {@link Orientation#MIXED_ROW_MAJOR} → horizontal traversal by clusters
  • + *
  • {@link Orientation#MIXED_COLUMN_MAJOR} → vertical traversal by clusters
  • + *
+ * + *

This setting does not affect the page size — only the ordering of + * component placement inside the layout.

+ * + * This API is experimental and is not subject to the general compatibility guarantees + * such API may be changed or may be removed completely in any further release. + * + * @param orientation the {@link Orientation} value to use + */ + @ApiStatus.Experimental + void setOrientation(Orientation orientation); + + enum Orientation { + + /** + * Column-major ordering. + * + *

Slots are traversed from top to bottom within each column, + * and columns are processed from left to right.

+ * + *

This is used for vertical progression: + *

+         * (r0,c0), (r1,c0), (r2,c0), ...
+         * (r0,c1), (r1,c1), (r2,c1), ...
+         * 
+ *

+ */ VERTICAL, + /** + * Row-major ordering. + * + *

Slots are traversed from left to right within each row, + * and rows are processed from top to bottom.

+ * + *

This is the traditional horizontal progression: + *

+         * (r0,c0), (r0,c1), (r0,c2), ...
+         * (r1,c0), (r1,c1), (r1,c2), ...
+         * 
+ *

+ */ HORIZONTAL, + + /** + * Mixed row-major ordering. + * + *

Slots are traversed horizontally (left to right, top to bottom), + * but processed sequence-by-sequence. A "sequence" is a contiguous group + * of valid slots ('O') in the layout.

+ * + *

This mode preserves logical grouping while still following a + * horizontal reading direction.

+ */ + MIXED_ROW_MAJOR, + + /** + * Mixed column-major ordering. + * + *

Slots are traversed vertically (top to bottom, left to right), + * but processed sequence-by-sequence. A "sequence" is a contiguous group + * of valid slots ('O') in the layout.

+ * + *

This mode preserves logical grouping while following a + * vertical reading direction.

+ */ + MIXED_COLUMN_MAJOR } } diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationStateBuilder.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationStateBuilder.java index 72dd417d0..1b398d538 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationStateBuilder.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationStateBuilder.java @@ -33,7 +33,7 @@ public PaginationStateBuilder( this.sourceProvider = sourceProvider; this.async = async; this.computed = computed; - this.orientation = Pagination.Orientation.HORIZONTAL; + this.orientation = Pagination.Orientation.VERTICAL; } /** @@ -110,15 +110,24 @@ public PaginationStateBuilder onPageSwitch( } /** - * Defines the pagination iteration order. - * Default value is {@link Pagination.Orientation#HORIZONTAL}. + * Defines the iteration order used by pagination. + * The default value is {@link Pagination.Orientation#HORIZONTAL}. * - *

This API is experimental and is not subject to the general compatibility guarantees - * such API may be changed or may be removed completely in any further release. + *

This controls how layout slot positions are traversed when generating + * paginated components. The orientation affects only the ordering of the + * slot iteration.

* - * @param orientation The pagination orientation. - * @return This pagination builder. - * @see Pagination Orientation on Wiki + *

This API is experimental and is not subject to the general + * compatibility guarantees. It may be changed or removed entirely + * in a future release.

+ * + * @param orientation the pagination orientation to apply. + * @return this pagination builder. + * + * @see Pagination.Orientation + * @see + * Pagination Orientation on Wiki + * */ @ApiStatus.Experimental public PaginationStateBuilder orientation(Pagination.Orientation orientation) { diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/internal/LayoutSlot.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/internal/LayoutSlot.java index 9b15c29d2..84298e16f 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/internal/LayoutSlot.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/internal/LayoutSlot.java @@ -33,6 +33,10 @@ public LayoutSlot withFactory(@Nullable IntFunction factory) { return new LayoutSlot(character, factory, positions); } + public LayoutSlot withPositions(int[] positions) { + return new LayoutSlot(character, factory, positions); + } + public int[] getPositions() { return positions; } diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java index d58b6d773..d625089c5 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java @@ -3,6 +3,7 @@ import static me.devnatan.inventoryframework.IFDebug.debug; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -41,7 +42,7 @@ public class PaginationImpl extends AbstractStateValue implements Pagination, In private final Object sourceProvider; private final PaginationElementFactory elementFactory; private final BiConsumer pageSwitchHandler; - private final Pagination.Orientation orientation; + private Pagination.Orientation orientation; // --- Internal --- private int currPageIndex; @@ -248,9 +249,17 @@ private void addComponentsForUnconstrainedPagination(IFRenderContext context, Li * @param pageContents Elements of the current page. */ private void addComponentsForLayeredPagination(IFRenderContext context, List pageContents) { - final LayoutSlot layoutSlot = getLayoutSlotForCurrentTarget(context); + LayoutSlot layoutSlot = getLayoutSlotForCurrentTarget(context); debug("[Pagination] Is layout slot defined by the user? %b", layoutSlot.isDefinedByTheUser()); + if (orientation == Pagination.Orientation.VERTICAL) { + int[] verticallyOrdered = + reorderLayoutPositionsVertically(layoutSlot.getPositions(), context.getContainer()); + layoutSlot = layoutSlot.withPositions(verticallyOrdered); + + debug("[Pagination] Reordered layout positions vertically: %s", Arrays.toString(verticallyOrdered)); + } + final int contentSize = pageContents.size(); debug("[Pagination] Elements count: %d elements", contentSize); debug("[Pagination] Iterating over '%c' layout target", layoutSlot.getCharacter()); @@ -305,6 +314,31 @@ private void addComponentsForLayeredPagination(IFRenderContext context, List } } + private int[] reorderLayoutPositionsVertically(int[] positions, ViewContainer container) { + int cols = container.getColumnsCount(); + int rows = container.getRowsCount(); + + // Transformar todos os slots em uma matriz [row][col] + List coords = new ArrayList<>(positions.length); + for (int pos : positions) { + int row = pos / cols; + int col = pos % cols; + coords.add(new int[] {row, col, pos}); + } + + // Agora ordenar por colunas primeiro, depois linhas + coords.sort(Comparator.comparingInt((int[] a) -> a[1]) // coluna primeiro + .thenComparingInt(a -> a[0])); // depois linha + + // Extrair apenas o slot original na ordem nova + int[] reordered = new int[positions.length]; + for (int i = 0; i < coords.size(); i++) { + reordered[i] = coords.get(i)[2]; // pega o slot + } + + return reordered; + } + /** * Updates the current page size. *

@@ -738,6 +772,11 @@ public Orientation getOrientation() { return orientation; } + @Override + public void setOrientation(Orientation orientation) { + this.orientation = orientation; + } + @Override public boolean equals(Object o) { if (this == o) return true; From 5d66280d99166c1b271c97bca96070ecfc5eeaaf Mon Sep 17 00:00:00 2001 From: Natan Date: Thu, 27 Nov 2025 13:44:11 -0300 Subject: [PATCH 3/7] docs: update --- .../inventoryframework/component/PaginationImpl.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java index d625089c5..24f618265 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java @@ -316,9 +316,7 @@ private void addComponentsForLayeredPagination(IFRenderContext context, List private int[] reorderLayoutPositionsVertically(int[] positions, ViewContainer container) { int cols = container.getColumnsCount(); - int rows = container.getRowsCount(); - // Transformar todos os slots em uma matriz [row][col] List coords = new ArrayList<>(positions.length); for (int pos : positions) { int row = pos / cols; @@ -326,14 +324,12 @@ private int[] reorderLayoutPositionsVertically(int[] positions, ViewContainer co coords.add(new int[] {row, col, pos}); } - // Agora ordenar por colunas primeiro, depois linhas - coords.sort(Comparator.comparingInt((int[] a) -> a[1]) // coluna primeiro - .thenComparingInt(a -> a[0])); // depois linha + coords.sort(Comparator.comparingInt((int[] a) -> a[1]) + .thenComparingInt(a -> a[0])); - // Extrair apenas o slot original na ordem nova int[] reordered = new int[positions.length]; for (int i = 0; i < coords.size(); i++) { - reordered[i] = coords.get(i)[2]; // pega o slot + reordered[i] = coords.get(i)[2]; } return reordered; From 055fa7c0b312e439103800eaad01bfa1ba0843a7 Mon Sep 17 00:00:00 2001 From: Natan Date: Thu, 27 Nov 2025 16:09:59 -0300 Subject: [PATCH 4/7] feat: alternating orientation --- .../component/Pagination.java | 38 ++++++--- .../component/PaginationStateBuilder.java | 2 +- .../component/PaginationImpl.java | 83 ++++++++++++++----- 3 files changed, 87 insertions(+), 36 deletions(-) diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java index d13655db4..ee37d94e0 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java @@ -308,27 +308,41 @@ enum Orientation { HORIZONTAL, /** - * Mixed row-major ordering. + * Mixed column-major ordering. * - *

Slots are traversed horizontally (left to right, top to bottom), + *

Slots are traversed vertically (top to bottom, left to right), * but processed sequence-by-sequence. A "sequence" is a contiguous group * of valid slots ('O') in the layout.

* - *

This mode preserves logical grouping while still following a - * horizontal reading direction.

+ *

This mode preserves logical grouping while following a + * vertical reading direction.

*/ - MIXED_ROW_MAJOR, + MIXED, /** - * Mixed column-major ordering. + * Iterates slot positions using an alternating row-major traversal. + * Elements are interleaved from both ends of the row-major ordered list: + *
+         * first, last, second, penultimate, ...
+         * 
* - *

Slots are traversed vertically (top to bottom, left to right), - * but processed sequence-by-sequence. A "sequence" is a contiguous group - * of valid slots ('O') in the layout.

+ *

Example for a 3×3 grid (row-major base order: 1–9): + *

+         * order = 1, 9, 2, 8, 3, 7, 4, 6, 5
+         * 
+ */ + ALTERNATING_ROWS, + + /** + * Iterates slot positions using an alternating column-major traversal. + * This variant applies the same interleaving strategy as {@link #ALTERNATING_ROWS} + * but operates on the column-major base order. * - *

This mode preserves logical grouping while following a - * vertical reading direction.

+ *

Example for a 3×3 grid (column-major base order: 1–6): + *

+         * order = 1, 9, 4, 6, 7, 3, 2, 8, 5
+         * 
*/ - MIXED_COLUMN_MAJOR + ALTERNATING_COLUMNS, } } diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationStateBuilder.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationStateBuilder.java index 1b398d538..a71b215c5 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationStateBuilder.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationStateBuilder.java @@ -33,7 +33,7 @@ public PaginationStateBuilder( this.sourceProvider = sourceProvider; this.async = async; this.computed = computed; - this.orientation = Pagination.Orientation.VERTICAL; + this.orientation = Pagination.Orientation.HORIZONTAL; } /** diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java index 24f618265..10b867f68 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java @@ -3,7 +3,6 @@ import static me.devnatan.inventoryframework.IFDebug.debug; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -252,20 +251,14 @@ private void addComponentsForLayeredPagination(IFRenderContext context, List LayoutSlot layoutSlot = getLayoutSlotForCurrentTarget(context); debug("[Pagination] Is layout slot defined by the user? %b", layoutSlot.isDefinedByTheUser()); - if (orientation == Pagination.Orientation.VERTICAL) { - int[] verticallyOrdered = - reorderLayoutPositionsVertically(layoutSlot.getPositions(), context.getContainer()); - layoutSlot = layoutSlot.withPositions(verticallyOrdered); - - debug("[Pagination] Reordered layout positions vertically: %s", Arrays.toString(verticallyOrdered)); - } - final int contentSize = pageContents.size(); debug("[Pagination] Elements count: %d elements", contentSize); debug("[Pagination] Iterating over '%c' layout target", layoutSlot.getCharacter()); + final int[] orderedPositions = computeSlotOrder(context, layoutSlot.getPositions()); + int index = 0; - for (final int layoutPosition : layoutSlot.getPositions()) { + for (final int layoutPosition : orderedPositions) { if (index >= contentSize) { // A layout slot defined by the user mean the user probably want to add a placeholder // item to fill empty slots that couldn't be reached by the pagination. Happens when @@ -314,25 +307,69 @@ private void addComponentsForLayeredPagination(IFRenderContext context, List } } - private int[] reorderLayoutPositionsVertically(int[] positions, ViewContainer container) { - int cols = container.getColumnsCount(); + private int[] computeSlotOrder(IFRenderContext context, int[] positions) { + Orientation orientation = getOrientation(); + String[] layout = context.getConfig().getLayout(); + + final int rows = layout.length; + final int cols = layout[0].length(); + + List present = new ArrayList<>(); - List coords = new ArrayList<>(positions.length); - for (int pos : positions) { - int row = pos / cols; - int col = pos % cols; - coords.add(new int[] {row, col, pos}); + for (int p : positions) present.add(p); + + List rowMajor = new ArrayList<>(positions.length); + for (int r = 0; r < rows; r++) { + for (int c = 0; c < cols; c++) { + int slot = r * cols + c; + if (present.contains(slot)) rowMajor.add(slot); + } } - coords.sort(Comparator.comparingInt((int[] a) -> a[1]) - .thenComparingInt(a -> a[0])); + List colMajor = new ArrayList<>(positions.length); + for (int c = 0; c < cols; c++) { + for (int r = 0; r < rows; r++) { + int slot = r * cols + c; + if (present.contains(slot)) colMajor.add(slot); + } + } - int[] reordered = new int[positions.length]; - for (int i = 0; i < coords.size(); i++) { - reordered[i] = coords.get(i)[2]; + if (orientation == Orientation.HORIZONTAL) { + return listToIntArray(rowMajor); } - return reordered; + if (orientation == Orientation.VERTICAL) { + return listToIntArray(colMajor); + } + + if (orientation == Orientation.ALTERNATING_ROWS) { + return interleaveList(rowMajor); + } + + if (orientation == Orientation.ALTERNATING_COLUMNS) { + return interleaveList(colMajor); + } + + return positions; + } + + /** Convert List to int[] */ + private int[] listToIntArray(List list) { + int[] out = new int[list.size()]; + for (int i = 0; i < list.size(); i++) out[i] = list.get(i); + return out; + } + + /** Interleave list as: first, last, second, penultimate, ... */ + private int[] interleaveList(List list) { + int n = list.size(); + int[] out = new int[n]; + int l = 0, r = n - 1, idx = 0; + while (l <= r) { + out[idx++] = list.get(l++); + if (l <= r) out[idx++] = list.get(r--); + } + return out; } /** From 2626fb33147f19fe5e540958ea70cacfb1762cb4 Mon Sep 17 00:00:00 2001 From: Natan Date: Thu, 27 Nov 2025 17:10:12 -0300 Subject: [PATCH 5/7] feat: top bottom left right pagination --- .../component/Pagination.java | 32 +++---- .../component/PaginationImpl.java | 94 +++++++++++++++++-- 2 files changed, 96 insertions(+), 30 deletions(-) diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java index ee37d94e0..a3b97ac41 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java @@ -256,14 +256,6 @@ static List splitSourceForPage(int index, int pageSize, int pagesCount, List< * Sets the orientation that defines how layout slots will be traversed * when producing paginated components. * - *

The orientation determines the fill direction of the layout grid: - *

    - *
  • {@link Orientation#HORIZONTAL} → row-major ordering
  • - *
  • {@link Orientation#VERTICAL} → column-major ordering
  • - *
  • {@link Orientation#MIXED_ROW_MAJOR} → horizontal traversal by clusters
  • - *
  • {@link Orientation#MIXED_COLUMN_MAJOR} → vertical traversal by clusters
  • - *
- * *

This setting does not affect the page size — only the ordering of * component placement inside the layout.

* @@ -307,18 +299,6 @@ enum Orientation { */ HORIZONTAL, - /** - * Mixed column-major ordering. - * - *

Slots are traversed vertically (top to bottom, left to right), - * but processed sequence-by-sequence. A "sequence" is a contiguous group - * of valid slots ('O') in the layout.

- * - *

This mode preserves logical grouping while following a - * vertical reading direction.

- */ - MIXED, - /** * Iterates slot positions using an alternating row-major traversal. * Elements are interleaved from both ends of the row-major ordered list: @@ -344,5 +324,17 @@ enum Orientation { * */ ALTERNATING_COLUMNS, + + /** + * Mixed column-major ordering. + * + *

Slots are traversed vertically (top to bottom, left to right), + * but processed sequence-by-sequence. A "sequence" is a contiguous group + * of valid slots in the layout.

+ * + *

This mode preserves logical grouping while following a + * vertical reading direction.

+ */ + TOP_BOTTOM_LEFT_RIGHT; } } diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java index 10b867f68..a16bc5dfa 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java @@ -313,16 +313,18 @@ private int[] computeSlotOrder(IFRenderContext context, int[] positions) { final int rows = layout.length; final int cols = layout[0].length(); + final int totalSlots = rows * cols; - List present = new ArrayList<>(); - - for (int p : positions) present.add(p); + boolean[] present = new boolean[totalSlots]; + for (int p : positions) { + if (p >= 0 && p < totalSlots) present[p] = true; + } List rowMajor = new ArrayList<>(positions.length); for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) { int slot = r * cols + c; - if (present.contains(slot)) rowMajor.add(slot); + if (present[slot]) rowMajor.add(slot); } } @@ -330,37 +332,109 @@ private int[] computeSlotOrder(IFRenderContext context, int[] positions) { for (int c = 0; c < cols; c++) { for (int r = 0; r < rows; r++) { int slot = r * cols + c; - if (present.contains(slot)) colMajor.add(slot); + if (present[slot]) colMajor.add(slot); } } if (orientation == Orientation.HORIZONTAL) { return listToIntArray(rowMajor); } - if (orientation == Orientation.VERTICAL) { return listToIntArray(colMajor); } - if (orientation == Orientation.ALTERNATING_ROWS) { return interleaveList(rowMajor); } - if (orientation == Orientation.ALTERNATING_COLUMNS) { return interleaveList(colMajor); } + if (orientation == Orientation.TOP_BOTTOM_LEFT_RIGHT) { + boolean[] added = new boolean[totalSlots]; + List out = new ArrayList<>(positions.length); + + for (int c = 0; c < cols; c++) { + if (c == 0 || c == 2 || c == 4 || c == 6 || c == 8) { + // top -> bottom + for (int r = 0; r < rows; r++) { + int slot = r * cols + c; + if (slot >= 0 && slot < totalSlots && present[slot] && !added[slot]) { + out.add(slot); + added[slot] = true; + } + } + } else { + // bottom -> top + for (int r = rows - 1; r >= 0; r--) { + int slot = r * cols + c; + if (slot >= 0 && slot < totalSlots && present[slot] && !added[slot]) { + out.add(slot); + added[slot] = true; + } + } + } + } + + if (out.size() < positions.length) { + for (int rmSlot : rowMajor) { + if (!added[rmSlot]) { + out.add(rmSlot); + added[rmSlot] = true; + } + } + } + + return listToIntArray(out); + } + + if (orientation == Orientation.STEPPED) { + boolean[] added = new boolean[totalSlots]; + List out = new ArrayList<>(positions.length); + + for (int c = 0; c < cols; c++) { + if (c % 2 != 0) { + // top -> bottom + for (int r = 0; r < rows; r++) { + int slot = r * cols + c; + if (slot >= 0 && slot < totalSlots && present[slot] && !added[slot]) { + out.add(slot); + added[slot] = true; + } + } + } else { + // bottom -> top + for (int r = rows - 1; r >= 0; r--) { + int slot = r * cols + c; + if (slot >= 0 && slot < totalSlots && present[slot] && !added[slot]) { + out.add(slot); + added[slot] = true; + } + } + } + } + + if (out.size() < positions.length) { + for (int rmSlot : rowMajor) { + if (!added[rmSlot]) { + out.add(rmSlot); + added[rmSlot] = true; + } + } + } + + return listToIntArray(out); + } + + // fallback return positions; } - /** Convert List to int[] */ private int[] listToIntArray(List list) { int[] out = new int[list.size()]; for (int i = 0; i < list.size(); i++) out[i] = list.get(i); return out; } - /** Interleave list as: first, last, second, penultimate, ... */ private int[] interleaveList(List list) { int n = list.size(); int[] out = new int[n]; From 82e099b8e24e82576c097c52d2c6bdc142cbf84d Mon Sep 17 00:00:00 2001 From: Natan Date: Thu, 27 Nov 2025 17:10:29 -0300 Subject: [PATCH 6/7] feat: pagination orientation sample --- .../runtime/ExampleUtil.java | 6 +- .../runtime/SamplePlugin.java | 7 ++- .../commands/IFExampleCommandExecutor.java | 17 +++-- .../runtime/view/PaginationOrientation.java | 63 +++++++++++++++++++ 4 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/view/PaginationOrientation.java diff --git a/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/ExampleUtil.java b/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/ExampleUtil.java index 19832f5ad..1f2e51af2 100644 --- a/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/ExampleUtil.java +++ b/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/ExampleUtil.java @@ -24,7 +24,11 @@ public static List getRandomItems(int amount) { } public static ItemStack displayItem(Material material, String displayName) { - ItemStack item = new ItemStack(material); + return displayItem(material, displayName, 1); + } + + public static ItemStack displayItem(Material material, String displayName, int amount) { + ItemStack item = new ItemStack(material, amount); ItemMeta itemMeta = item.getItemMeta(); itemMeta.setDisplayName(displayName); item.setItemMeta(itemMeta); diff --git a/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/SamplePlugin.java b/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/SamplePlugin.java index 57c94c90b..894537adf 100644 --- a/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/SamplePlugin.java +++ b/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/SamplePlugin.java @@ -17,7 +17,12 @@ public class SamplePlugin extends JavaPlugin { public void onEnable() { ViewFrame viewFrame = ViewFrame.create(this) .install(AnvilInputFeature.AnvilInput) - .with(new AnvilInputSample(), new Failing(), new SimplePagination(), new AutoUpdate()) + .with( + new AnvilInputSample(), + new Failing(), + new SimplePagination(), + new AutoUpdate(), + new PaginationOrientation()) .register(); IFExampleCommandExecutor command = new IFExampleCommandExecutor(viewFrame); diff --git a/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/commands/IFExampleCommandExecutor.java b/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/commands/IFExampleCommandExecutor.java index aa9fd9efd..79599b6cb 100644 --- a/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/commands/IFExampleCommandExecutor.java +++ b/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/commands/IFExampleCommandExecutor.java @@ -1,6 +1,7 @@ package me.devnatan.inventoryframework.runtime.commands; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import me.devnatan.inventoryframework.View; @@ -8,6 +9,7 @@ import me.devnatan.inventoryframework.runtime.view.AnvilInputSample; import me.devnatan.inventoryframework.runtime.view.AutoUpdate; import me.devnatan.inventoryframework.runtime.view.Failing; +import me.devnatan.inventoryframework.runtime.view.PaginationOrientation; import me.devnatan.inventoryframework.runtime.view.SimplePagination; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -19,11 +21,16 @@ public class IFExampleCommandExecutor implements CommandExecutor, TabCompleter { - private static final Map> views = Map.of( - "anvil", AnvilInputSample.class, - "failing", Failing.class, - "simple-pagination", SimplePagination.class, - "auto-update", AutoUpdate.class); + private static final Map> views; + + static { + views = new HashMap<>(); + views.put("anvil", AnvilInputSample.class); + views.put("failing", Failing.class); + views.put("simple-pagination", SimplePagination.class); + views.put("auto-update", AutoUpdate.class); + views.put("pagination", PaginationOrientation.class); + } private final ViewFrame viewFrame; diff --git a/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/view/PaginationOrientation.java b/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/view/PaginationOrientation.java new file mode 100644 index 000000000..d706dd3cc --- /dev/null +++ b/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/view/PaginationOrientation.java @@ -0,0 +1,63 @@ +package me.devnatan.inventoryframework.runtime.view; + +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import me.devnatan.inventoryframework.View; +import me.devnatan.inventoryframework.ViewConfigBuilder; +import me.devnatan.inventoryframework.component.Pagination; +import me.devnatan.inventoryframework.context.RenderContext; +import me.devnatan.inventoryframework.runtime.ExampleUtil; +import me.devnatan.inventoryframework.state.State; +import org.bukkit.Material; +import org.jetbrains.annotations.NotNull; + +public class PaginationOrientation extends View { + + private final State paginationState = lazyPaginationState( + () -> IntStream.range(0, 50).boxed().collect(Collectors.toList()), (context, builder, index, value) -> { + builder.withItem(ExampleUtil.displayItem(Material.ARROW, "Item " + value, value + 1)); + builder.onClick((ctx) -> { + ctx.getPlayer().sendMessage("You clicked on item " + index); + }); + }); + + @Override + public void onInit(@NotNull ViewConfigBuilder config) { + config.cancelOnClick(); + config.size(6); + config.title("Pagination (HORIZONTAL)"); + config.layout(" ", " ", "OOOOOOOOO", "OOOOOOOOO", "OOOOOOOOO", "OOOOOOOOO"); + } + + @Override + public void onFirstRender(@NotNull RenderContext render) { + render.firstSlot(ExampleUtil.displayItem(Material.DIAMOND, "Change orientation")) + .onClick(click -> { + final Pagination pagination = paginationState.get(click); + pagination.setOrientation(pagination.getOrientation()); + + switch (pagination.getOrientation()) { + case VERTICAL: + pagination.setOrientation(Pagination.Orientation.HORIZONTAL); + break; + case HORIZONTAL: + pagination.setOrientation(Pagination.Orientation.ALTERNATING_COLUMNS); + break; + case ALTERNATING_COLUMNS: + pagination.setOrientation(Pagination.Orientation.ALTERNATING_ROWS); + break; + case ALTERNATING_ROWS: + pagination.setOrientation(Pagination.Orientation.TOP_BOTTOM_LEFT_RIGHT); + break; + case TOP_BOTTOM_LEFT_RIGHT: + pagination.setOrientation(Pagination.Orientation.VERTICAL); + break; + } + + pagination.forceUpdate(); + click.updateTitleForPlayer( + "Pagination (" + pagination.getOrientation().name() + ")"); + click.getPlayer().sendMessage("Pagination orientation set to " + pagination.getOrientation()); + }); + } +} From 5d7ebd22c503d20f2d59c191dd41db63b4b0dde9 Mon Sep 17 00:00:00 2001 From: Natan Date: Thu, 27 Nov 2025 17:10:47 -0300 Subject: [PATCH 7/7] fix: remove STEPPED pagination orientation --- .../component/PaginationImpl.java | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java index a16bc5dfa..382dc7538 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/component/PaginationImpl.java @@ -387,45 +387,6 @@ private int[] computeSlotOrder(IFRenderContext context, int[] positions) { return listToIntArray(out); } - if (orientation == Orientation.STEPPED) { - boolean[] added = new boolean[totalSlots]; - List out = new ArrayList<>(positions.length); - - for (int c = 0; c < cols; c++) { - if (c % 2 != 0) { - // top -> bottom - for (int r = 0; r < rows; r++) { - int slot = r * cols + c; - if (slot >= 0 && slot < totalSlots && present[slot] && !added[slot]) { - out.add(slot); - added[slot] = true; - } - } - } else { - // bottom -> top - for (int r = rows - 1; r >= 0; r--) { - int slot = r * cols + c; - if (slot >= 0 && slot < totalSlots && present[slot] && !added[slot]) { - out.add(slot); - added[slot] = true; - } - } - } - } - - if (out.size() < positions.length) { - for (int rmSlot : rowMajor) { - if (!added[rmSlot]) { - out.add(rmSlot); - added[rmSlot] = true; - } - } - } - - return listToIntArray(out); - } - - // fallback return positions; }