From 4712aaca8f953d0db652d75e84a7f8807a5beacd Mon Sep 17 00:00:00 2001 From: Natan Date: Thu, 31 Jul 2025 14:25:19 -0300 Subject: [PATCH 1/9] fix: layered pagination layout slot iteration --- .../inventoryframework/component/PaginationImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 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 20bb5cdea..e2f14a01b 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 @@ -251,7 +251,7 @@ private void addComponentsForLayeredPagination(IFRenderContext context, List int iterationIndex = 0; for (final int position : targetLayoutSlot.getPositions()) { - final Object value = pageContents.get(iterationIndex++); + final Object value = pageContents.get(iterationIndex); try { final ComponentFactory factory = elementFactory.create(this, iterationIndex, position, value); @@ -268,7 +268,7 @@ private void addComponentsForLayeredPagination(IFRenderContext context, List exception.printStackTrace(); } - if (iterationIndex == elementsLen) break; + if (iterationIndex++ == elementsLen) break; } } @@ -390,6 +390,7 @@ public int getPosition() { @Override public void render(@NotNull IFSlotRenderContext context) { + System.out.println("[debug] PaginationImpl: render(...)"); if (!initialized) { setVisible(true); final IFRenderContext root = context.getParent(); @@ -403,6 +404,7 @@ public void render(@NotNull IFSlotRenderContext context) { } private void renderChild(IFSlotRenderContext context) { + System.out.println("[debug] PaginationImpl: rendering child"); getInternalComponents().forEach(context::renderComponent); } From 25e00c2c7a1ed41beab8a5537e91a887687e3b23 Mon Sep 17 00:00:00 2001 From: Natan Date: Sat, 2 Aug 2025 13:38:13 -0300 Subject: [PATCH 2/9] refactor: simplify and reduce pagination source split --- .../component/Pagination.java | 17 ++++------------- 1 file changed, 4 insertions(+), 13 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 8b69e64bf..b56a060d5 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 @@ -1,8 +1,6 @@ package me.devnatan.inventoryframework.component; -import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedList; import java.util.List; import me.devnatan.inventoryframework.state.StateValue; import me.devnatan.inventoryframework.state.StateValueHost; @@ -229,19 +227,12 @@ public interface Pagination extends ComponentComposition, StateValue { */ static List splitSourceForPage(int index, int pageSize, int pagesCount, List src) { if (src.isEmpty()) return Collections.emptyList(); - - if (src.size() <= pageSize) return new ArrayList<>(src); - if (index < 0 || (pagesCount > 0 && index > pagesCount)) + if (index < 0 || (pagesCount > 0 && index >= pagesCount)) throw new IndexOutOfBoundsException(String.format( "Page index must be between the range of 0 and %d. Given: %d", pagesCount - 1, index)); - final List contents = new LinkedList<>(); - final int base = index * pageSize; - int until = base + pageSize; - if (until > src.size()) until = src.size(); - - for (int i = base; i < until; i++) contents.add(src.get(i)); - - return contents; + int fromIndex = index * pageSize; + int toIndex = Math.min(fromIndex + pageSize, src.size()); + return src.subList(fromIndex, toIndex); } } From ea7f8d10f4de83be86459a2e2e038730cdc12dc5 Mon Sep 17 00:00:00 2001 From: Natan Date: Sat, 2 Aug 2025 13:38:38 -0300 Subject: [PATCH 3/9] refactor: improve AvailableSlotInterceptor readability --- .../pipeline/AvailableSlotInterceptor.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/AvailableSlotInterceptor.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/AvailableSlotInterceptor.java index 55df822c2..682604eea 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/AvailableSlotInterceptor.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/AvailableSlotInterceptor.java @@ -22,9 +22,12 @@ public void intercept(PipelineContext pipeline, VirtualView subject final IFRenderContext context = (IFRenderContext) subject; if (context.getAvailableSlotFactories() == null) return; - final List slotComponents = context.getConfig().getLayout() == null - ? resolveFromInitialSlot(context) - : resolveFromLayoutSlot(context); + final List slotComponents; + if (context.getConfig().getLayout() == null) { + slotComponents = resolveFromInitialSlot(context); + } else { + slotComponents = resolveFromLayoutSlot(context); + } slotComponents.forEach(componentFactory -> context.addComponent(componentFactory.create())); } From b794e58e452f91a2d55b688b9d03fcf9b214089b Mon Sep 17 00:00:00 2001 From: Natan Date: Sat, 2 Aug 2025 13:38:57 -0300 Subject: [PATCH 4/9] fix: do not render layoutSlot in the same char if pagination is available --- .../pipeline/LayoutRenderInterceptor.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/LayoutRenderInterceptor.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/LayoutRenderInterceptor.java index eadb12ff4..05a7b9700 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/LayoutRenderInterceptor.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/LayoutRenderInterceptor.java @@ -1,11 +1,15 @@ package me.devnatan.inventoryframework.pipeline; +import java.util.AbstractMap; +import java.util.Map; import java.util.function.IntFunction; +import java.util.stream.Collectors; import me.devnatan.inventoryframework.InventoryFrameworkException; import me.devnatan.inventoryframework.VirtualView; import me.devnatan.inventoryframework.component.Component; import me.devnatan.inventoryframework.component.ComponentFactory; import me.devnatan.inventoryframework.component.ItemComponentBuilder; +import me.devnatan.inventoryframework.component.Pagination; import me.devnatan.inventoryframework.context.IFRenderContext; import me.devnatan.inventoryframework.internal.LayoutSlot; @@ -16,6 +20,14 @@ public void intercept(PipelineContext pipeline, VirtualView subject if (!(subject instanceof IFRenderContext)) return; final IFRenderContext renderContext = (IFRenderContext) subject; + final Map paginationComponents = renderContext.getComponents().stream() + .filter(component -> component instanceof Pagination) + .map(component -> { + final Pagination pagination = (Pagination) component; + return new AbstractMap.SimpleImmutableEntry<>(pagination.getLayoutTarget(), pagination); + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + for (final LayoutSlot layoutSlot : renderContext.getLayoutSlots()) { final IntFunction factory = layoutSlot.getFactory(); if (factory == null) { @@ -25,6 +37,13 @@ public void intercept(PipelineContext pipeline, VirtualView subject continue; } + // Pagination component are rendered AFTER `availableSlot`/`layoutSlot`. + // Once a layout slot that uses the same layout target as the pagination component one + // is detected, pagination component takes care of them and uses them as fallback. + if (paginationComponents.containsKey(layoutSlot.getCharacter())) { + continue; + } + int iterationIndex = 0; for (final int slot : layoutSlot.getPositions()) { final ComponentFactory componentFactory = factory.apply(iterationIndex++); From 17b0ae9885964b59ba3d4fbb69bdfc01e9a83f8f Mon Sep 17 00:00:00 2001 From: Natan Date: Sat, 2 Aug 2025 13:39:26 -0300 Subject: [PATCH 5/9] refactor: improve `renderComponent` readability --- .../context/AbstractIFContext.java | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java index 1d4117485..c1230925a 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java @@ -121,21 +121,28 @@ private IFSlotRenderContext createSlotRenderContext(@NotNull Component component @Override public void renderComponent(@NotNull Component component) { - if (!component.shouldRender(this)) { - component.setVisible(false); + if (component.shouldRender(this)) { + IFDebug.debug("Rendering component...: %s", component); + component.render(createSlotRenderContext(component, false)); + return; + } - final Optional overlapOptional = getOverlappingComponentToRender(this, component); - if (overlapOptional.isPresent()) { - Component overlap = overlapOptional.get(); - renderComponent(overlap); + component.setVisible(false); - if (overlap.isVisible()) return; - } + final Optional overlapOptional = getOverlappingComponentToRender(this, component); + if (overlapOptional.isPresent()) { + Component overlap = overlapOptional.get(); + renderComponent(overlap); - component.clear(this); - return; + if (overlap.isVisible()) { + IFDebug.debug( + "Component was not rendered due to overlapping component (component = %s, overlap = %s)", + component, overlap); + return; + } } - component.render(createSlotRenderContext(component, false)); + + component.clear(this); } private Optional getOverlappingComponentToRender(ComponentContainer container, Component subject) { From 1af1d80313a243618f87b19f646ef9355df6b56c Mon Sep 17 00:00:00 2001 From: Natan Date: Sat, 2 Aug 2025 13:39:39 -0300 Subject: [PATCH 6/9] fix: remove "reserved layout character" restriction --- .../context/PlatformRenderContext.java | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/PlatformRenderContext.java b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/PlatformRenderContext.java index 3a26742ad..9730bca01 100644 --- a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/PlatformRenderContext.java +++ b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/PlatformRenderContext.java @@ -1,6 +1,5 @@ package me.devnatan.inventoryframework.context; -import static java.lang.String.format; import static me.devnatan.inventoryframework.utils.SlotConverter.convertSlot; import java.util.ArrayList; @@ -168,8 +167,6 @@ public final void availableSlot(@NotNull BiConsumer factory) { * @return An item builder to configure the item. */ public final @NotNull T layoutSlot(char character) { - requireNonReservedLayoutCharacter(character); - // TODO More detailed exception message final LayoutSlot layoutSlot = getLayoutSlots().stream() .filter(value -> value.getCharacter() == character) @@ -191,8 +188,6 @@ public final void availableSlot(@NotNull BiConsumer factory) { * @param character The layout character target. */ public final void layoutSlot(char character, @NotNull BiConsumer factory) { - requireNonReservedLayoutCharacter(character); - // TODO More detailed exception message final LayoutSlot layoutSlot = getLayoutSlots().stream() .filter(value -> value.getCharacter() == character) @@ -355,18 +350,5 @@ private void checkAlignedContainerTypeForSlotAssignment() { "Non-aligned container type %s cannot use row-column slots, use absolute %s instead", getContainer().getType().getIdentifier(), "#slot(n)")); } - - /** - * Checks if the character is a reserved layout character. - * - * @param character The character to be checked. - * @throws IllegalArgumentException If the given character is a reserved layout character. - */ - private void requireNonReservedLayoutCharacter(char character) { - if (character == LayoutSlot.FILLED_RESERVED_CHAR) - throw new IllegalArgumentException(format( - "The '%c' character cannot be used because it is only available for backwards compatibility. Please use another character.", - character)); - } // endregion } From 8090eea47d4a4ba52576e3b46586ea376d576cd2 Mon Sep 17 00:00:00 2001 From: Natan Date: Sat, 2 Aug 2025 14:10:08 -0300 Subject: [PATCH 7/9] fix: consider fallback items --- .../component/Pagination.java | 2 +- .../component/PaginationImpl.java | 73 +++++++++++++------ .../context/AbstractIFContext.java | 1 - 3 files changed, 53 insertions(+), 23 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 b56a060d5..06c3439ef 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 @@ -219,7 +219,7 @@ public interface Pagination extends ComponentComposition, StateValue { * * @param index The page index. * @param pageSize Number of elements that each page can have. - * @param pagesCount Pre-calculated total number of pages available (set zero if not available). + * @param pagesCount Total number of pages available (zero if not available) [exclusive]. * @param src The source to split. * @return All elements in a page. * @throws IndexOutOfBoundsException If the specified index is {@code < 0} or 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 e2f14a01b..7a51225fb 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 @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Objects; @@ -244,31 +245,60 @@ private void addComponentsForUnconstrainedPagination(IFRenderContext context, Li * @param pageContents Elements of the current page. */ private void addComponentsForLayeredPagination(IFRenderContext context, List pageContents) { - final LayoutSlot targetLayoutSlot = getLayoutSlotForCurrentTarget(context); - final int elementsLen = pageContents.size(); - debug("[Pagination] Elements count: %d elements", elementsLen); - debug("[Pagination] Iterating over '%c' layout target", targetLayoutSlot.getCharacter()); - - int iterationIndex = 0; - for (final int position : targetLayoutSlot.getPositions()) { - final Object value = pageContents.get(iterationIndex); + final LayoutSlot layoutSlot = getLayoutSlotForCurrentTarget(context); + debug("[Pagination] Is layout slot defined by the user? %b", layoutSlot.isDefinedByTheUser()); + + final int contentSize = pageContents.size(); + debug("[Pagination] Elements count: %d elements", contentSize); + debug("[Pagination] Iterating over '%c' layout target", layoutSlot.getCharacter()); + + int index = 0; + for (final int layoutPosition : layoutSlot.getPositions()) { + 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 + // using pagination and #layoutSlot with the same character as the pagination. + if (layoutSlot.isDefinedByTheUser()) { + final ComponentFactory componentFactory = + layoutSlot.getFactory().apply(index); + + if (componentFactory instanceof ItemComponentBuilder) { + final ItemComponentBuilder itemBuilder = (ItemComponentBuilder) componentFactory; + + // In normal scenarios they their position in LayoutRenderInterceptor. + // But since it uses the same layout target as a Pagination component, + // This is the same behavior as `PaginationStateBuilder#elementFactory` + itemBuilder.withSlot(layoutPosition); + + // Marking as "externally managed" prevents other components from modifying + // this item accidentally. + itemBuilder.withExternallyManaged(true); + } + + final Component component = componentFactory.create(); + + debug(() -> " @ placeholder %d (index %d) = %s", layoutPosition, index, component.toString()); + getInternalComponents().add(component); + index++; + continue; + } + + break; // EOF + } try { - final ComponentFactory factory = elementFactory.create(this, iterationIndex, position, value); + final Object paginatedValue = pageContents.get(index); + final ComponentFactory factory = elementFactory.create(this, index, layoutPosition, paginatedValue); final Component component = factory.create(); - debug( - () -> " @ added %d (index %d) = %s", - position, - iterationIndex, - component.getClass().getSimpleName()); + debug(() -> " @ added %d (index %d) = %s", layoutPosition, index, component.toString()); getInternalComponents().add(component); } catch (final Exception exception) { - debug(() -> " @ failed to add %d (index %d) = %s", position, iterationIndex, exception.getMessage()); + debug(() -> " @ failed to add %d (index %d) = %s", layoutPosition, index, exception.getMessage()); exception.printStackTrace(); } - if (iterationIndex++ == elementsLen) break; + index++; } } @@ -298,11 +328,12 @@ private LayoutSlot getLayoutSlotForCurrentTarget(IFRenderContext context) { final Optional layoutSlotOptional = context.getLayoutSlots().stream() .filter(layoutSlot -> layoutSlot.getCharacter() == getLayoutTarget()) - .findFirst(); + .max(Comparator.comparing(LayoutSlot::isDefinedByTheUser)); - if (!layoutSlotOptional.isPresent()) + if (!layoutSlotOptional.isPresent()) { // TODO more detailed error message throw new IllegalArgumentException(String.format("Layout slot target not found: %c", getLayoutTarget())); + } return (currentLayoutSlot = layoutSlotOptional.get()); } @@ -390,7 +421,7 @@ public int getPosition() { @Override public void render(@NotNull IFSlotRenderContext context) { - System.out.println("[debug] PaginationImpl: render(...)"); + System.out.println("[debug] PaginationImpl: render(...)"); if (!initialized) { setVisible(true); final IFRenderContext root = context.getParent(); @@ -404,7 +435,7 @@ public void render(@NotNull IFSlotRenderContext context) { } private void renderChild(IFSlotRenderContext context) { - System.out.println("[debug] PaginationImpl: rendering child"); + System.out.println("[debug] PaginationImpl: rendering child"); getInternalComponents().forEach(context::renderComponent); } @@ -460,7 +491,7 @@ public void clear(@NotNull IFContext context) { } @Override - public @UnmodifiableView Set> getWatchingStates() { + public @UnmodifiableView Set> getWatchingStates() { return Collections.emptySet(); } diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java index c1230925a..aa576eb6b 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java @@ -122,7 +122,6 @@ private IFSlotRenderContext createSlotRenderContext(@NotNull Component component @Override public void renderComponent(@NotNull Component component) { if (component.shouldRender(this)) { - IFDebug.debug("Rendering component...: %s", component); component.render(createSlotRenderContext(component, false)); return; } From 9af7dd82b613bff32eb30f0c75f6fad7993d0a16 Mon Sep 17 00:00:00 2001 From: Natan Date: Sat, 2 Aug 2025 14:31:49 -0300 Subject: [PATCH 8/9] docs: "they their" -> "item position are set in" --- .../inventoryframework/component/PaginationImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 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 7a51225fb..ef56ca4c7 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 @@ -265,9 +265,9 @@ private void addComponentsForLayeredPagination(IFRenderContext context, List if (componentFactory instanceof ItemComponentBuilder) { final ItemComponentBuilder itemBuilder = (ItemComponentBuilder) componentFactory; - // In normal scenarios they their position in LayoutRenderInterceptor. - // But since it uses the same layout target as a Pagination component, - // This is the same behavior as `PaginationStateBuilder#elementFactory` + // In normal scenarios item position are set in LayoutRenderInterceptor + // but since it uses the same layout target as a Pagination component, + // This is the same behavior as `PaginationStateBuilder#elementFactory`. itemBuilder.withSlot(layoutPosition); // Marking as "externally managed" prevents other components from modifying From b486586682570e335f19662633d2a16815338e8a Mon Sep 17 00:00:00 2001 From: Natan Date: Sat, 2 Aug 2025 14:34:50 -0300 Subject: [PATCH 9/9] refactor: lint codebase --- .../devnatan/inventoryframework/component/PaginationImpl.java | 2 -- 1 file changed, 2 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 ef56ca4c7..2237c51b4 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 @@ -421,7 +421,6 @@ public int getPosition() { @Override public void render(@NotNull IFSlotRenderContext context) { - System.out.println("[debug] PaginationImpl: render(...)"); if (!initialized) { setVisible(true); final IFRenderContext root = context.getParent(); @@ -435,7 +434,6 @@ public void render(@NotNull IFSlotRenderContext context) { } private void renderChild(IFSlotRenderContext context) { - System.out.println("[debug] PaginationImpl: rendering child"); getInternalComponents().forEach(context::renderComponent); }