ml::docs | Your First App Manifests Events Messaging Plugins Scenes Resources
See all tutorials →
Intermediate 6 sections ~13 min C++17/20

Lists & Data Display

Tables, editable lists, drag-to-reorder, thumbnail grids and data-driven panels — chosen by what the user is allowed to do to the collection.

Introduction

Five Ways to Show a Collection

Five controls for showing collections. They differ by what the user may do to the collection.

ControlUser can
TableRead rows and columns
ThumbnailPickerPick one of a grid of images
EditableListAdd, remove and edit rows
DragListReorder by dragging
DynamicPanelNothing directly — it rebuilds a panel from data
DragList is not a widget but a helper. It computes drag and drop against geometry you describe, so any list you already have can gain reordering without being replaced.
1Read-only

Table

Table displays rows under fixed columns.

ml::Table roster;
roster.addColumn("Student", 220.f);
roster.addColumn("ID",      120.f);
roster.addColumn("Status");          // 0 width = share the remainder

roster.addRow({ "Amelia Adamczewski", "10734279", "Joined" });
roster.addRow({ "Samir Chaudhry",     "10668854", "Waiting" });
MethodEffect
addColumn(header, width)Append a column. Width 0 shares leftover space.
addRow(cells)Append a row. Cell count should match the columns.
removeRow(index)Remove by position; returns whether it existed.
clear()Drop the rows, keep the columns.
reset()Drop rows and columns.
clear() and reset() differ in a way that bites: refreshing data wants clear(). reset() removes your columns too, and the next addRow then has nowhere to put its cells.
2Editing

EditableList

EditableList is a list the user maintains — quiz options, tags, roster entries.

ml::EditableList options;
options.setPlaceholder("Answer option");
options.setShowAddButton(true);
options.setAddButtonLabel("Add option");
options.setShowActions(true);        // per-row delete
options.setMinRows(2);               // a question needs at least two
options.setMaxRows(6);
MethodEffect
setPlaceholder(text)Ghost text in an empty row.
setShowAddButton(bool) / setAddButtonLabel(s)The add control.
setShowActions(bool)Per-row delete button.
setMinRows(n) / setMaxRows(n)Bounds the user cannot cross.
setSelectionMode(mode)Whether and how rows select.
setContentFactory(factory)Supply your own row content instead of a text field.
setMinRows is a real constraint, not a hint — the delete button disappears at the minimum. Use it to encode rules like "a multiple-choice question needs two answers" rather than validating after the fact.
3Dragging

DragList

DragList adds drag-to-reorder to a list you have already drawn. You tell it the geometry; it tells you what moved.

ml::DragList drag;
drag.onReorder([&](int from, int to){ std::swap(items[from], items[to]); });

// each frame, describe the rows and feed it the pointer
drag.setGeometry(x0, x1, rowTop, int(items.size()), rowHeight);
drag.update(localX, localY, mouseDown, /*live*/ true);

// draw in the order it reports, so rows follow the finger
for (int i : drag.displayOrder(int(items.size())))
    drawRow(items[i]);
MethodPurpose
setGeometry(...)Where the rows are, how many, how tall.
update(x, y, down, live)Feed pointer state; returns whether it consumed the input.
displayOrder(count)Indices in the order to draw right now, mid-drag.
onReorder(cb)The move is committed — apply it to your data.
isDragging(), dragIndex(), dropIndex()Current state, for highlighting.
Reorder your own data in onReorder only. displayOrder is a view of the in-progress drag — mutating your vector while the drag is live makes the indices disagree with what is on screen.
4Images

ThumbnailPicker

ThumbnailPicker is a grid of images with captions.

ml::ThumbnailPicker picker;
picker.setColumns(4);
picker.setTileSize(120.f, 90.f);
picker.addThumbnail(slideImage, "Slide 1");
picker.onSelectionChanged([&](int i){ showSlide(i); });
MethodEffect
addThumbnail(image, caption)Append a tile.
setColumns(n)Grid width.
setTileSize(w, h)Tile dimensions.
setSelectedIndex(i)Select programmatically.
onSelectionChanged(cb)The user picked a tile.
clear()Remove every tile.
5Rebuilding

DynamicPanel

DynamicPanel rebuilds a panel's contents from a callback whenever the underlying data changes, pooling and reusing the child widgets rather than destroying them.

ml::DynamicPanel panel;
panel.onBuild([&](ml::DynamicPanel::Builder& b){
    for (const auto& q : questions)
        b.button(q.title, [&]{ openQuestion(q.id); });
});

questions.push_back(newQuestion);
panel.invalidate();      // rebuild on the next frame
MethodEffect
onBuild(fn)Describe the contents; called on rebuild.
invalidate()Mark dirty — rebuild happens once, next frame.
isRebuildPending()Whether a rebuild is queued.
doRebuild()Rebuild immediately rather than waiting.
setButtonStyler(fn)Style generated buttons consistently.
Call invalidate() freely — several calls in one frame produce one rebuild. That is the point: you signal "the data changed" without having to work out whether a rebuild is already scheduled.