Panel Controls
TabbedPanel, SplitPanel, and ScrollPane — container components that own and manage their children, with automatic resizing, draggable dividers, and scrolling.
· Item A
· Item B
· Item C
Ownership and Sizing
All three panel types take ownership of their content components. You pass a std::unique_ptr to addTab() or addPane(). If the content type has a setSize() method, the panel calls it automatically whenever the available area changes. ScrollPane uses a non-owning pointer model — you call addComponent() on it like a scene.
| Class | Layout | Content ownership |
|---|---|---|
ml::TabbedPanel | One panel visible at a time, selected by tabs | Owns via unique_ptr |
ml::SplitPanel | N panes separated by draggable dividers | Owns via unique_ptr |
ml::ScrollPane | Clipped scrollable area with a scroll bar | Non-owning pointer |
TabbedPanel
Displays one content panel at a time. Tabs can be placed on any edge (TOP, BOTTOM, LEFT, RIGHT). Hidden panels remain alive in memory so their state is preserved between tab switches. addTab() is a template that returns a reference to the content object, making inline configuration clean.
ml::TabbedPanel tabs;
tabs.setSize({600.f, 400.f});
tabs.setPosition({40.f, 100.f});
// addTab() takes ownership; returns a reference to the content
auto& notes = tabs.addTab("Notes", std::make_unique<ml::TextArea>());
auto& settings = tabs.addTab("Settings", std::make_unique<ml::List>());
// Tab with an icon texture
auto& log = tabs.addTab("Log", std::make_unique<ml::TextArea>(), &logIcon);
// Closeable tab (shows a × button)
auto& debug = tabs.addTab("Debug", std::make_unique<ml::TextArea>(),
nullptr, /*closeable=*/true);
addComponent(tabs);
tabs.onTabChanged([](std::size_t index, const std::string& label) {
std::cout << "Active: " << label << "\n";
});
tabs.onTabClosed([](std::size_t index, const std::string& label) {
std::cout << "Closed: " << label << "\n";
});
tabs.selectTab(1); // show "Settings"
int active = tabs.activeTab(); // index, or -1 if empty
std::size_t count = tabs.tabCount();
// Remove a tab (content destroyed on next update frame)
tabs.removeTab(2);
// contentSize() returns the drawable area minus the tab strip.
// Components with setSize() are auto-resized, but you can query
// the size first if you need it during construction:
sf::Vector2f cs = tabs.contentSize();
auto myWidget = std::make_unique<MyWidget>(cs);
tabs.addTab("Widget", std::move(myWidget));
SplitPanel
Stacks N panes separated by draggable dividers. The split can be horizontal (default) or vertical. Components with a setSize() method are resized automatically when a divider is dragged or the panel is resized. Per-pane min/max sizes prevent panes from collapsing.
ml::SplitPanel split;
split.setSize({800.f, 600.f});
split.setPosition({0.f, 30.f});
// addPane() takes ownership; second arg is the initial size along the axis
auto& sidebar = split.addPane(std::make_unique<ml::List>(), 220.f);
auto& editor = split.addPane(std::make_unique<ml::TextArea>()); // fills remaining
// Set size constraints
split.setPaneMinSize(0, 120.f); // sidebar never smaller than 120 px
split.setPaneMinSize(1, 200.f); // editor never smaller than 200 px
split.onDividerMoved([](std::size_t divIdx, float pos) {
std::cout << "Divider " << divIdx << " moved to " << pos << " px\n";
});
addComponent(split);
ml::SplitPanel vsplit(ml::SplitPanelSettings::Orientation::VERTICAL);
vsplit.setSize({800.f, 600.f});
vsplit.setPosition({0.f, 0.f});
vsplit.addPane(std::make_unique<ml::Rectangle>(), 40.f); // header
vsplit.addPane(std::make_unique<ml::TextArea>()); // content
vsplit.addPane(std::make_unique<ml::Rectangle>(), 28.f); // status bar
vsplit.setPaneMinSize(0, 40.f);
vsplit.setPaneMinSize(2, 28.f);
addComponent(vsplit);
split.setDividerPosition(0, 300.f); // move divider 0 to 300 px
std::size_t n = split.paneCount(); // 2
ScrollPane
ScrollPane clips its children into a viewport and draws a vertical scroll bar. Children are stacked vertically. Unlike TabbedPanel and SplitPanel, it holds non-owning pointers — you add children via addComponent() and register them with the scene separately if they need their own events.
ml::ScrollPane pane(400.f, 300.f); // width, height
pane.setPosition({50.f, 100.f});
pane.addComponent(itemA);
pane.addComponent(itemB);
pane.addComponent(itemC);
// Styling
pane.setBackgroundColor(sf::Color(20, 20, 30));
pane.setScrollBarColor(sf::Color(100, 100, 140, 200));
pane.setScrollBarWidth(8.f);
addComponent(pane);
// Programmatic scroll
float y = pane.getScrollOffsetY();
pane.setScrollOffsetY(150.f); // clamped automatically
// Override computed content height (useful when content is
// rendered externally rather than as child components)
pane.setContentHeight(2000.f);
// Resize the viewport
pane.setSize(600.f, 400.f);
// When ScrollPane is a private member of another component
// (not registered with addComponent()), call embed() to remove
// all its event subscriptions so it cannot steal focus.
// draw(), setPosition(), setSize(), and setScrollOffsetY() still work.
class MyWidget : public ml::Component<>
{
ml::ScrollPane _pane{400.f, 300.f};
public:
MyWidget() {
_pane.embed(); // silence BEFORE registering any other events
}
};