Skip to content

Splitter

Header: winui/ui/panes/Splitter.hpp. Spec: winui/ui/panes/Splitter.hpp.

A draggable divider that resizes along one axis, between two Grid tracks or two panes.

Place it in a Grid track of its own with a track on either side: a column for a Vertical bar, a row for a Horizontal one. It then resizes those two tracks within their min and max (GridControl::resize_beside()). With an on_drag_delta() handler it resizes nothing and passes each delta to the handler, for a layout of the caller's own. While it has focus, the arrow keys along its axis resize too. It fills the available length along its axis and is thickness() across it. The declarative form is ui::Splitter.

Example:

using ui::operator""_fr;
using ui::operator""_dip;

window.set_content(ui::Grid{
    .columns   = {ui::minmax(80_dip, 1_fr), 6_dip, 1_fr},
    .min_width = 420.0f,
    .content   = {
        ui::Cell{.column = 0, .content = ui::Button{.text = "Left"}},
        ui::Cell{.column = 1, .content = ui::Splitter{}}, // drag or arrow keys move the two columns
        ui::Cell{.column = 2, .content = ui::Button{.text = "Right"}},
    },
});

// A layout of your own: the handler gets the deltas and the splitter resizes nothing.
ui::Property<float> top_height{200.0f};
auto splitter = std::make_unique<ui::SplitterControl>(ui::Orientation::Horizontal);
splitter->on_drag_delta([&](float dy) { top_height = std::max(40.0f, top_height.get() + dy); });

ui::Splitter

A draggable divider. Builds a ui::SplitterControl.

In a Grid track of its own between two others it resizes those two tracks, by drag or by the arrow keys, within their ui::minmax() limits. For a layout of your own, set .on_drag_delta. The splitter then resizes nothing and passes on each delta in DIPs. Add the delta to GridControl::column_size(), not to a neighbor's bounds(). Bounds are already shrunk by margins, so feeding them back collapses the track on the first drag.

Example:

using ui::operator""_dip;
using ui::operator""_fr;

window.set_content(ui::Grid{
    .columns = {ui::minmax(120_dip, 200_dip), ui::AutoSize, 1_fr},
    .content = {
        ui::Cell{.column = 0, .content = ui::Text{.text = "Folders"}},
        ui::Cell{.column = 1, .content = ui::Splitter{}},
        ui::Cell{.column = 2, .content = ui::Text{.text = "Messages"}},
    },
});

Every field, in the order a designated initializer names them:

ui::Splitter{
    .orientation, .thickness, .focusable, .keyboard_increment, .style, .margin, .width,
    .height, .min_width, .min_height, .max_width, .max_height, .horizontal_alignment,
    .vertical_alignment, .visible, .tool_tip, .enabled, .accessibility, .on_drag_delta,
    .on_pointer_entered, .on_pointer_exited, .on_got_focus, .on_lost_focus, .on_key_down,
    .on_wheel, .control
}

Fields, in declaration order (designators must follow it):

Field Type Default
orientation ui::Orientation ui::Orientation::Vertical The bar's own axis. Vertical (the default) is a vertical bar dragged left and right to resize columns. Horizontal resizes rows.
thickness ui::Binding<float> {6.0f} The bar's thickness across its axis, DIPs.
focusable bool true Whether the bar takes focus. With focus, the arrow keys along the drag axis resize as a drag does, four times as far with Shift. On by default.
keyboard_increment ui::Binding<float> {8.0f} One arrow-key step, DIPs; four times as far with Shift.
on_drag_delta ui::Callback<void(float)> The drag or key step along the drag axis since the previous call, DIPs. Positive is right or down. Setting it turns off the resizing of the Grid tracks beside the bar.
on_pointer_entered ui::Action The pointer moved onto the control.
on_pointer_exited ui::Action The pointer left the control.
on_got_focus ui::Action Keyboard focus arrived on the control, or on anything inside it, from outside it. A control with nothing focusable in it never gets focus, and never raises this.
on_lost_focus ui::Action Keyboard focus left the control and everything inside it.
on_key_down ui::Callback<ui::Handling(const ui::KeyEvent&)> A key pressed while focus is on the control or inside it, before the control's own handling; ui::Handled stops it there (Element::on_key_down()).
on_wheel ui::Callback<ui::Handling(const ui::PointerEvent&)> A wheel turn over the control, before the control scrolls; ui::Handled stops it there (Element::on_wheel()).
control ui::ControlOut<ui::Splitter> Out: the handle that receives the built control.

Plus the common fields style, margin, width, height, min_width, min_height, max_width, max_height, horizontal_alignment, vertical_alignment, visible, tool_tip, enabled, accessibility every spec carries (conventions).

build

std::unique_ptr<Element> build()&&

Consumes the spec and returns the built ui::SplitterControl; the caller owns it.

ui::SplitterControl

Derives from ui::Element.

A draggable divider that resizes along one axis, between two Grid tracks or two panes.

Place it in a Grid track of its own with a track on either side: a column for a Vertical bar, a row for a Horizontal one. It then resizes those two tracks within their min and max (GridControl::resize_beside()). With an on_drag_delta() handler it resizes nothing and passes each delta to the handler, for a layout of the caller's own. While it has focus, the arrow keys along its axis resize too. It fills the available length along its axis and is thickness() across it. The declarative form is ui::Splitter.

Example:

using ui::operator""_fr;
using ui::operator""_dip;

window.set_content(ui::Grid{
    .columns   = {ui::minmax(80_dip, 1_fr), 6_dip, 1_fr},
    .min_width = 420.0f,
    .content   = {
        ui::Cell{.column = 0, .content = ui::Button{.text = "Left"}},
        ui::Cell{.column = 1, .content = ui::Splitter{}}, // drag or arrow keys move the two columns
        ui::Cell{.column = 2, .content = ui::Button{.text = "Right"}},
    },
});

// A layout of your own: the handler gets the deltas and the splitter resizes nothing.
ui::Property<float> top_height{200.0f};
auto splitter = std::make_unique<ui::SplitterControl>(ui::Orientation::Horizontal);
splitter->on_drag_delta([&](float dy) { top_height = std::max(40.0f, top_height.get() + dy); });

SplitterControl

SplitterControl(Orientation orientation = Orientation::Vertical)

A 6 DIP bar along orientation, focusable.

orientation

Orientation orientation() const

The bar's axis, fixed at construction.

thickness

float thickness() const

The bar's size across its axis, in DIPs; 6 by default.

set_thickness

void set_thickness(float thickness)

Changes the thickness and lays the bar out again.

on_drag_delta

void on_drag_delta(Callback<void(float)> handler)

Fired while the pointer drags or an arrow key steps, with the movement in DIPs since the previous call. The axis follows orientation: x for Vertical, y for Horizontal. Setting a handler turns off the resizing of the Grid tracks beside the bar.

dragging

bool dragging() const

Whether a pointer drag is in progress: the button went down on the bar and is still held.

hovered

bool hovered() const

Whether the pointer is over the bar. The bar draws the accent separator while hovered or dragged.

set_focusable

void set_focusable(bool focusable)

Whether the bar takes focus; true by default. With focus, ArrowLeft/Right (Vertical) or ArrowUp/Down (Horizontal) resize by +/- keyboard_increment(), or four times that with Shift held. False leaves it out of the tab order.

focusable

bool focusable() const override

Whether the bar is a tab stop: set_focusable(), and only while enabled.

focused

bool focused() const

Whether the bar has keyboard focus.

keyboard_increment

float keyboard_increment() const

The arrow-key step, in DIPs; 8 by default. Set a different step, such as one grid unit, with set_keyboard_increment().

set_keyboard_increment

void set_keyboard_increment(float increment)

Sets the arrow-key step, in DIPs.

cursor_at

std::optional<CursorShape> cursor_at(Point) const override

The resize arrow along the drag axis: west-east for a vertical bar, north-south for a horizontal one.

automation_role

AutomationRole automation_role() const override

Reported to screen readers as a Thumb with no value, because the splitter reports relative movement and has no bounded value of its own.

measure_override

Size measure_override(Size available, const MeasureContext& context) const override

thickness() across the axis by the available length along it (thickness() when unbounded).

render_override

void render_override(Renderer& renderer, const RenderContext& context) const override

A 1 pixel divider at rest, and a 2 pixel accent separator while hovered or dragged.

pointer_override

bool pointer_override(const PointerEvent& e) override

Starts a drag on a press over the bar, reports each move's delta and ends on release.

enabled_changed

void enabled_changed() override

is_enabled() flipped, through set_enabled() here or on an ancestor, or a move to another parent. Drop any press or hover state. The element is redrawn either way. No-op by default.

focus_changed

void focus_changed(bool) override

Called by the Window as focus arrives or leaves; redraws.

key_override

bool key_override(const KeyEvent& e) override

The arrow keys along the axis step by keyboard_increment(), four times as far with Shift.

Tests

  • tests/test_splitter.cpp