Intermediate
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.
| Control | User can |
|---|---|
Table | Read rows and columns |
ThumbnailPicker | Pick one of a grid of images |
EditableList | Add, remove and edit rows |
DragList | Reorder by dragging |
DynamicPanel | Nothing 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" });
| Method | Effect |
|---|---|
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);
| Method | Effect |
|---|---|
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]);
| Method | Purpose |
|---|---|
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); });
| Method | Effect |
|---|---|
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
| Method | Effect |
|---|---|
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.