Skip to content

Canvas

Header: winui/ui/panels/Canvas.hpp. Spec: winui/ui/panels/Canvas.hpp.

Children placed at absolute left/top offsets (DIPs) from its top-left corner, drawn in z-index order.

Every child is measured with unbounded room and arranged at its own desired size. The Canvas itself asks for no room at all. It takes the size its width/height or its slot give it. It does not clip, so a child placed past its edge still draws there. Children draw lowest z-index first, ties in the order added, and the topmost child gets the wheel first. The background comes from Panel::set_background().

Example:

auto canvas = std::make_unique<ui::CanvasControl>();
canvas->set_width(140.0f);
canvas->set_height(140.0f);
canvas->set_background(ui::Color::from_rgba(128, 128, 128));

auto square = std::make_unique<ui::RectangleControl>();
square->set_width(40.0f);
square->set_height(40.0f);
square->set_fill(ui::Color::from_rgba(255, 0, 0));
ui::RectangleControl* red = canvas->add(std::move(square), 20.0f, 30.0f);
canvas->set_z_index(red, 5); // drawn above every child with a lower z-index
canvas->set_left(red, 60.0f); // moves it and updates the layout
window.set_content(std::move(canvas));

// Declaratively, with bindable placements:
ui::Property<float> left{20.0f};
window.set_content(ui::Canvas{
    .width    = 140.0f,
    .height   = 140.0f,
    .content  = {
        ui::CanvasItem{.left    = left,
         .top     = 30.0f,
         .content = ui::Rectangle{.fill = ui::colors::Black, .width = 40.0f, .height = 40.0f}},
        ui::CanvasItem{.left = 60.0f, .top = 60.0f, .z_index = 1, .content = ui::Text{.text = "On top"}},
    },
});

ui::Canvas

Children at absolute offsets. Builds a ui::CanvasControl.

A canvas does not size to its children; give it a width/height (or a stretching slot).

Example:

ui::Property<float> x{0.0f};

window.set_content(ui::VerticalStack{.spacing = 8, .content = {
    ui::Canvas{
        .background = ui::Color::from_rgba(200, 200, 200),
        .width      = 140.0f,
        .height     = 140.0f,
        .content    = {
            ui::CanvasItem{.left = x, .top = 10.0f, .z_index = 2,
             .content = ui::Border{.background = ui::colors::Black, .width = 40.0f, .height = 40.0f}},
            ui::CanvasItem{.left = 20.0f, .top = 20.0f, .z_index = 1,
             .content = ui::Border{.background = ui::colors::White, .width = 40.0f, .height = 40.0f}},
        },
    },
    ui::Button{.text = "Move", .on_click = [&] { x += 10.0f; }},
}});

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

ui::Canvas{
    .background, .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
background ui::Binding<ui::Paint> Background: a Color, or a ThemeColor read from the theme; empty (the default) draws none.
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::Canvas> Out: the handle that receives the built control.
content ui::CanvasItemList The positioned children; see ui::CanvasItem.

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::CanvasControl; the caller owns it.

ui::CanvasItem

One child of a Canvas with its left and top offsets and z-index, each bindable. Written inline in a ui::Canvas's .content; see ui::Canvas for an example.

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

ui::CanvasItem{
    .left, .top, .z_index, .content
}

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

Field Type Default
left ui::Binding<float> {0.0f} Offset from the canvas's left, DIPs.
top ui::Binding<float> {0.0f} Offset from the canvas's top, DIPs.
z_index ui::Binding<int> {0} Higher draws on top.
content ui::Spec The placed child.

ui::CanvasControl

Derives from ui::Panel.

Children placed at absolute left/top offsets (DIPs) from its top-left corner, drawn in z-index order.

Every child is measured with unbounded room and arranged at its own desired size. The Canvas itself asks for no room at all. It takes the size its width/height or its slot give it. It does not clip, so a child placed past its edge still draws there. Children draw lowest z-index first, ties in the order added, and the topmost child gets the wheel first. The background comes from Panel::set_background().

Example:

auto canvas = std::make_unique<ui::CanvasControl>();
canvas->set_width(140.0f);
canvas->set_height(140.0f);
canvas->set_background(ui::Color::from_rgba(128, 128, 128));

auto square = std::make_unique<ui::RectangleControl>();
square->set_width(40.0f);
square->set_height(40.0f);
square->set_fill(ui::Color::from_rgba(255, 0, 0));
ui::RectangleControl* red = canvas->add(std::move(square), 20.0f, 30.0f);
canvas->set_z_index(red, 5); // drawn above every child with a lower z-index
canvas->set_left(red, 60.0f); // moves it and updates the layout
window.set_content(std::move(canvas));

// Declaratively, with bindable placements:
ui::Property<float> left{20.0f};
window.set_content(ui::Canvas{
    .width    = 140.0f,
    .height   = 140.0f,
    .content  = {
        ui::CanvasItem{.left    = left,
         .top     = 30.0f,
         .content = ui::Rectangle{.fill = ui::colors::Black, .width = 40.0f, .height = 40.0f}},
        ui::CanvasItem{.left = 60.0f, .top = 60.0f, .z_index = 1, .content = ui::Text{.text = "On top"}},
    },
});

CanvasControl

CanvasControl() = default

An empty canvas; with no width/height set it takes whatever its slot gives it.

add

template <typename T>
T* add(std::unique_ptr<T> child, float left = 0.0f, float top = 0.0f, int z_index = 0)

Takes ownership of a child placed at (left, top), DIPs from the canvas' top-left, at z_index, and returns a non-owning observer, as StackControl::add() does. Updates the layout. A null child is ignored and returns nullptr.

add

template <SpecLike S>
Element* add(S&& spec, float left = 0.0f, float top = 0.0f, int z_index = 0)

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.

set_left

void set_left(const Element* child, float left)

Sets the left offset of child, DIPs, and updates the layout. Ignored for an element that is not a child of this canvas.

set_top

void set_top(const Element* child, float top)

Sets the top offset of child, DIPs, and updates the layout. Ignored for a non-child.

set_z_index

void set_z_index(const Element* child, int z_index)

Sets the z-index of child. A higher z-index draws later, on top, and gets input first. Redraws. Ignored for a non-child.

left

float left(const Element* child) const

The left offset of child, DIPs; 0 for an element that is not a child of this canvas.

top

float top(const Element* child) const

The top offset of child, DIPs; 0 for a non-child.

z_index

int z_index(const Element* child) const

The z-index of child; 0 for a non-child.

parallel_measure_safe

bool parallel_measure_safe() const override

True: measuring touches only the canvas and its children.

render_order

std::vector<Element*> render_order() const override

The children sorted by z-index (stable, so ties keep insertion order), bottom first.

measure_override

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

Measures every child with unbounded room and asks for zero itself.

arrange_override

void arrange_override(const Rect& final_rect, const ArrangeContext& context) override

Places each child at its offset from final_rect's top-left, at its desired size.

class CanvasItemList

A Canvas' children, built, in the same shape as CellList. Callers write it as a braced list of ui::CanvasItem{...}.

items_

std::vector<Placed> items_

The placed children, in declaration order.

CanvasItemList

CanvasItemList() = default

An empty list.

CanvasItemList

template <typename... S>
CanvasItemList(S&& ... items)
    requires (sizeof...(S) > 0 && (std::same_as<S, CanvasItem> && ...))

Builds every item's content and takes ownership of it.

CanvasItemList

CanvasItemList(const CanvasItemList&) = delete

operator=

CanvasItemList& operator=(const CanvasItemList&) = delete

CanvasItemList

CanvasItemList(CanvasItemList&&) = default

operator=

CanvasItemList& operator=(CanvasItemList&&) = default

add

void add(CanvasItem item)

Builds item's content and appends it, as one more entry of the braced list would.

struct Placed

One built child with its position; the node is already owned by the list.

Field Type Default
left Binding<float> Left offset, possibly bound.
top Binding<float> Top offset, possibly bound.
z_index Binding<int> Z-index, possibly bound.
node std::unique_ptr<Element> The built child.

Tests

  • tests/test_layout_panels.cpp