Gui
What is a Gui?
In InvUI, a Gui
is simply a rectangle of slots. Each slot can either be empty or contain something like an Item, a link to another Gui, or a link to an Inventory.
Gui Types
Normal Gui
Using Gui.builder()
, you can create a very simple Gui without any special functionality. With the gui builder, you can define a structure where each character corresponds to an ingredient, in this case the glass pane item.
Paged Gui
A PagedGui
is a specialized gui type that has pages of content. This content can be either items, entire guis, or inventories. The following example will use items as content.
Before creating a PagedGui
, you must first create the page buttons with which the player is supposed to navigate it. For that, bound items are a useful tool. Using BoundItem.pagedBuilder()
, you can create a builder for a bound item that targets paged guis. This item's ItemProvider
will also automatically be updated when the current page or total page count changes:
BoundItem back = BoundItem.pagedBuilder()
.setItemProvider(new ItemBuilder(Material.ARROW))
.addClickHandler((item, gui, click) -> gui.setPage(gui.getPage() - 1))
.build();
BoundItem forward = BoundItem.pagedBuilder()
.setItemProvider(new ItemBuilder(Material.ARROW))
.addClickHandler((item, gui, click) -> gui.setPage(gui.getPage() + 1))
.build();
When creating the PagedGui
, you will need to tell InvUI where to put the page content. This is what Markers.CONTENT_LIST_SLOT_HORIZONTAL
does. In the following example, the ingredient x
is marked as a content list slot, i.e. a slot for page content. There is also Markers.CONTENT_LIST_SLOT_VERTICAL
. The difference between the two is that with the HORIZONTAL
marker, the slots are filled left-to-right row-by-row, whereas the VERTICAL
marker orders the items top-down column-by-column.
val allTheItemTypes: List<Item> = Material.entries
.filter { !it.isLegacy && it.isItem }
.map { Item.simple(ItemBuilder(it)) }
val gui: PagedGui<Item> = PagedGui.itemsBuilder()
.setStructure(
"# # # # # # # # #",
"# x x x x x x x #",
"# x x x x x x x #",
"# x x x x x x x #",
"# # # < # > # # #",
)
.addIngredient('#', Item.simple(ItemBuilder(Material.BLACK_STAINED_GLASS_PANE)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('<', back)
.addIngredient('>', forward)
.setContent(allTheItemTypes)
.build()
List<Item> allTheItemTypes = Arrays.stream(Material.values())
.filter(m -> !m.isLegacy() && m.isItem())
.map(m -> Item.simple(new ItemBuilder(m)))
.toList();
PagedGui<Item> gui = PagedGui.itemsBuilder()
.setStructure(
"# # # # # # # # #",
"# x x x x x x x #",
"# x x x x x x x #",
"# x x x x x x x #",
"# # # < # > # # #"
)
.addIngredient('#', Item.simple(new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('<', back)
.addIngredient('>', forward)
.setContent(allTheItemTypes)
.build();
Markers.CONTENT_LIST_SLOT_HORIZONTAL
vs. Markers.CONTENT_LIST_SLOT_VERTICAL
The above example uses Markers.CONTENT_LIST_SLOT_HORIZONTAL
, but there is also Markers.CONTENT_LIST_SLOT_VERTICAL
. The difference between the two is that with the HORIZONTAL
marker, the slots are filled left-to-right row-by-row, whereas the VERTICAL
marker orders the items top-down column-by-column.
Example: Page buttons with proper item names
The above example omits names from the navigation buttons for simplicity. In real-world cases, you will likely want to do something like this:
val back: BoundItem = BoundItem.pagedBuilder()
.setItemProvider { _, gui ->
if (gui.page > 0) {
ItemBuilder(Material.ARROW)
.setName("<gray>Move to page <aqua>${gui.page}<gray>/<aqua>${gui.pageCount}") // (1)!
} else {
// no arrow if we can't go further back
ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).hideTooltip(true)
}
}
.addClickHandler { _, gui, _ -> gui.page-- }
.build()
val forward: BoundItem = BoundItem.pagedBuilder()
.setItemProvider { _, gui ->
if (gui.page < gui.pageCount - 1) {
ItemBuilder(Material.ARROW)
.setName("<gray>Move to page <aqua>${gui.page + 2}<gray>/<aqua>${gui.pageCount}")
} else {
// no arrow if we can't go further
ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).hideTooltip(true)
}
}
.addClickHandler { _, gui, _ -> gui.page++ }
.build()
- Names in
ItemBuilder
are in MiniMessage format.
BoundItem back = BoundItem.pagedBuilder()
.setItemProvider((player, gui) -> {
if (gui.getPage() > 0) {
return new ItemBuilder(Material.ARROW)
.setName("<gray>Move to page <aqua>" + gui.getPage() + "<gray>/<aqua>" + gui.getPageCount()); // (1)!
} else {
// no arrow if we can't go further back
return new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).hideTooltip(true);
}
})
.addClickHandler((item, gui, click) -> gui.setPage(gui.getPage() - 1))
.build();
BoundItem forward = BoundItem.pagedBuilder()
.setItemProvider((player, gui) -> {
if (gui.getPage() < gui.getPageCount() - 1) {
return new ItemBuilder(Material.ARROW)
.setName("<gray>Move to page <aqua>" + (gui.getPage() + 2) + "<gray>/<aqua>" + gui.getPageCount());
} else {
// no arrow if we can't go further back
return new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).hideTooltip(true);
}
})
.addClickHandler((item, gui, click) -> gui.setPage(gui.getPage() + 1))
.build();
- Names in
ItemBuilder
are in MiniMessage format.
Scroll Gui
Like paged guis, a ScrollGui
is also a specialized gui type that can contain items, guis, or inventories. However, instead of pages, content is displayed in lines that can be scrolled through.
Firstly, you will need to create the scroll buttons:
BoundItem up = BoundItem.scrollBuilder()
.setItemProvider(new ItemBuilder(Material.ARROW))
.addClickHandler((item, gui, click) -> gui.setLine(gui.getLine() - 1))
.build();
BoundItem down = BoundItem.scrollBuilder()
.setItemProvider(new ItemBuilder(Material.ARROW))
.addClickHandler((item, gui, click) -> gui.setLine(gui.getLine() + 1))
.build();
Like for paged guis, the content slots can be defined by adding a content list slot marker ingredient:
val allTheItemTypes: List<Item> = Material.entries
.filter { !it.isLegacy && it.isItem }
.map { Item.simple(ItemBuilder(it)) }
val gui: ScrollGui<Item> = ScrollGui.itemsBuilder()
.setStructure(
"# # x x x x x x #",
"u # x x x x x x #",
"# # x x x x x x #",
"d # x x x x x x #",
"# # x x x x x x #",
)
.addIngredient('#', Item.simple(ItemBuilder(Material.BLACK_STAINED_GLASS_PANE)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('u', up)
.addIngredient('d', down)
.setContent(allTheItemTypes)
.build()
List<Item> allTheItemTypes = Arrays.stream(Material.values())
.filter(m -> !m.isLegacy() && m.isItem())
.map(m -> Item.simple(new ItemBuilder(m)))
.toList();
ScrollGui<Item> gui = ScrollGui.itemsBuilder()
.setStructure(
"# # x x x x x x #",
"u # x x x x x x #",
"# # x x x x x x #",
"d # x x x x x x #",
"# # x x x x x x #"
)
.addIngredient('#', Item.simple(new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('u', up)
.addIngredient('d', down)
.setContent(allTheItemTypes)
.build();
Tab Gui
A TabGui
allows you to define a list of tabs, which themselves are also guis. You can then create tab buttons to switch between them.
First, let's create the tab buttons:
BoundItem tab0Btn = BoundItem.tabBuilder()
.setItemProvider(new ItemBuilder(Material.PAPER))
.addClickHandler((item, gui, click) -> gui.setTab(0))
.build();
BoundItem tab1Btn = BoundItem.tabBuilder()
.setItemProvider(new ItemBuilder(Material.CHAIN))
.addClickHandler((item, gui, click) -> gui.setTab(1))
.build();
Like before, you can define the content list slots for the tab area using a content list slot marker ingredient. In the following example, I have re-used the example paged- and scroll guis from above as tabs (after adjusting them a bit to fit into the dimensions of a tab):
val gui: TabGui = TabGui.builder()
.setStructure(
"# # # 0 # 1 # # #",
"x x x x x x x x x",
"x x x x x x x x x",
"x x x x x x x x x",
"x x x x x x x x x",
)
.addIngredient('#', Item.simple(ItemBuilder(Material.BLACK_STAINED_GLASS_PANE)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('0', tab0Btn)
.addIngredient('1', tab1Btn)
.setTabs(listOf(tab0, tab1))
.build()
TabGui gui = TabGui.builder()
.setStructure(
"# # # 0 # 1 # # #",
"x x x x x x x x x",
"x x x x x x x x x",
"x x x x x x x x x",
"x x x x x x x x x"
)
.addIngredient('#', Item.simple(new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('0', tab0Btn)
.addIngredient('1', tab1Btn)
.setTabs(List.of(tab0, tab1))
.build();
Full code
val back: BoundItem = BoundItem.pagedBuilder()
.setItemProvider(ItemBuilder(Material.ARROW))
.addClickHandler { _, gui, _ -> gui.page-- }
.build()
val forward: BoundItem = BoundItem.pagedBuilder()
.setItemProvider(ItemBuilder(Material.ARROW))
.addClickHandler { _, gui, _ -> gui.page++ }
.build()
val up: BoundItem = BoundItem.scrollBuilder()
.setItemProvider(ItemBuilder(Material.ARROW))
.addClickHandler { _, gui, _ -> gui.line-- }
.build()
val down: BoundItem = BoundItem.scrollBuilder()
.setItemProvider(ItemBuilder(Material.ARROW))
.addClickHandler { _, gui, _ -> gui.line++ }
.build()
val tab0Btn: BoundItem = BoundItem.tabBuilder()
.setItemProvider(ItemBuilder(Material.PAPER))
.addClickHandler { _, gui, _ -> gui.tab = 0 }
.build()
val tab1Btn: BoundItem = BoundItem.tabBuilder()
.setItemProvider(ItemBuilder(Material.CHAIN))
.addClickHandler { _, gui, _ -> gui.tab = 1 }
.build()
val allTheItemTypes: List<Item> = Material.entries
.filter { !it.isLegacy && it.isItem }
.map { Item.simple(ItemBuilder(it)) }
val tab0: PagedGui<Item> = PagedGui.itemsBuilder()
.setStructure(
"x x x x x x x x x",
"x x x x x x x x x",
"x x x x x x x x x",
"# # # < # > # # #",
)
.addIngredient('#', Item.simple(ItemBuilder(Material.BLACK_STAINED_GLASS_PANE)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('<', back)
.addIngredient('>', forward)
.setContent(allTheItemTypes)
.build()
val tab1: ScrollGui<Item> = ScrollGui.itemsBuilder()
.setStructure(
"u # x x x x x x #",
"# # x x x x x x #",
"# # x x x x x x #",
"d # x x x x x x #",
)
.addIngredient('#', Item.simple(ItemBuilder(Material.BLACK_STAINED_GLASS_PANE)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('u', up)
.addIngredient('d', down)
.setContent(allTheItemTypes)
.build()
val gui: TabGui = TabGui.builder()
.setStructure(
"# # # 0 # 1 # # #",
"x x x x x x x x x",
"x x x x x x x x x",
"x x x x x x x x x",
"x x x x x x x x x",
)
.addIngredient('#', Item.simple(ItemBuilder(Material.BLACK_STAINED_GLASS_PANE)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('0', tab0Btn)
.addIngredient('1', tab1Btn)
.setTabs(listOf(tab0, tab1))
.build()
BoundItem back = BoundItem.pagedBuilder()
.setItemProvider(new ItemBuilder(Material.ARROW))
.addClickHandler((item, gui, click) -> gui.setPage(gui.getPage() - 1))
.build();
BoundItem forward = BoundItem.pagedBuilder()
.setItemProvider(new ItemBuilder(Material.ARROW))
.addClickHandler((item, gui, click) -> gui.setPage(gui.getPage() + 1))
.build();
BoundItem up = BoundItem.scrollBuilder()
.setItemProvider(new ItemBuilder(Material.ARROW))
.addClickHandler((item, gui, click) -> gui.setLine(gui.getLine() - 1))
.build();
BoundItem down = BoundItem.scrollBuilder()
.setItemProvider(new ItemBuilder(Material.ARROW))
.addClickHandler((item, gui, click) -> gui.setLine(gui.getLine() + 1))
.build();
BoundItem tab0Btn = BoundItem.tabBuilder()
.setItemProvider(new ItemBuilder(Material.PAPER))
.addClickHandler((item, gui, click) -> gui.setTab(0))
.build();
BoundItem tab1Btn = BoundItem.tabBuilder()
.setItemProvider(new ItemBuilder(Material.CHAIN))
.addClickHandler((item, gui, click) -> gui.setTab(1))
.build();
List<Item> allTheItemTypes = Arrays.stream(Material.values())
.filter(m -> !m.isLegacy() && m.isItem())
.map(m -> Item.simple(new ItemBuilder(m)))
.toList();
PagedGui<Item> tab0 = PagedGui.itemsBuilder()
.setStructure(
"# x x x x x x x #",
"# x x x x x x x #",
"# x x x x x x x #",
"# # # < # > # # #"
)
.addIngredient('#', Item.simple(new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('<', back)
.addIngredient('>', forward)
.setContent(allTheItemTypes)
.build();
ScrollGui<Item> tab1 = ScrollGui.itemsBuilder()
.setStructure(
"u # x x x x x x #",
"# # x x x x x x #",
"d # x x x x x x #",
"# # x x x x x x #"
)
.addIngredient('#', Item.simple(new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('u', up)
.addIngredient('d', down)
.setContent(allTheItemTypes)
.build();
TabGui gui = TabGui.builder()
.setStructure(
"# # # 0 # 1 # # #",
"x x x x x x x x x",
"x x x x x x x x x",
"x x x x x x x x x",
"x x x x x x x x x"
)
.addIngredient('#', Item.simple(new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('0', tab0Btn)
.addIngredient('1', tab1Btn)
.setTabs(List.of(tab0, tab1))
.build();
Global Ingredients
Some ingredients, like content list slot markers or navigation buttons, are expected to stay the same across guis. For such cases, it may make sense to register the ingredients globally. Once you have defined a global ingredient, it will be automatically used by all gui builders, without having to manually call addIngredient
every time.
Structure.addGlobalIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
Structure.addGlobalIngredient(
'>',
BoundItem.pagedBuilder()
.setItemProvider(ItemBuilder(Material.ARROW))
.addClickHandler { _, gui, _ -> gui.page++ }
)
Structure.addGlobalIngredient(
'<',
BoundItem.pagedBuilder()
.setItemProvider(ItemBuilder(Material.ARROW))
.addClickHandler { _, gui, _ -> gui.page-- }
)
Structure.addGlobalIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL);
Structure.addGlobalIngredient(
'>',
BoundItem.pagedBuilder()
.setItemProvider(new ItemBuilder(Material.ARROW))
.addClickHandler((item, gui, click) -> gui.setPage(gui.getPage() + 1))
);
Structure.addGlobalIngredient(
'<',
BoundItem.pagedBuilder()
.setItemProvider(new ItemBuilder(Material.ARROW))
.addClickHandler((item, gui, click) -> gui.setPage(gui.getPage() - 1))
);
Ingredient Preset
Instead of defining global ingredients, you can also create ingredient presets which you can then apply whenever needed:
val preset: IngredientPreset = IngredientPreset.builder()
.addIngredient(
'>',
BoundItem.pagedBuilder()
.setItemProvider(ItemBuilder(Material.ARROW))
.addClickHandler { _, gui, _ -> gui.page++ }
)
.addIngredient(
'<',
BoundItem.pagedBuilder()
.setItemProvider(ItemBuilder(Material.ARROW))
.addClickHandler { _, gui, _ -> gui.page-- }
)
.build()
val gui: Gui = Gui.builder()
.setStructure(/* ... */)
.applyPreset(preset)
.build()
IngredientPreset preset = IngredientPreset.builder()
.addIngredient(
'>',
BoundItem.pagedBuilder()
.setItemProvider(new ItemBuilder(Material.ARROW))
.addClickHandler((item, gui, click) -> gui.setPage(gui.getPage() + 1))
)
.addIngredient(
'<',
BoundItem.pagedBuilder()
.setItemProvider(new ItemBuilder(Material.ARROW))
.addClickHandler((item, gui, click) -> gui.setPage(gui.getPage() - 1))
)
.build();
Gui gui = Gui.builder()
.setStructure(/* ... */)
.applyPreset(preset)
.build();
Animations
An animation lets slots of a gui pop in following a specific order with a specific delay.
Animations consist of the following five components:
- Slot filter: Defines which slots are part of the animation.
- Slot selector: Selects the slots that are shown in each frame.
- Intermediary generator: Creates intermediary items that are displayed before the slots pop in. (air by default)
- Show handler: Called when slot(s) are shown, for example to play a sound effect.
- Finish handler: Called when the animation is finished.
There are various built-in slot selectors available. In the following example, I chose horizontalSnake
and added a filter that looks for slots of ingredient #
. This means that only those slots will pop in, whereas all other slots will already be visible when the animation starts.
To start an animation, call Gui.playAnimation(animation)
.
Example: Playing animations on window open
There is no direct way to play animations on gui-open, because guis do not have awareness of "being opened". Instead, it is the window which is opened, and the gui is just embedded in the window. Additionally, it also doesn't make sense to share such guis between players, because only one of them will be the one opening the window. With a shared gui however, both would see the animation. This is why a separate gui instance per window is needed.
If you configure your window and gui builders in a certain way, you can ensure separate gui instances per player and trigger a call to playAnimation
automatically:
val windowBuilder: Window.Builder<*, *> = Window.builder()
.setUpperGui(PagedGui.itemsBuilder()
.setStructure(
"# # # # # # # # #",
"# x x x x x x x #",
"# x x x x x x x #",
"# x x x x x x x #",
"# # # < # > # # #",
)
.addIngredient('#', Item.simple(ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).hideTooltip(true)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('<', back)
.addIngredient('>', forward)
.setContent(allTheItemTypes)
.addModifier { it.playAnimation(animation) } // (1)!
// no .build()
)
windowBuilder.open(player) // builds gui and window for player, then opens window
// ... later:
windowBuilder.open(otherPlayer) // builds gui and window for otherPlayer, then opens window
- A modifier is a lambda that is run when the gui is built. Note that this code omits the
.build()
at the end of the gui builder chain. This causes the window builder to build a new gui for each window. Since we only usewindowBuilder.open
, building a window also means opening it. This means our modifier withplayAnimation
is called every time the window is opened.
Window.Builder<?, ?> windowBuilder = Window.builder()
.setUpperGui(PagedGui.itemsBuilder()
.setStructure(
"# # # # # # # # #",
"# x x x x x x x #",
"# x x x x x x x #",
"# x x x x x x x #",
"# # # < # > # # #"
)
.addIngredient('#', Item.simple(new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).hideTooltip(true)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('<', back)
.addIngredient('>', forward)
.setContent(allTheItemTypes)
.addModifier(gui -> gui.playAnimation(animation)) // (1)!
// no .build()
);
windowBuilder.open(player) // builds gui and window for player, then opens window
// ... later:
windowBuilder.open(otherPlayer) // builds gui and window for otherPlayer, then opens window
- A modifier is a lambda that is run when the gui is built. Note that this code omits the
.build()
at the end of the gui builder chain. This causes the window builder to build a new gui for each window. Since we only usewindowBuilder.open
, building a window also means opening it. This means our modifier withplayAnimation
is called every time the window is opened.
Example: Playing animations for page switching
The following example uses the page buttons to start playing the animation, but you can just as well start the animation in the page change handler of the PagedGui
.
val animation = Animation.builder()
.setSlotSelector(Animation::horizontalSnakeSlotSelector)
.filterTaggedSlots('x')
.setFreezing(false) // (1)!
.build()
val back: BoundItem = BoundItem.pagedBuilder()
.setItemProvider(ItemBuilder(Material.ARROW))
.addClickHandler { _, gui, _ ->
gui.cancelAnimation()
gui.page--
gui.playAnimation(animation)
}
.build()
val forward: BoundItem = BoundItem.pagedBuilder()
.setItemProvider(ItemBuilder(Material.ARROW))
.addClickHandler { _, gui, _ ->
gui.cancelAnimation()
gui.page++
gui.playAnimation(animation)
}
.build()
val allTheItemTypes: List<Item> = Material.entries
.filter { !it.isLegacy && it.isItem }
.map { Item.simple(ItemBuilder(it)) }
val gui: PagedGui<Item> = PagedGui.itemsBuilder()
.setStructure(
"# # # # # # # # #",
"# x x x x x x x #",
"# x x x x x x x #",
"# x x x x x x x #",
"# # # < # > # # #",
)
.addIngredient('#', Item.simple(ItemBuilder(Material.BLACK_STAINED_GLASS_PANE)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('<', back)
.addIngredient('>', forward)
.setContent(allTheItemTypes)
.build()
- By default, animations freeze the gui while they are running. This means that the player cannot interact with the gui until the animation is finished. Disabling freezing allows the user to switch to the next page before the animation finishes playing.
Animation animation = Animation.builder()
.setSlotSelector(Animation::horizontalSnakeSlotSelector)
.filterTaggedSlots('x')
.setFreezing(false) // (1)!
.build();
BoundItem back = BoundItem.pagedBuilder()
.setItemProvider(new ItemBuilder(Material.ARROW))
.addClickHandler((item, gui, click) -> {
gui.cancelAnimation();
gui.setPage(gui.getPage() - 1);
gui.playAnimation(animation);
})
.build();
BoundItem forward = BoundItem.pagedBuilder()
.setItemProvider(new ItemBuilder(Material.ARROW))
.addClickHandler((item, gui, click) -> {
gui.cancelAnimation();
gui.setPage(gui.getPage() + 1);
gui.playAnimation(animation);
})
.build();
List<Item> allTheItemTypes = Arrays.stream(Material.values())
.filter(m -> !m.isLegacy() && m.isItem())
.map(m -> Item.simple(new ItemBuilder(m)))
.toList();
PagedGui<Item> gui = PagedGui.itemsBuilder()
.setStructure(
"# # # # # # # # #",
"# x x x x x x x #",
"# x x x x x x x #",
"# x x x x x x x #",
"# # # < # > # # #"
)
.addIngredient('#', Item.simple(new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE)))
.addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)
.addIngredient('<', back)
.addIngredient('>', forward)
.setContent(allTheItemTypes)
.build();
- By default, animations freeze the gui while they are running. This means that the player cannot interact with the gui until the animation is finished. Disabling freezing allows the user to switch to the next page before the animation finishes playing.