Skip to content

Inventory and Slots

ssbaxys edited this page Oct 26, 2026 · 1 revision

Stacks, slots, grids and item drag and drop.

On this page: Stacks and inventories (inventory.Stack, inventory.Inventory) · Slots and grids (Slot, SlotGrid, InventoryPanel) · Drag and drop (SlotGroup)

Stacks and inventories (inventory.Stack, inventory.Inventory)

Stack is an immutable stack for the UI that doesn't depend on the game version: item id, count, custom name, lore lines, enchantment glint and durability. Inventory is the slot model: slots only show it and change it through it, so a mod syncs data with one onChange subscription, and rules can be checked without a screen.

Stack sword = Stack.of("minecraft:diamond_sword").withName("Excalibur")
        .withLore("Forged in the Nether").withGlint(true).withDurability(400, 1561);   // stack limit is 1
Stack coal = Stack.parse("coal*48");                                           // "minecraft:coal", 48
Inventory chest = new Inventory(27);
chest.set(0, sword);
Stack rest = chest.insert(Stack.of("minecraft:stone", 200));   // into matching stacks, then empty slots; returns the rest
chest.filter((index, s) -> index != 26 || s.itemId().endsWith("_ingot")).limit(64);
chest.onChange((inv, index, before, after) -> sync(index, after));

Stacks merge (canStackWith) when everything except the count matches. The limit is 64, 1 for items with durability, or your own with withMaxStackSize(16); an inventory can lower it with limit(n).

Slots and grids (Slot, SlotGrid, InventoryPanel)

Slot shows one slot: the item, the count in the bottom-right corner (thousands as 1.5k), a durability bar colored green to red, and the glint. The look is Slot.Look.VANILLA (a gray 18×18 slot with a beveled edge and a white highlight) or MODERN (a rounded theme slot using the well, border, accent tokens; background, radius and border can be styled). A non-empty slot has a tooltip as in the game (ItemTooltip: name, aqua for enchanted items, lore, durability when damaged); the tooltip look is tooltipKind.

Slot single = new Slot(Stack.of("minecraft:diamond", 12));       // its own one-slot inventory
Slot helmet = new Slot(armor, 0).placeholder("minecraft:iron_helmet");   // faint silhouette in an empty slot
Slot result = new Slot(craft, 0).dropTarget(false);               // take only
SlotGrid hotbar = new SlotGrid(player, 0, 9, 9).look(Slot.Look.MODERN).slotSize(22).gap(3);
InventoryPanel chestUi = InventoryPanel.chest("Chest", chest, player, Slot.Look.VANILLA);
InventoryPanel mine = new InventoryPanel(Slot.Look.MODERN).title("Furnace").add(row).add(hotbar, 4);
Slot.paintStack(canvas, stack, x, y, 16);                         // a stack in your own widget

InventoryPanel.chest builds a chest window: a grid of 9 per row, "Inventory", the backpack (player slots 9..35) and the hotbar (0..8); Shift+click moves between the chest and the player. In the core the player inventory is a plain 36-slot model; container screens (below) connect it to the real inventory and the server.

Drag and drop (SlotGroup)

  • LMB drag (from 3 px) takes the whole stack, RMB takes half rounded up; a "ghost" follows the cursor (a passive root overlay) and the source shows what's left. The model doesn't change until the drop, so cancelling rolls nothing back, and the mod sees one change per slot.
  • Dropping on an empty slot or a matching stack adds as much as fits; the rest stays in the source. A whole stack dropped on a different item swaps them (if both slots accept the other item). The slot under the cursor is highlighted normally if the drop is allowed and red if it isn't.
  • Dropping outside the slots calls onDropOutside (return true if the stack was taken), otherwise it cancels.
  • Shift+click is quickMove: your onQuickMove or a transfer into the inventories from Slot.quickMoveTo (slot ranges work too). The core reads Shift through Platform.isShiftDown().
SlotGroup group = new SlotGroup()
        .validator((from, to, stack) -> to.inventory() != shop)          // drop check on top of filters
        .onTransfer((from, to, moved) -> playSound())                   // to == null means Shift+click or a throw
        .onDropOutside((from, stack) -> { throwItem(stack); return true; })
        .onQuickMove((slot, stack) -> false);                           // false means the default transfer
chestGrid.group(group);
group.beginDrag(slot, false); group.dropOn(otherSlot);                  // the same from code (tests, keyboard)

Without their own group, slots share SlotGroup.shared(): you can drag between any slots.


← Markdown and Books · English home · Container Screens →

Clone this wiki locally