Skip to content

Commit 8bbdd72

Browse files
authored
Add support for vertical pagination, alternating and top-bottom left-right orientation modes (#808)
1 parent ea4e7fb commit 8bbdd72

13 files changed

Lines changed: 398 additions & 62 deletions

File tree

examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/ExampleUtil.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ public static List<ItemStack> getRandomItems(int amount) {
2424
}
2525

2626
public static ItemStack displayItem(Material material, String displayName) {
27-
ItemStack item = new ItemStack(material);
27+
return displayItem(material, displayName, 1);
28+
}
29+
30+
public static ItemStack displayItem(Material material, String displayName, int amount) {
31+
ItemStack item = new ItemStack(material, amount);
2832
ItemMeta itemMeta = item.getItemMeta();
2933
itemMeta.setDisplayName(displayName);
3034
item.setItemMeta(itemMeta);

examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/SamplePlugin.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ public class SamplePlugin extends JavaPlugin {
1717
public void onEnable() {
1818
ViewFrame viewFrame = ViewFrame.create(this)
1919
.install(AnvilInputFeature.AnvilInput)
20-
.with(new AnvilInputSample(), new Failing(), new SimplePagination(), new AutoUpdate())
20+
.with(
21+
new AnvilInputSample(),
22+
new Failing(),
23+
new SimplePagination(),
24+
new AutoUpdate(),
25+
new PaginationOrientation())
2126
.register();
2227

2328
IFExampleCommandExecutor command = new IFExampleCommandExecutor(viewFrame);

examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/commands/IFExampleCommandExecutor.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package me.devnatan.inventoryframework.runtime.commands;
22

33
import java.util.ArrayList;
4+
import java.util.HashMap;
45
import java.util.List;
56
import java.util.Map;
67
import me.devnatan.inventoryframework.View;
78
import me.devnatan.inventoryframework.ViewFrame;
89
import me.devnatan.inventoryframework.runtime.view.AnvilInputSample;
910
import me.devnatan.inventoryframework.runtime.view.AutoUpdate;
1011
import me.devnatan.inventoryframework.runtime.view.Failing;
12+
import me.devnatan.inventoryframework.runtime.view.PaginationOrientation;
1113
import me.devnatan.inventoryframework.runtime.view.SimplePagination;
1214
import org.bukkit.command.Command;
1315
import org.bukkit.command.CommandExecutor;
@@ -19,11 +21,16 @@
1921

2022
public class IFExampleCommandExecutor implements CommandExecutor, TabCompleter {
2123

22-
private static final Map<String, Class<? extends View>> views = Map.of(
23-
"anvil", AnvilInputSample.class,
24-
"failing", Failing.class,
25-
"simple-pagination", SimplePagination.class,
26-
"auto-update", AutoUpdate.class);
24+
private static final Map<String, Class<? extends View>> views;
25+
26+
static {
27+
views = new HashMap<>();
28+
views.put("anvil", AnvilInputSample.class);
29+
views.put("failing", Failing.class);
30+
views.put("simple-pagination", SimplePagination.class);
31+
views.put("auto-update", AutoUpdate.class);
32+
views.put("pagination", PaginationOrientation.class);
33+
}
2734

2835
private final ViewFrame viewFrame;
2936

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package me.devnatan.inventoryframework.runtime.view;
2+
3+
import java.util.stream.Collectors;
4+
import java.util.stream.IntStream;
5+
import me.devnatan.inventoryframework.View;
6+
import me.devnatan.inventoryframework.ViewConfigBuilder;
7+
import me.devnatan.inventoryframework.component.Pagination;
8+
import me.devnatan.inventoryframework.context.RenderContext;
9+
import me.devnatan.inventoryframework.runtime.ExampleUtil;
10+
import me.devnatan.inventoryframework.state.State;
11+
import org.bukkit.Material;
12+
import org.jetbrains.annotations.NotNull;
13+
14+
public class PaginationOrientation extends View {
15+
16+
private final State<Pagination> paginationState = lazyPaginationState(
17+
() -> IntStream.range(0, 50).boxed().collect(Collectors.toList()), (context, builder, index, value) -> {
18+
builder.withItem(ExampleUtil.displayItem(Material.ARROW, "Item " + value, value + 1));
19+
builder.onClick((ctx) -> {
20+
ctx.getPlayer().sendMessage("You clicked on item " + index);
21+
});
22+
});
23+
24+
@Override
25+
public void onInit(@NotNull ViewConfigBuilder config) {
26+
config.cancelOnClick();
27+
config.size(6);
28+
config.title("Pagination (HORIZONTAL)");
29+
config.layout(" ", " ", "OOOOOOOOO", "OOOOOOOOO", "OOOOOOOOO", "OOOOOOOOO");
30+
}
31+
32+
@Override
33+
public void onFirstRender(@NotNull RenderContext render) {
34+
render.firstSlot(ExampleUtil.displayItem(Material.DIAMOND, "Change orientation"))
35+
.onClick(click -> {
36+
final Pagination pagination = paginationState.get(click);
37+
pagination.setOrientation(pagination.getOrientation());
38+
39+
switch (pagination.getOrientation()) {
40+
case VERTICAL:
41+
pagination.setOrientation(Pagination.Orientation.HORIZONTAL);
42+
break;
43+
case HORIZONTAL:
44+
pagination.setOrientation(Pagination.Orientation.ALTERNATING_COLUMNS);
45+
break;
46+
case ALTERNATING_COLUMNS:
47+
pagination.setOrientation(Pagination.Orientation.ALTERNATING_ROWS);
48+
break;
49+
case ALTERNATING_ROWS:
50+
pagination.setOrientation(Pagination.Orientation.TOP_BOTTOM_LEFT_RIGHT);
51+
break;
52+
case TOP_BOTTOM_LEFT_RIGHT:
53+
pagination.setOrientation(Pagination.Orientation.VERTICAL);
54+
break;
55+
}
56+
57+
pagination.forceUpdate();
58+
click.updateTitleForPlayer(
59+
"Pagination (" + pagination.getOrientation().name() + ")");
60+
click.getPlayer().sendMessage("Pagination orientation set to " + pagination.getOrientation());
61+
});
62+
}
63+
}

inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/ComponentBuilder.java

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -165,48 +165,48 @@ public interface ComponentBuilder<S extends ComponentBuilder<S, C>, C extends IF
165165
*/
166166
S hideIf(Predicate<C> condition);
167167

168-
/**
169-
* Identifies this component with a constant key.
170-
* <p>
171-
* Components with explicit keys are only re-rendered when their key changes.
172-
* This can be used to prevent unnecessary re-renders during updates.
173-
*
174-
* <p><b><i>This API is experimental and is not subject to the general compatibility guarantees.
175-
* It may be changed or removed completely in any further release.</i></b>
176-
*
177-
* @param key The constant key to identify this component
178-
* @return This component builder
179-
*/
168+
/**
169+
* Identifies this component with a constant key.
170+
* <p>
171+
* Components with explicit keys are only re-rendered when their key changes.
172+
* This can be used to prevent unnecessary re-renders during updates.
173+
*
174+
* <p><b><i>This API is experimental and is not subject to the general compatibility guarantees.
175+
* It may be changed or removed completely in any further release.</i></b>
176+
*
177+
* @param key The constant key to identify this component
178+
* @return This component builder
179+
*/
180180
@ApiStatus.Experimental
181181
S identifiedBy(String key);
182182

183-
/**
184-
* Identifies this component with a key provided by a {@link Supplier}.
185-
* <p>
186-
* Components with explicit keys are only re-rendered when their key changes.
187-
* This can be used to prevent unnecessary re-renders during scheduled updates.
188-
*
189-
* <p><b><i>This API is experimental and is not subject to the general compatibility guarantees.
190-
* It may be changed or removed completely in any further release.</i></b>
191-
*
192-
* @param keyProvider A supplier that provides the key to identify this component.
193-
* @return This component builder.
194-
*/
183+
/**
184+
* Identifies this component with a key provided by a {@link Supplier}.
185+
* <p>
186+
* Components with explicit keys are only re-rendered when their key changes.
187+
* This can be used to prevent unnecessary re-renders during scheduled updates.
188+
*
189+
* <p><b><i>This API is experimental and is not subject to the general compatibility guarantees.
190+
* It may be changed or removed completely in any further release.</i></b>
191+
*
192+
* @param keyProvider A supplier that provides the key to identify this component.
193+
* @return This component builder.
194+
*/
195195
@ApiStatus.Experimental
196196
S identifiedBy(Supplier<String> keyProvider);
197197

198-
/**
199-
* Identifies this component with a key provided by a {@link Function} based on the context.
200-
* <p>
201-
* Components with explicit keys are only re-rendered when their key changes.
202-
* This can be used to prevent unnecessary re-renders during scheduled updates.
203-
*
204-
* <p><b><i>This API is experimental and is not subject to the general compatibility guarantees.
205-
* It may be changed or removed completely in any further release.</i></b>
206-
*
207-
* @param keyProvider A function that provides the key to identify this component based on the context.
208-
* @return This component builder.
209-
*/
198+
/**
199+
* Identifies this component with a key provided by a {@link Function} based on the context.
200+
* <p>
201+
* Components with explicit keys are only re-rendered when their key changes.
202+
* This can be used to prevent unnecessary re-renders during scheduled updates.
203+
*
204+
* <p><b><i>This API is experimental and is not subject to the general compatibility guarantees.
205+
* It may be changed or removed completely in any further release.</i></b>
206+
*
207+
* @param keyProvider A function that provides the key to identify this component based on the context.
208+
* @return This component builder.
209+
*/
210210
@ApiStatus.Experimental
211211
S identifiedBy(Function<C, String> keyProvider);
212212
}

inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/ItemComponent.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,7 @@ public void render(@NotNull IFSlotRenderContext context) {
189189
public void updated(@NotNull IFSlotRenderContext context) {
190190
if (context.isCancelled()) return;
191191
// Key-based skip optimization should always take precedence
192-
if (keyFactory != null
193-
&& lastKey != null) {
192+
if (keyFactory != null && lastKey != null) {
194193
String currentKey = keyFactory.apply(context);
195194
if (Objects.equals(lastKey, currentKey)) return;
196195
}

inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/Pagination.java

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,106 @@ static List<?> splitSourceForPage(int index, int pageSize, int pagesCount, List<
235235
int toIndex = Math.min(fromIndex + pageSize, src.size());
236236
return src.subList(fromIndex, toIndex);
237237
}
238+
239+
/**
240+
* Returns the orientation used to determine how pagination slot positions
241+
* are traversed when generating paginated components.
242+
*
243+
* <p>The orientation affects only the ordering of slot positions,
244+
* not the page size. It controls whether items fill the pagination
245+
* horizontally, vertically, or in one of the mixed cluster-based modes.</p>
246+
*
247+
* <b><i> This API is experimental and is not subject to the general compatibility guarantees
248+
* such API may be changed or may be removed completely in any further release. </i></b>
249+
*
250+
* @return the currently configured {@link Orientation}
251+
*/
252+
@ApiStatus.Experimental
253+
Orientation getOrientation();
254+
255+
/**
256+
* Sets the orientation that defines how layout slots will be traversed
257+
* when producing paginated components.
258+
*
259+
* <p>This setting does not affect the page size — only the ordering of
260+
* component placement inside the layout.</p>
261+
*
262+
* <b><i> This API is experimental and is not subject to the general compatibility guarantees
263+
* such API may be changed or may be removed completely in any further release. </i></b>
264+
*
265+
* @param orientation the {@link Orientation} value to use
266+
*/
267+
@ApiStatus.Experimental
268+
void setOrientation(Orientation orientation);
269+
270+
enum Orientation {
271+
272+
/**
273+
* Column-major ordering.
274+
*
275+
* <p>Slots are traversed from top to bottom within each column,
276+
* and columns are processed from left to right.</p>
277+
*
278+
* <p>This is used for vertical progression:
279+
* <pre>
280+
* (r0,c0), (r1,c0), (r2,c0), ...
281+
* (r0,c1), (r1,c1), (r2,c1), ...
282+
* </pre>
283+
* </p>
284+
*/
285+
VERTICAL,
286+
287+
/**
288+
* Row-major ordering.
289+
*
290+
* <p>Slots are traversed from left to right within each row,
291+
* and rows are processed from top to bottom.</p>
292+
*
293+
* <p>This is the traditional horizontal progression:
294+
* <pre>
295+
* (r0,c0), (r0,c1), (r0,c2), ...
296+
* (r1,c0), (r1,c1), (r1,c2), ...
297+
* </pre>
298+
* </p>
299+
*/
300+
HORIZONTAL,
301+
302+
/**
303+
* Iterates slot positions using an alternating row-major traversal.
304+
* Elements are interleaved from both ends of the row-major ordered list:
305+
* <pre>
306+
* first, last, second, penultimate, ...
307+
* </pre>
308+
*
309+
* <p>Example for a 3×3 grid (row-major base order: 1–9):
310+
* <pre>
311+
* order = 1, 9, 2, 8, 3, 7, 4, 6, 5
312+
* </pre>
313+
*/
314+
ALTERNATING_ROWS,
315+
316+
/**
317+
* Iterates slot positions using an alternating column-major traversal.
318+
* This variant applies the same interleaving strategy as {@link #ALTERNATING_ROWS}
319+
* but operates on the column-major base order.
320+
*
321+
* <p>Example for a 3×3 grid (column-major base order: 1–6):
322+
* <pre>
323+
* order = 1, 9, 4, 6, 7, 3, 2, 8, 5
324+
* </pre>
325+
*/
326+
ALTERNATING_COLUMNS,
327+
328+
/**
329+
* Mixed column-major ordering.
330+
*
331+
* <p>Slots are traversed vertically (top to bottom, left to right),
332+
* but processed sequence-by-sequence. A "sequence" is a contiguous group
333+
* of valid slots in the layout.</p>
334+
*
335+
* <p>This mode preserves logical grouping while following a
336+
* vertical reading direction.</p>
337+
*/
338+
TOP_BOTTOM_LEFT_RIGHT;
339+
}
238340
}

inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationStateBuilder.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import me.devnatan.inventoryframework.internal.ElementFactory;
88
import me.devnatan.inventoryframework.internal.LayoutSlot;
99
import me.devnatan.inventoryframework.state.State;
10+
import org.jetbrains.annotations.ApiStatus;
1011
import org.jetbrains.annotations.NotNull;
1112

1213
public final class PaginationStateBuilder<
@@ -19,6 +20,7 @@ public final class PaginationStateBuilder<
1920
private PaginationElementFactory<V> paginationElementFactory;
2021
private BiConsumer<Context, Pagination> pageSwitchHandler;
2122
private final boolean async, computed;
23+
private Pagination.Orientation orientation;
2224

2325
public PaginationStateBuilder(
2426
Supplier<ElementFactory> internalElementFactoryProvider,
@@ -31,6 +33,7 @@ public PaginationStateBuilder(
3133
this.sourceProvider = sourceProvider;
3234
this.async = async;
3335
this.computed = computed;
36+
this.orientation = Pagination.Orientation.HORIZONTAL;
3437
}
3538

3639
/**
@@ -106,6 +109,32 @@ public PaginationStateBuilder<Context, Builder, V> onPageSwitch(
106109
return this;
107110
}
108111

112+
/**
113+
* Defines the iteration order used by pagination.
114+
* The default value is {@link Pagination.Orientation#HORIZONTAL}.
115+
*
116+
* <p>This controls how layout slot positions are traversed when generating
117+
* paginated components. The orientation affects only the ordering of the
118+
* slot iteration.</p>
119+
*
120+
* <p><b><i>This API is experimental and is not subject to the general
121+
* compatibility guarantees. It may be changed or removed entirely
122+
* in a future release.</i></b></p>
123+
*
124+
* @param orientation the pagination orientation to apply.
125+
* @return this pagination builder.
126+
*
127+
* @see Pagination.Orientation
128+
* @see <a href="https://github.com/DevNatan/inventory-framework/wiki/Pagination#pagination-orientation">
129+
* Pagination Orientation on Wiki
130+
* </a>
131+
*/
132+
@ApiStatus.Experimental
133+
public PaginationStateBuilder<Context, Builder, V> orientation(Pagination.Orientation orientation) {
134+
this.orientation = orientation;
135+
return this;
136+
}
137+
109138
/**
110139
* Builds a pagination state based on this builder values.
111140
*
@@ -144,4 +173,8 @@ public BiConsumer<Context, Pagination> getPageSwitchHandler() {
144173
public PaginationElementFactory<V> getPaginationElementFactory() {
145174
return paginationElementFactory;
146175
}
176+
177+
public Pagination.Orientation getOrientation() {
178+
return orientation;
179+
}
147180
}

0 commit comments

Comments
 (0)