Skip to content

Popup

Header: winui/ui/dialogs/Popup.hpp. Spec: winui/ui/dialogs/Popup.hpp.

Floats one child above the tree, at an offset from where the Popup sits.

The Popup itself takes no space in layout and draws no background, border or shadow. While it is open, the window measures the child at its natural size and places its top-left corner at the Popup's position plus the offsets. A child running past the window edge is clipped and stays where it is. Light dismiss is off by default, so the popup stays open until set_open(false) and clicks outside reach the page. With light dismiss on, a click outside or Escape closes it, and it holds keyboard focus while open and gives it back on close. In a spec tree it is built by ui::Popup, usually with is_open bound to a Property.

Example:

auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
ui::ButtonControl* show  = stack->add(std::make_unique<ui::ButtonControl>("Show popup"));
ui::PopupControl*  popup = stack->add(std::make_unique<ui::PopupControl>());
ui::ButtonControl* close = popup->set_child(std::make_unique<ui::ButtonControl>("Close"));
popup->set_horizontal_offset(200.0f);
popup->set_light_dismiss(true);
popup->set_child_transitions({ui::PopupTransition{}});
show->on_click([popup] { popup->set_open(true); });
close->on_click([popup] { popup->set_open(false); });
popup->on_closed([] { /* closed by the button, a click outside or Escape */ });
window.set_content(std::move(stack));

// Declaratively, with the open state in a Property:
ui::Property<bool> open{false};
std::unique_ptr<ui::Element> node = ui::build(ui::Popup{
    .is_open       = open, // two-way: a light dismiss writes false into `open`
    .light_dismiss = true,
    .content       = ui::Text{.text = "Simple Popup"},
}); // add `node` to any panel

ui::Popup

One child floated above the tree at an offset from where the popup sits, with no chrome of its own. Builds a ui::PopupControl, which takes no room in layout.

Example:

ui::Property<bool> open{false};

window.set_content(ui::VerticalStack{ .content = {
    ui::Button{ .text = "Show Popup", .on_click = [&] { open = true; } },
    ui::Popup{ .is_open           = open,
               .horizontal_offset = 200,
               .light_dismiss     = true,
               .content           = ui::Text{ .text = "Simple Popup" } },
}});

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

ui::Popup{
    .is_open, .horizontal_offset, .vertical_offset, .light_dismiss, .child_transitions, .style,
    .margin, .width, .height, .min_width, .min_height, .max_width, .max_height,
    .horizontal_alignment, .vertical_alignment, .visible, .tool_tip, .accessibility,
    .on_opened, .on_closed, .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
is_open ui::TwoWayField<bool> {false} Whether the child is showing.
horizontal_offset ui::Binding<float> {0.0f} The child's horizontal offset from the popup's own position, in DIPs.
vertical_offset ui::Binding<float> {0.0f} The child's vertical offset from the popup's own position, in DIPs.
light_dismiss ui::Binding<bool> {false} Whether an outside click or Escape closes it.
child_transitions ui::Binding<ui::TransitionList> The transitions the child plays (ui::PopupControl::set_child_transitions()). A PopupTransition animates the child opening and closing.
on_opened ui::Action Raised once the child is showing.
on_closed ui::Action Raised however it closed, including a click outside or Escape. A two-way bound is_open has been written by then.
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::Popup> Out: the handle that receives the built control.
content ui::Spec The floated content; it draws its own background, if any.

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

build

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

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

ui::PopupControl

Derives from ui::Element.

Floats one child above the tree, at an offset from where the Popup sits.

The Popup itself takes no space in layout and draws no background, border or shadow. While it is open, the window measures the child at its natural size and places its top-left corner at the Popup's position plus the offsets. A child running past the window edge is clipped and stays where it is. Light dismiss is off by default, so the popup stays open until set_open(false) and clicks outside reach the page. With light dismiss on, a click outside or Escape closes it, and it holds keyboard focus while open and gives it back on close. In a spec tree it is built by ui::Popup, usually with is_open bound to a Property.

Example:

auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
ui::ButtonControl* show  = stack->add(std::make_unique<ui::ButtonControl>("Show popup"));
ui::PopupControl*  popup = stack->add(std::make_unique<ui::PopupControl>());
ui::ButtonControl* close = popup->set_child(std::make_unique<ui::ButtonControl>("Close"));
popup->set_horizontal_offset(200.0f);
popup->set_light_dismiss(true);
popup->set_child_transitions({ui::PopupTransition{}});
show->on_click([popup] { popup->set_open(true); });
close->on_click([popup] { popup->set_open(false); });
popup->on_closed([] { /* closed by the button, a click outside or Escape */ });
window.set_content(std::move(stack));

// Declaratively, with the open state in a Property:
ui::Property<bool> open{false};
std::unique_ptr<ui::Element> node = ui::build(ui::Popup{
    .is_open       = open, // two-way: a light dismiss writes false into `open`
    .light_dismiss = true,
    .content       = ui::Text{.text = "Simple Popup"},
}); // add `node` to any panel

PopupControl

PopupControl()

A closed popup with no child, zero offsets and light dismiss off. Not hit-test visible: it draws nothing where it sits, so a press there goes to what is under it.

set_child

template <typename T>
T* set_child(std::unique_ptr<T> child)

Takes ownership of the floated child and returns a non-owning observer to it. The child is not part of the main tree. The window lays it out and draws it while the popup is open.

child

Element* child() const

The floated child (non-owning), or nullptr.

is_open

bool is_open() const

Whether the popup is open. Opening raises on_opened. Closing raises on_closed, whether by set_open(), a click outside or Escape. Setting the value it already has raises nothing.

set_open

void set_open(bool open)

Opens or closes the popup. With child transitions, the child stays on screen after closing until its close transition has played (see closing()).

horizontal_offset

float horizontal_offset() const

The child's offset from the Popup's own position, in DIPs. 0 by default.

set_horizontal_offset

void set_horizontal_offset(float offset)

Sets the horizontal offset. The child moves on the next layout.

vertical_offset

float vertical_offset() const

The child's vertical offset from the Popup's position, in DIPs; 0 by default.

set_vertical_offset

void set_vertical_offset(float offset)

Sets the vertical offset. The child moves on the next layout.

light_dismiss

bool light_dismiss() const

Whether light dismiss is on; false by default. On, a click outside the child or Escape closes the popup, and it holds keyboard focus while open.

set_light_dismiss

void set_light_dismiss(bool enabled)

Turns light dismiss on or off. Takes effect at once, on an open popup too.

on_opened

void on_opened(Callback<void()> handler)

Fired after the popup opens. Replaces any previous handler.

on_closed

void on_closed(Callback<void()> handler)

Fired after the popup closes, whether by set_open(false), a click outside or Escape. Reset state bound to is_open() here. Replaces any previous handler.

child_transitions

const TransitionList& child_transitions() const

The transitions the child plays; empty by default. A PopupTransition slides the child in from its offset and fades it in on open, and fades it out on close. A ContentTransition also plays both ways, and an EntranceTransition on open only.

set_child_transitions

void set_child_transitions(TransitionList transitions)

Sets the child's transitions. Applies from the next open or close.

closing

bool closing() const

Whether the child is still on screen after closing, playing its close transition.

automation_role

AutomationRole automation_role() const override

Screen readers see the Popup as an empty group where it sits. They see the child only while the popup is open.

measure_override

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

Always zero: the Popup takes no space in layout.

arrange_override

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

Position children within the rect arrange() settled on (also in bounds(), window DIPs). Default: nothing, for a leaf.

render_override

void render_override(Renderer& renderer, const RenderContext& context) const 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().

clear_dirty_override

void clear_dirty_override(DirtyFlags flags) override

This element's own part of clear_dirty(): clear any part it owns but leaves out of for_each_child() (a parked page, a hidden button), through that part's clear_dirty(). Default: nothing.

tick_override

void tick_override(Duration delta) override

This element's own part of tick(): a clock of its own (ToolTip's hover delay, an animation's phase, a caret blink). Runs after the visual layer has advanced and before the children are ticked, which tick() does by itself. Tick only parts that for_each_child() does not list here. Default: nothing.

Tests

  • tests/test_popup.cpp