Content Builder

Content Builder

ContentBuilder is the layout DSL most Lotus menus start with. It is the easiest way to describe what goes where without dropping down to manual slot mutation.

Content content = Content.builder(Capacity.ofRows(3))
    .fillBorder(Button.of(filler()))
    .set(1, 4, Button.clickable(icon(), buyAction))
    .build();

The Core Operations

Place one button

.set(Slot.of(13), button)
.set(1, 4, button)

The (row, column) overload is usually the most readable.

Place many buttons

.buttons(Map.of(
    Slot.of(10), leftButton,
    Slot.of(13), centerButton,
    Slot.of(16), rightButton
))

Fill regions

.fill(mask, button)
.fillAll(button)
.fillBorder(button)

fill(...) becomes especially powerful with SlotMask:

.fill(SlotMask.range(size, Slot.of(9), Slot.of(17)), divider)
.fill(SlotMask.full(size).excluding(Slot.of(13)), filler)

Rows and columns are zero-based. set(1, 4, button) means the second row and the fifth column.

Drawing Lines

You can draw until the edge:

.draw(Slot.at(1, 1, size), Direction.RIGHT, divider)

Or between two explicit points:

.draw(Slot.at(1, 1, size), Slot.at(1, 7, size), Direction.RIGHT, divider)

This is useful for separators, outlines, diagonal effects, and guides.

apply(...) — The Escape Hatch

If the fluent methods are not enough, apply(...) gives you the raw Content object:

.apply(content -> {
    for (int column = 0; column < 9; column++) {
        content.set(Slot.at(0, column, size), topRowButton(column));
    }
})

That lets you mix a readable DSL with custom loops when needed.

A Complete Beginner Layout

public Content content(MenuView<?, ?> view) {
    Capacity size = view.capacity();

    return Content.builder(size)
        .fillBorder(Button.of(filler()))
        .draw(
            Slot.at(1, 1, size),
            Slot.at(1, 7, size),
            Direction.RIGHT,
            Button.of(divider())
        )
        .set(2, 3, Button.clickable(cancelIcon(), (menuView, event) -> {
            menuView.viewer().closeInventory();
        }))
        .set(2, 5, Button.clickable(confirmIcon(), (menuView, event) -> {
            commit(menuView.viewer());
            menuView.viewer().closeInventory();
        }))
        .build();
}

What Happens After .build()

The result is still a mutable Content object. That means later you can do:

view.content().set(Slot.of(13), replacement);
view.content().update(Slot.of(13), current -> current.withItem(highlightedIcon()));
view.content().remove(Slot.of(13));

Lotus repaints automatically after button dispatch.

If your mutation happens outside a click handler, call view.refresh() to rebuild the menu content for that viewer.

When To Prefer The Builder

Reach for ContentBuilder when:

  • you are writing the initial menu layout
  • the shape of the menu matters to readability
  • you want future you to understand the menu at a glance

Next: Data Registry.