From e2e348d5c51fe45264181fe49de80c8deb7bc8a2 Mon Sep 17 00:00:00 2001 From: Dustin Norberg Date: Thu, 24 Sep 2026 14:54:42 -0400 Subject: [PATCH] Refine Writing Desk GUI to match Legacy layout --- README.md | 7 +- gradle.properties | 2 +- .../client/screen/WritingDeskScreen.java | 281 ++++++++++-------- .../gametest/WritingDeskGameTests.java | 29 ++ .../rivencraft/menu/WritingDeskMenu.java | 35 ++- 5 files changed, 216 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index 632e486..7eabc9d 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,10 @@ and right inventory. Put vanilla paper in the upper-left slot and Ink Vials in the upper-right; a copy appears in the lower-left result slot when the selected page is valid. Taking it consumes one paper and, by default, one ink. An empty meter consumes a vial to refill and -returns a vanilla glass bottle to the lower-right slot. The ALL browser searches -and filters symbols held in the desk's loose pages and Notebooks; selection -is validated against the server's current symbol catalog. +returns a vanilla glass bottle to the lower-right slot. The `AZ` control sorts +visible pages by name; `ALL` shows pages held across every desk tab. Search and +category filtering narrow the view without granting new pages, and cross-tab +copy selection is checked by the server. Selecting a stored page on the desk surface shows its Legacy-proportioned portrait preview and name above the player inventory. diff --git a/gradle.properties b/gradle.properties index c79688c..0ebe785 100644 --- a/gradle.properties +++ b/gradle.properties @@ -35,7 +35,7 @@ mod_name=Rivencraft # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=LGPL-3.0-or-later # The mod version. Rivencraft uses the x.y.z.b policy documented in README.md. -mod_version=0.3.6.1 +mod_version=0.3.6.2 immersive_portals_version=6.0.7 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. diff --git a/src/main/java/net/dnorberg/rivencraft/client/screen/WritingDeskScreen.java b/src/main/java/net/dnorberg/rivencraft/client/screen/WritingDeskScreen.java index c0fd52c..40c8119 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/screen/WritingDeskScreen.java +++ b/src/main/java/net/dnorberg/rivencraft/client/screen/WritingDeskScreen.java @@ -2,7 +2,8 @@ package net.dnorberg.rivencraft.client.screen; import java.util.List; import java.util.Locale; -import java.util.Objects; +import java.util.ArrayList; +import java.util.Comparator; import net.dnorberg.rivencraft.Rivencraft; import net.dnorberg.rivencraft.item.PageItem; import net.dnorberg.rivencraft.menu.WritingDeskMenu; @@ -29,56 +30,83 @@ public final class WritingDeskScreen extends AbstractContainerScreen { - allMode = true; + sortButton = addRenderableWidget(Button.builder(Component.literal("AZ"), button -> { + sortAlphabetically = !sortAlphabetically; scrollRows = 0; - }).bounds(leftPos + 48, topPos - 18, 29, 18).build()); - categoryButton = addRenderableWidget(Button.builder(Component.literal("All types"), button -> { + }).bounds(leftPos + 58, topPos - 18, 20, 18).build()); + allButton = addRenderableWidget(Button.builder(Component.literal("ALL"), button -> { + allMode = !allMode; + scrollRows = 0; + if (!allMode) { + selectedCollection = -1; + selectedPage = 0; + if (minecraft != null && minecraft.gameMode != null) + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, menu.activeCollection()); + } + }).bounds(leftPos + 80, topPos - 18, 28, 18).build()); + search = new EditBox(font, leftPos + 110, topPos - 18, 86, 18, + Component.translatable("screen.rivencraft.writing_desk.search")); + search.setMaxLength(64); + search.setHint(Component.translatable("screen.rivencraft.writing_desk.search")); + search.setResponder(value -> scrollRows = 0); + addRenderableWidget(search); + categoryButton = addRenderableWidget(Button.builder(Component.literal("C"), button -> { List categories = menu.catalog().stream() .map(SymbolDisplayMetadata::category).distinct().sorted().toList(); int next = selectedCategory == null ? 0 : categories.indexOf(selectedCategory) + 1; selectedCategory = next >= categories.size() ? null : categories.get(next); - categoryButton.setMessage(Component.literal(selectedCategory == null - ? "All types" : selectedCategory.getPath())); - allMode = true; + categoryButton.setMessage(Component.literal(selectedCategory == null ? "C" : "C*")); scrollRows = 0; - }).bounds(leftPos + 79, topPos - 18, 66, 18).build()); - search = new EditBox(font, leftPos + 147, topPos - 18, 69, 18, - Component.translatable("screen.rivencraft.writing_desk.search")); - search.setMaxLength(64); - search.setHint(Component.translatable("screen.rivencraft.writing_desk.search")); - search.setResponder(value -> { - allMode = true; - scrollRows = 0; - }); - addRenderableWidget(search); + }).bounds(leftPos + 198, topPos - 18, 18, 18).build()); } - private List filteredCatalog() { + private List filteredPages() { String query = search == null ? "" : search.getValue().trim().toLowerCase(Locale.ROOT); - return menu.catalog().stream() - .filter(entry -> selectedCategory == null || selectedCategory.equals(entry.category())) - .filter(entry -> query.isEmpty() - || entry.id().toString().toLowerCase(Locale.ROOT).contains(query) - || Component.translatable(entry.nameTranslationKey()).getString() - .toLowerCase(Locale.ROOT).contains(query)) - .toList(); + List entries = new ArrayList<>(); + int from = allMode ? 0 : menu.activeCollection(); + int until = allMode ? 4 : from + 1; + for (int collection = from; collection < until; collection++) { + List pages = pages(collection); + int last = -1; + for (int i = 0; i < pages.size(); i++) if (!pages.get(i).isEmpty()) last = i; + for (int i = 0; i <= last; i++) { + ItemStack page = pages.get(i); + if (page.isEmpty() && (allMode || sortAlphabetically || !query.isEmpty() || selectedCategory != null)) continue; + if (!page.isEmpty() && !query.isEmpty() + && !page.getHoverName().getString().toLowerCase(Locale.ROOT).contains(query) + && !PageItem.content(page).symbol().map(symbol -> symbol.id().toString() + .toLowerCase(Locale.ROOT).contains(query)).orElse(false)) continue; + if (!page.isEmpty() && selectedCategory != null) { + ResourceLocation id = PageItem.content(page).symbol().map(symbol -> symbol.id()).orElse(null); + if (menu.catalog().stream().noneMatch(metadata -> metadata.id().equals(id) + && metadata.category().equals(selectedCategory))) continue; + } + entries.add(new DisplayPage(collection, i, page)); + } + } + if (sortAlphabetically) entries.sort(Comparator + .comparing((DisplayPage entry) -> entry.stack().getHoverName().getString(), String.CASE_INSENSITIVE_ORDER) + .thenComparingInt(DisplayPage::collection).thenComparingInt(DisplayPage::index)); + return entries; } @Override public void renderBackground(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { @@ -90,28 +118,40 @@ public final class WritingDeskScreen extends AbstractContainerScreen 0) graphics.fill(x + 352, y + 73 - filled, x + 362, y + 73, 0xFF6666AA); - graphics.renderOutline(x + 350, y + 20, 14, 55, 0xFF484848); + if (filled > 0) graphics.fill(x + 352, y + 62 - filled, x + 362, y + 62, 0xFF6666AA); + graphics.renderOutline(x + 350, y + 9, 14, 55, 0xFF484848); for (int row = 0; row < 3; row++) { - for (int column = 0; column < 9; column++) slotFrame(graphics, 224 + column * 18, 91 + row * 18); + for (int column = 0; column < 9; column++) slotFrame(graphics, 224 + column * 18, 79 + row * 18); } - for (int column = 0; column < 9; column++) slotFrame(graphics, 224 + column * 18, 149); + for (int column = 0; column < 9; column++) slotFrame(graphics, 224 + column * 18, 137); } private void slotFrame(GuiGraphics graphics, int x, int y) { @@ -122,22 +162,29 @@ public final class WritingDeskScreen extends AbstractContainerScreen= leftPos + 350 && mouseX < leftPos + 364 - && mouseY >= topPos + 20 && mouseY < topPos + 75) { + && mouseY >= topPos + 9 && mouseY < topPos + 64) { graphics.renderTooltip(font, Component.translatable("screen.rivencraft.writing_desk.ink", menu.inkRemaining(), 16), mouseX, mouseY); } + if (mouseX >= leftPos + 198 && mouseX < leftPos + 216 + && mouseY >= topPos - 18 && mouseY < topPos) { + graphics.renderTooltip(font, Component.literal(selectedCategory == null + ? "All categories" : selectedCategory.toString()), mouseX, mouseY); + } } - private List pages() { - ItemStack collection = menu.getSlot(menu.activeCollection()).getItem(); + private List pages(int collectionIndex) { + ItemStack collection = menu.getSlot(collectionIndex).getItem(); if (collection.is(ModItems.PAGE.get())) return List.of(collection); if (collection.is(ModItems.NOTEBOOK.get())) { return collection.getOrDefault(ModDataComponents.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages(); @@ -146,73 +193,34 @@ public final class WritingDeskScreen extends AbstractContainerScreen pages = pages(); if (lastCollection != menu.activeCollection()) { lastCollection = menu.activeCollection(); scrollRows = 0; selectedPage = 0; + selectedCollection = -1; } - int last = -1; - for (int i = 0; i < pages.size(); i++) if (!pages.get(i).isEmpty()) last = i; - for (int i = scrollRows * 5; i <= last && i < scrollRows * 5 + 15; i++) { + List entries = filteredPages(); + for (int i = scrollRows * 5; i < entries.size() && i < scrollRows * 5 + 15; i++) { int column = i % 5; int row = i / 5 - scrollRows; - ItemStack page = pages.get(i); - int x = leftPos + 48 + column * 31; + DisplayPage entry = entries.get(i); + ItemStack page = entry.stack(); + int x = leftPos + 58 + column * 29; int y = topPos + 18 + row * 41; drawPage(graphics, page, x, y); if (page.isEmpty()) graphics.fill(x, y, x + 30, y + 40, 0xB8000000); - if (i == selectedPage && !page.isEmpty()) graphics.renderOutline(x, y, 30, 40, 0xFFFFD061); - } - if (selectedPage >= pages.size() || pages.get(selectedPage).isEmpty()) return; - ItemStack selected = pages.get(selectedPage); - graphics.pose().pushPose(); - graphics.pose().translate(leftPos + 294, topPos + 12, 0); - graphics.pose().scale(1.35F, 1.35F, 1.0F); - drawPage(graphics, selected, 0, 0); - graphics.pose().popPose(); - String title = font.plainSubstrByWidth(selected.getHoverName().getString(), 112); - graphics.fill(leftPos + 251, topPos + 64, leftPos + 365, topPos + 77, 0xFF202020); - graphics.drawString(font, title, leftPos + 254, topPos + 67, 0xFFE0E0E0, false); - } - - private void renderCatalog(GuiGraphics graphics) { - List filtered = filteredCatalog(); - if (filtered.isEmpty()) { - graphics.drawString(font, Component.translatable("screen.rivencraft.writing_desk.no_symbols"), - leftPos + 52, topPos + 24, 0xFFC4B8A3, false); - } - for (int i = scrollRows * 5; i < filtered.size() && i < scrollRows * 5 + 15; i++) { - SymbolDisplayMetadata entry = filtered.get(i); - int x = leftPos + 48 + (i % 5) * 31; - int y = topPos + 18 + (i / 5 - scrollRows) * 41; - drawSymbolPage(graphics, entry.id(), x, y); - if (Objects.equals(entry.id(), selectedSymbol)) { + int active = selectedCollection >= 0 ? selectedCollection : menu.activeCollection(); + if (entry.collection() == active && entry.index() == selectedPage && !page.isEmpty()) graphics.renderOutline(x, y, 30, 40, 0xFFFFD061); - } } - SymbolDisplayMetadata selected = menu.catalog().stream() - .filter(entry -> Objects.equals(entry.id(), selectedSymbol)).findFirst().orElse(null); - if (selected == null) return; - graphics.pose().pushPose(); - graphics.pose().translate(leftPos + 294, topPos + 12, 0); - graphics.pose().scale(1.35F, 1.35F, 1.0F); - drawSymbolPage(graphics, selected.id(), 0, 0); - graphics.pose().popPose(); - String title = font.plainSubstrByWidth( - Component.translatable(selected.nameTranslationKey()).getString(), 112); - graphics.fill(leftPos + 251, topPos + 64, leftPos + 365, topPos + 77, 0xFF202020); - graphics.drawString(font, title, leftPos + 254, topPos + 67, 0xFFE0E0E0, false); - } - - private void drawSymbolPage(GuiGraphics graphics, ResourceLocation id, int x, int y) { - graphics.blit(PAGE_ATLAS, x, y, 30, 40, 156, 0, 30, 40, 256, 256); - graphics.blit(NotebookScreen.symbolTexture(id), x + 1, y + 6, - 28, 28, 16, 16, 96, 96, 128, 128); + int active = selectedCollection >= 0 ? selectedCollection : menu.activeCollection(); + List source = pages(active); + if (selectedPage >= source.size() || source.get(selectedPage).isEmpty()) return; + ItemStack selected = source.get(selectedPage); + drawPage(graphics, selected, leftPos + 253, topPos + 10); + String title = font.plainSubstrByWidth(selected.getHoverName().getString(), 84); + graphics.fill(leftPos + 251, topPos + 58, leftPos + 342, topPos + 73, 0xFF202020); + graphics.drawString(font, title, leftPos + 254, topPos + 61, 0xFFE0E0E0, false); } private void drawPage(GuiGraphics graphics, ItemStack page, int x, int y) { @@ -228,41 +236,40 @@ public final class WritingDeskScreen extends AbstractContainerScreen= leftPos + 206 && mouseX < leftPos + 217 + && mouseY >= topPos + 14 && mouseY < topPos + 144) { + draggingScrollbar = true; + setScrollFromMouse(mouseY); + return true; + } + int x = (int) mouseX - leftPos - 58; int y = (int) mouseY - topPos - 18; - if (x >= 0 && x < 155 && y >= 0 && y < 123) { - int column = x / 31; + if (x >= 0 && x < 145 && y >= 0 && y < 123) { + int column = x / 29; int row = y / 41; int index = (row + scrollRows) * 5 + column; - if (allMode) { - List filtered = filteredCatalog(); - if (x % 31 < 30 && y % 41 < 40 && index < filtered.size()) { - SymbolDisplayMetadata entry = filtered.get(index); - selectedSymbol = entry.id(); - int serverIndex = menu.catalog().indexOf(entry); - if (serverIndex >= 0 && minecraft != null && minecraft.gameMode != null) { - minecraft.gameMode.handleInventoryButtonClick(menu.containerId, 100 + serverIndex); - } - } - } else { - List pages = pages(); - if (x % 31 < 30 && y % 41 < 40 && index < pages.size() - && !pages.get(index).isEmpty()) { - selectedPage = index; + List entries = filteredPages(); + if (y % 41 < 40 && index < entries.size()) { + DisplayPage entry = entries.get(index); + if (!entry.stack().isEmpty()) { + selectedCollection = entry.collection(); + selectedPage = entry.index(); if (minecraft != null && minecraft.gameMode != null) { - minecraft.gameMode.handleInventoryButtonClick(menu.containerId, 16 + index); + int id = allMode ? 1000 + entry.collection() * NotebookContents.MAX_PAGES + entry.index() + : 16 + entry.index(); + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, id); } } } return true; } } - if (button == 0 && mouseX >= leftPos + 29 && mouseX < leftPos + 40) { + if (button == 0 && mouseX >= leftPos && mouseX < leftPos + 35) { for (int i = 0; i < 4; i++) { int top = topPos + 16 + i * 37; - if (mouseY >= top && mouseY < top + 32) { + if (mouseY >= top && mouseY < top + 36) { allMode = false; - selectedSymbol = null; + selectedCollection = -1; if (minecraft != null && minecraft.gameMode != null) { minecraft.gameMode.handleInventoryButtonClick(menu.containerId, i); } @@ -274,15 +281,37 @@ public final class WritingDeskScreen extends AbstractContainerScreen= leftPos + 48 && mouseX < leftPos + 203 + if (mouseX >= leftPos + 58 && mouseX < leftPos + 217 && mouseY >= topPos + 18 && mouseY < topPos + 141) { - int rows = ((allMode ? filteredCatalog().size() : pages().size()) + 4) / 5; + int rows = (filteredPages().size() + 4) / 5; scrollRows = Math.clamp(scrollRows - (int) Math.signum(scrollY), 0, Math.max(0, rows - 3)); return true; } return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY); } + private void setScrollFromMouse(double mouseY) { + int rows = (filteredPages().size() + 4) / 5; + int max = Math.max(0, rows - 3); + scrollRows = Math.clamp((int) Math.round((mouseY - topPos - 16) * max / 126.0), 0, max); + } + + @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) { + if (button == 0 && draggingScrollbar) { + setScrollFromMouse(mouseY); + return true; + } + return super.mouseDragged(mouseX, mouseY, button, dragX, dragY); + } + + @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { + if (button == 0 && draggingScrollbar) { + draggingScrollbar = false; + return true; + } + return super.mouseReleased(mouseX, mouseY, button); + } + @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { if (search != null && search.isFocused()) { if (keyCode == 256) { diff --git a/src/main/java/net/dnorberg/rivencraft/gametest/WritingDeskGameTests.java b/src/main/java/net/dnorberg/rivencraft/gametest/WritingDeskGameTests.java index 3c3e4a7..9e17332 100644 --- a/src/main/java/net/dnorberg/rivencraft/gametest/WritingDeskGameTests.java +++ b/src/main/java/net/dnorberg/rivencraft/gametest/WritingDeskGameTests.java @@ -321,4 +321,33 @@ public final class WritingDeskGameTests { "Open shelves must not cull terrain behind the desk"); helper.succeed(); } + + @GameTest(template = "empty") + public static void allPagesSelectionUsesTheRequestedPhysicalTab(GameTestHelper helper) { + BlockPos pos = helper.absolutePos(new BlockPos(1, 1, 1)); + helper.getLevel().setBlock(pos, ModBlocks.WRITING_DESK.get().defaultBlockState(), 3); + WritingDeskBlockEntity desk = (WritingDeskBlockEntity) helper.getLevel().getBlockEntity(pos); + ItemStack first = PageItem.createLinkPanelPage(); + ItemStack second = PageItem.createSymbolPage(new SymbolReference( + ResourceLocation.parse("rivencraft:terrain/standard"))); + desk.setItem(0, first); + ItemStack notebook = new ItemStack(ModItems.NOTEBOOK.get()); + notebook.set(ModDataComponents.NOTEBOOK_CONTENTS.get(), + new NotebookContents(List.of(ItemStack.EMPTY, second))); + desk.setItem(1, notebook); + desk.setItem(WritingDeskBlockEntity.PAPER, new ItemStack(Items.PAPER)); + desk.setItem(WritingDeskBlockEntity.INK, new ItemStack(ModItems.INK_VIAL.get())); + var player = helper.makeMockServerPlayerInLevel(); + player.moveTo(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5); + WritingDeskMenu menu = new WritingDeskMenu(1, player.getInventory(), desk); + helper.assertFalse(menu.clickMenuButton(player, 1000 + 2 * NotebookContents.MAX_PAGES), + "ALL view must reject empty cross-tab positions"); + helper.assertTrue(menu.clickMenuButton(player, 1000 + NotebookContents.MAX_PAGES + 1), + "ALL view must accept the populated second tab page"); + helper.assertTrue(ItemStack.matches(menu.getSlot(WritingDeskMenu.COPY_RESULT).getItem(), second), + "Copy preview must match the physical page selected from the other tab"); + helper.assertValueEqual(desk.activeCollection(), 0, + "Selecting from ALL must not silently change the active tab"); + helper.succeed(); + } } diff --git a/src/main/java/net/dnorberg/rivencraft/menu/WritingDeskMenu.java b/src/main/java/net/dnorberg/rivencraft/menu/WritingDeskMenu.java index 34a9942..d896215 100644 --- a/src/main/java/net/dnorberg/rivencraft/menu/WritingDeskMenu.java +++ b/src/main/java/net/dnorberg/rivencraft/menu/WritingDeskMenu.java @@ -37,6 +37,7 @@ public final class WritingDeskMenu extends AbstractContainerMenu { private int syncedActiveCollection; private int syncedInkRemaining; private int selectedPage; + private int selectedCollection = -1; private ResourceLocation selectedSymbol; private List catalog = List.of(); private final ServerPlayer serverViewer; @@ -65,20 +66,20 @@ public final class WritingDeskMenu extends AbstractContainerMenu { @Override public void set(int value) { syncedInkRemaining = value; } }); for (int i = 0; i < WritingDeskBlockEntity.COLLECTIONS; i++) { - addSlot(new DeskSlot(desk, i, 12, 20 + i * 37, 1)); + addSlot(new DeskSlot(desk, i, 36, 20 + i * 37, 1)); } - addSlot(new DeskSlot(desk, WritingDeskBlockEntity.INK, 370, 20, 16)); + addSlot(new DeskSlot(desk, WritingDeskBlockEntity.INK, 370, 9, 16)); addSlot(new DeskSlot(desk, WritingDeskBlockEntity.RETURN, 370, 57, 64)); - addSlot(new DeskSlot(desk, WritingDeskBlockEntity.PAPER, 228, 20, 64)); + addSlot(new DeskSlot(desk, WritingDeskBlockEntity.PAPER, 228, 9, 64)); addSlot(new DeskSlot(desk, WritingDeskBlockEntity.LEGACY_OUTPUT, -1000, -1000, 64)); addSlot(new CopySlot(result, 0, 228, 57)); for (int row = 0; row < 3; row++) { for (int column = 0; column < 9; column++) { - addSlot(new Slot(inventory, column + row * 9 + 9, 224 + column * 18, 91 + row * 18)); + addSlot(new Slot(inventory, column + row * 9 + 9, 224 + column * 18, 79 + row * 18)); } } for (int column = 0; column < 9; column++) { - addSlot(new Slot(inventory, column, 224 + column * 18, 149)); + addSlot(new Slot(inventory, column, 224 + column * 18, 137)); } } @@ -92,9 +93,19 @@ public final class WritingDeskMenu extends AbstractContainerMenu { if (id >= 0 && id < WritingDeskBlockEntity.COLLECTIONS) { blockEntity.setActiveCollection(id); selectedPage = 0; + selectedCollection = -1; selectedSymbol = null; } else if (id >= 16 && id < 16 + NotebookContents.MAX_PAGES) { selectedPage = id - 16; + selectedCollection = -1; + selectedSymbol = null; + } else if (id >= 1000 && id < 1000 + WritingDeskBlockEntity.COLLECTIONS * NotebookContents.MAX_PAGES) { + int offset = id - 1000; + int collection = offset / NotebookContents.MAX_PAGES; + int page = offset % NotebookContents.MAX_PAGES; + if (pageAt(collection, page).isEmpty()) return false; + selectedCollection = collection; + selectedPage = page; selectedSymbol = null; } else if (id >= 100 && id < 100 + catalog.size()) { if (!catalog.equals(WritingDeskCatalog.available(blockEntity))) { @@ -162,12 +173,20 @@ public final class WritingDeskMenu extends AbstractContainerMenu { .anyMatch(entry -> entry.id().equals(selectedSymbol)) ? PageItem.createSymbolPage(reference) : ItemStack.EMPTY; } - ItemStack collection = desk.getItem(blockEntity == null ? syncedActiveCollection : blockEntity.activeCollection()); - if (collection.is(ModItems.PAGE.get())) return selectedPage == 0 ? collection : ItemStack.EMPTY; + int collectionIndex = selectedCollection >= 0 ? selectedCollection + : blockEntity == null ? syncedActiveCollection : blockEntity.activeCollection(); + return pageAt(collectionIndex, selectedPage); + } + + private ItemStack pageAt(int collectionIndex, int pageIndex) { + if (collectionIndex < 0 || collectionIndex >= WritingDeskBlockEntity.COLLECTIONS + || pageIndex < 0 || pageIndex >= NotebookContents.MAX_PAGES) return ItemStack.EMPTY; + ItemStack collection = desk.getItem(collectionIndex); + if (collection.is(ModItems.PAGE.get())) return pageIndex == 0 ? collection : ItemStack.EMPTY; if (collection.is(ModItems.NOTEBOOK.get())) { List pages = collection.getOrDefault( ModDataComponents.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages(); - return selectedPage < pages.size() ? pages.get(selectedPage) : ItemStack.EMPTY; + return pageIndex < pages.size() ? pages.get(pageIndex) : ItemStack.EMPTY; } return ItemStack.EMPTY; }