Grid¶
Header: winui/ui/panels/Grid.hpp. Spec: winui/ui/panels/Grid.hpp.
A two-dimensional layout panel: children placed in cells of row and column tracks, each track Auto, a fraction of the remaining space, or fixed DIPs.
Measure sizes Dip tracks first, then Auto tracks from their single-span children, then splits what is left across the Fraction tracks by weight. Arrange sizes the tracks again for the final rect, so Fraction tracks share the room the Grid is given. Every track stays within its min and max (ui::minmax()). On an unbounded axis a Fraction track behaves like Auto. An axis with no tracks has one implicit Fraction track, so a Grid without definitions layers every child in one cell. A child spanning several Auto tracks does not grow them. A border and padding inset the cells. Tracks can be resized at runtime with set_row_height() and set_column_width(). A Splitter in a track of its own between two others resizes them itself.
Example:
// Declarative: header, a sidebar beside the content, footer.
using ui::AutoSize;
using namespace ui::literals;
window.set_content(ui::Grid{
.columns = {200_dip, 1_fr},
.rows = {ui::AutoSize, 1_fr, ui::AutoSize},
.column_spacing = 8.0f,
.content = {
ui::Cell{.row = 0, .column = 0, .column_span = 2, .content = ui::Text{.text = "Header"}},
ui::Cell{.row = 1, .column = 0, .content = ui::Text{.text = "Sidebar"}},
ui::Cell{.row = 1, .column = 1, .content = ui::Text{.text = "Content"}},
ui::Cell{.row = 2, .column = 0, .column_span = 2, .content = ui::Text{.text = "Footer"}},
},
});
// Imperative:
auto grid = std::make_unique<ui::GridControl>();
grid->add_column(ui::GridLength::auto_());
grid->add_column(ui::GridLength::fraction());
grid->add_row(ui::GridLength::auto_());
grid->set_column_spacing(12.0f);
grid->add(std::make_unique<ui::TextControl>("Name:"), 0, 0);
grid->add(std::make_unique<ui::TextBoxControl>(), 0, 1);
window.set_content(std::move(grid));
ui::Grid¶
A two-dimensional track layout. Builds a ui::GridControl.
Tracks are sized with _dip (fixed), _fr (a fraction of the remaining space) and AutoSize (the content), and ui::minmax() keeps one between two sizes. A track list left empty is one fraction track. A ui::Splitter in a track of its own between two others resizes them. To resize tracks from code, call set_column_width() or set_row_height() on .control.
A deeply nested tree compiles faster with MSVC when a subtree is built into a named ui::Spec first and placed from there.
Example:
using namespace ui::literals;
struct Model
{
ui::Property<std::string> name;
ui::Property<std::string> email;
};
Model model;
window.set_content(ui::Grid{
.columns = {120_dip, 1_fr},
.rows = {ui::AutoSize, ui::AutoSize},
.row_spacing = 8,
.column_spacing = 12,
.content = {
ui::Cell{.row = 0, .column = 0, .content = ui::Text{.text = "Name"}},
ui::Cell{.row = 0, .column = 1, .content = ui::TextBox{.text = model.name}},
ui::Cell{.row = 1, .column = 0, .content = ui::Text{.text = "Email"}},
ui::Cell{.row = 1, .column = 1, .content = ui::TextBox{.text = model.email}},
},
});
Every field, in the order a designated initializer names them:
ui::Grid{
.columns, .rows, .row_spacing, .column_spacing, .background, .border_brush,
.border_thickness, .corner_radius, .padding, .style, .margin, .width, .height, .min_width,
.min_height, .max_width, .max_height, .horizontal_alignment, .vertical_alignment, .visible,
.tool_tip, .enabled, .accessibility, .on_pointer_entered, .on_pointer_exited,
.on_got_focus, .on_lost_focus, .on_key_down, .on_wheel, .control, .content
}
Fields, in declaration order (designators must follow it):
| Field | Type | Default | |
|---|---|---|---|
columns |
std::vector<ui::GridLength> |
One GridLength per column. | |
rows |
std::vector<ui::GridLength> |
One GridLength per row. | |
row_spacing |
ui::Binding<float> |
{0.0f} |
Gap between rows, DIPs. |
column_spacing |
ui::Binding<float> |
{0.0f} |
Gap between columns, DIPs. |
background |
ui::Binding<ui::Paint> |
Background behind the cells: a Color, or a ThemeColor read from the theme; empty (the default) draws nothing. | |
border_brush |
ui::Binding<ui::Paint> |
Border color, a Color or a ThemeColor; empty (the default) draws no border. | |
border_thickness |
ui::Binding<ui::Thickness> |
{ui::Thickness{}} |
Border width per edge, DIPs; it insets the children like padding. |
corner_radius |
ui::Binding<float> |
{0.0f} |
Corner radius of the background and the border, DIPs. |
padding |
ui::Binding<ui::Thickness> |
{ui::Thickness{}} |
Room between the border and the children, DIPs. |
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::Grid> |
Out: the handle that receives the built control. | |
content |
ui::CellList |
The placed children; see ui::Cell. |
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¶
Consumes the spec and returns the built ui::GridControl; the caller owns it.
ui::Cell¶
One placed child. row/column name tracks declared in .rows/.columns, and either may be bound to move the child as state changes.
Written inline in a ui::Grid's .content list; see ui::Grid for a full example.
Example:
ui::Property<std::size_t> column{0};
ui::Cell moving{.row = 1, .column = column, .column_span = 2, .content = ui::Text{.text = "Here"}};
Every field, in the order a designated initializer names them:
Fields, in declaration order (designators must follow it):
| Field | Type | Default | |
|---|---|---|---|
row |
ui::Binding<std::size_t> |
{std::size_t{0}} |
The row index. |
column |
ui::Binding<std::size_t> |
{std::size_t{0}} |
The column index. |
row_span |
std::size_t |
1 |
Rows covered. |
column_span |
std::size_t |
1 |
Columns covered. |
content |
ui::Spec |
The placed child. |
ui::CellList¶
A move-only, already-built list of placed children: the Grid analog of SpecList.
Callers write it as a braced list of ui::Cell{...}; build one in a loop with add().
Example:
std::vector<std::string> names = {"Ada", "Grace", "Linus"};
ui::CellList cells;
for (std::size_t i = 0; i < names.size(); ++i)
{
cells.add(ui::Cell{.row = i, .content = ui::Text{.text = names[i]}});
}
window.set_content(ui::Grid{.rows = std::vector(names.size(), ui::AutoSize),
.content = std::move(cells)});
Every field, in the order a designated initializer names them:
Fields, in declaration order (designators must follow it):
| Field | Type | Default | |
|---|---|---|---|
cells |
std::vector<Placed> |
The placed children, in declaration order. |
operator=¶
operator=¶
add¶
Builds cell's content and appends it, as one more entry of the braced list would.
struct Placed¶
One built child with its placement; the node is already owned by the list.
| Field | Type | Default | |
|---|---|---|---|
row |
Binding<std::size_t> |
Row index, possibly bound. | |
column |
Binding<std::size_t> |
Column index, possibly bound. | |
row_span |
std::size_t |
Rows covered. | |
column_span |
std::size_t |
Columns covered. | |
node |
std::unique_ptr<Element> |
The built child. |
ui::GridControl¶
Derives from ui::Element.
A two-dimensional layout panel: children placed in cells of row and column tracks, each track Auto, a fraction of the remaining space, or fixed DIPs.
Measure sizes Dip tracks first, then Auto tracks from their single-span children, then splits what is left across the Fraction tracks by weight. Arrange sizes the tracks again for the final rect, so Fraction tracks share the room the Grid is given. Every track stays within its min and max (ui::minmax()). On an unbounded axis a Fraction track behaves like Auto. An axis with no tracks has one implicit Fraction track, so a Grid without definitions layers every child in one cell. A child spanning several Auto tracks does not grow them. A border and padding inset the cells. Tracks can be resized at runtime with set_row_height() and set_column_width(). A Splitter in a track of its own between two others resizes them itself.
Example:
// Declarative: header, a sidebar beside the content, footer.
using ui::AutoSize;
using namespace ui::literals;
window.set_content(ui::Grid{
.columns = {200_dip, 1_fr},
.rows = {ui::AutoSize, 1_fr, ui::AutoSize},
.column_spacing = 8.0f,
.content = {
ui::Cell{.row = 0, .column = 0, .column_span = 2, .content = ui::Text{.text = "Header"}},
ui::Cell{.row = 1, .column = 0, .content = ui::Text{.text = "Sidebar"}},
ui::Cell{.row = 1, .column = 1, .content = ui::Text{.text = "Content"}},
ui::Cell{.row = 2, .column = 0, .column_span = 2, .content = ui::Text{.text = "Footer"}},
},
});
// Imperative:
auto grid = std::make_unique<ui::GridControl>();
grid->add_column(ui::GridLength::auto_());
grid->add_column(ui::GridLength::fraction());
grid->add_row(ui::GridLength::auto_());
grid->set_column_spacing(12.0f);
grid->add(std::make_unique<ui::TextControl>("Name:"), 0, 0);
grid->add(std::make_unique<ui::TextBoxControl>(), 0, 1);
window.set_content(std::move(grid));
GridControl¶
An empty grid with no track definitions (one implicit Fraction cell).
add_row¶
Appends a row track and updates the layout.
add_column¶
Appends a column track and updates the layout.
row_count¶
How many rows were added (0 means one implicit Fraction row).
column_count¶
How many columns were added (0 means one implicit Fraction column).
row¶
Row track index's definition; throws std::out_of_range past row_count().
column¶
Column track index's definition; throws std::out_of_range past column_count().
set_row_height¶
Switches a track to a fixed size in DIPs at runtime, keeping its min and max. A custom Splitter handler drives it from on_drag_delta (ui/panes/Splitter.hpp). An index past the current track count is ignored, so a drag handler need not guard against a grid with fewer tracks.
set_column_width¶
The column counterpart of set_row_height().
resize_beside¶
Moves delta DIPs from the track after splitter's to the track before it (a negative delta the other way), within both tracks' min and max, so the two keep their total. splitter must be a child of this grid in a track of its own, span 1, with a track on either side: the columns for a Vertical bar, the rows for a Horizontal one. Two Fraction tracks trade weight; any other track becomes a Dip track at its new size. Returns the delta applied after the limits, or nullopt (and nothing changes) when splitter is not placed that way. SplitterControl calls this.
row_size¶
A track's current size in DIPs: the literal value for a Dip track (so it reflects a set_row_height()/set_column_width() call immediately, before any layout pass has run on it), otherwise the size the last layout resolved for it. 0 before the first layout, or for an out-of-range index.
A Splitter or ResizeGripper drag handler must add its delta to this, not to an occupant's bounds(). A child's bounds are its inner rect, already shrunk by its own margin and focus-ring outset (Element::arrange). Feeding child->bounds().width + delta into set_column_width() takes that band off again on every drag event, so the track collapses on the first one and never grows back.
column_size¶
The column counterpart of row_size().
add¶
template <typename T>
T* add(std::unique_ptr<T> child, std::size_t row, std::size_t column, std::size_t row_span = 1, std::size_t column_span = 1)
Takes ownership of a child and returns a non-owning observer to it, as StackControl::add() does. row/column index the tracks added via add_row()/add_column(); an index past the last track places the child in the last one, and a span stops at the last track. Spans below 1 are raised to 1.
add¶
template <SpecLike S>
Element* add(S&& spec, std::size_t row, std::size_t column, std::size_t row_span = 1, std::size_t column_span = 1)
The declarative overload: builds spec (or takes the node a ui::Spec holds) and adds it as add() does a control. Returns the built node as an Element, or nullptr for an empty ui::Spec, which adds nothing; a spec's .control field gives a typed observer.
orphan¶
Takes child out of the grid alive and hands it back, for adding somewhere else. Focus inside it is cleared. Null when it is not one of this grid's children.
set_cell¶
void set_cell(const Element* child, std::size_t row, std::size_t column, std::size_t row_span = 1, std::size_t column_span = 1)
Moves a child to another cell and span. An element that is not a child of this grid is ignored.
set_row¶
Moves child to row, keeping its column and spans.
set_column¶
Moves child to column, keeping its row and spans.
background¶
The color filled behind the children; nullopt (the default) draws nothing. A ThemeColor is a theme resource, re-read whenever it is drawn (ui/BackgroundBrush.hpp).
background_resource¶
The background as a theme resource, or nullptr when a fixed color (or nothing) is set.
set_background¶
Sets a fixed background color (nullopt for none); redraws.
set_background¶
Sets the background to a theme resource; redraws.
background_transition¶
With a transition set, a later change of what the background draws (a new color, or a resource under a new theme) blends from the color on screen (Animation.hpp's BrushTransition). nullopt (the default) changes it at once.
set_background_transition¶
Sets or clears the background transition.
border_brush¶
The outline color; nullopt (the default) draws no border even with a non-zero border_thickness(). Nullopt as well while a ThemeColor is set.
border_brush_resource¶
The theme resource the border is drawn from, or null.
set_border_brush¶
A fixed outline color; replaces any resource. Redraws.
set_border_brush¶
An outline color read from the theme each time it is drawn; replaces any fixed color. Redraws.
border_thickness¶
The border width in DIPs per side (default zero); it insets the children like padding.
set_border_thickness¶
Sets the border width; updates the layout and redraws.
corner_radius¶
The corner radius in DIPs, uniform for all four corners (default 0, square). It rounds the background and the border; the children are not clipped.
set_corner_radius¶
Sets the corner radius; redraws.
padding¶
The room in DIPs between the border and the children (default zero).
set_padding¶
Sets the padding and updates the layout.
row_spacing¶
The gap between adjacent rows, DIPs; 0 by default.
set_row_spacing¶
Sets the row gap and updates the layout.
column_spacing¶
The gap between adjacent columns, DIPs; 0 by default.
set_column_spacing¶
Sets the column gap and updates the layout.
parallel_measure_safe¶
True: measuring touches only the grid and its children.
for_each_child¶
Lists the children in the order they were added.
measure_override¶
Implement these instead of measure() and arrange(). The base class has already applied margin, size limits and alignment by the time these run. measure_override() is pure (every concrete Element must size itself); arrange_override() defaults to a no-op since most leaves need nothing beyond having bounds() set, which the base already did.
arrange_override¶
Position children within the rect arrange() settled on (also in bounds(), window DIPs). Default: nothing, for a leaf.
render_override¶
What the control draws, in layout coordinates; render() has already applied the visual layer. A container draws its children through their render(), never their render_override().
tick_override¶
Advances the background transition, then ticks every child.
struct GridLength¶
The size of one Grid row or column: Auto, a fraction of the remaining space, or fixed DIPs, optionally kept between a smallest and a largest size. Defaults to one fraction.
In a spec, the literals ui::AutoSize, 1_fr and 48_dip are shorthand for auto_(), fraction() and dip(), and ui::minmax(200_dip, 1_fr) adds the limits.
Example:
auto grid = std::make_unique<ui::GridControl>();
grid->add_column(ui::GridLength::dip(200.0f)); // a 200-DIP sidebar
grid->add_column(ui::GridLength::fraction()); // the rest
grid->add_row(ui::GridLength::auto_()); // a header sized to its content
grid->add_row(ui::GridLength::fraction(2.0f)); // twice the share of a fraction() row
grid->add_column(ui::minmax(ui::GridLength::dip(120.0f), ui::GridLength::fraction())); // never under 120
unit¶
How value is read.
value¶
The Fraction weight or the Dip size in DIPs; ignored for Auto.
min¶
The smallest the track gets, DIPs; 0 by default.
max¶
The largest the track gets, DIPs; ui::Unbounded (no limit) by default.
auto_¶
An Auto track, sized to its content.
fraction¶
A fraction of the remaining space, weighted by weight: a fraction(2) track gets twice what a fraction(1) track does.
dip¶
A fixed track of dips DIPs.
Enumerations¶
GridUnit¶
How a Grid track's GridLength::value is read.
| Value | |
|---|---|
Auto |
sized to the largest single-span child in the track; value is ignored |
Fraction |
a weighted share of the space left after Dip and Auto tracks; value is the weight |
Dip |
a fixed size; value is DIPs |
Functions¶
minmax¶
A Grid track kept between two sizes, as CSS's minmax(): ui::minmax(200_dip, 1_fr) is a fraction of the remaining space that never gets narrower than 200 DIPs, and ui::minmax(ui::AutoSize, 300_dip) sizes to its content up to 300 DIPs.
smallest is a Dip length, which sets the lower limit, or Auto, which sets none. largest decides how the track is sized. When both are Dip lengths, the track takes a fraction of the remaining space, kept between the two. The limits also hold while a Splitter resizes the track.
Example:
using namespace ui::literals;
ui::Grid{.columns = {ui::minmax(160_dip, 1_fr), 6_dip, 2_fr}, .content = {/* ... */}};
operator""_dip¶
A fixed track size in DIPs: 200_dip, 12.5_dip.
operator""_dip¶
Integer form of _dip.
operator""_fr¶
A fraction of the remaining space: a weighted share of what is left after fixed and auto tracks, so a 2_fr track gets twice what a 1_fr one does. 0.5_fr.
operator""_fr¶
Integer form of _fr.
operator""_dip¶
A fixed track size in DIPs: 200_dip, 12.5_dip.
operator""_dip¶
Integer form of _dip.
operator""_fr¶
A fraction of the remaining space: a weighted share of what is left after fixed and auto tracks, so a 2_fr track gets twice what a 1_fr one does. 0.5_fr.
operator""_fr¶
Integer form of _fr.
Constants¶
| Name | Type | Value | |
|---|---|---|---|
AutoSize |
GridLength |
GridLength::auto_() |
A track sized to its content. |
Tests¶
tests/test_grid.cpp