Dialog¶
Header: winui/ui/dialogs/Dialog.hpp. Spec: winui/ui/dialogs/Dialog.hpp.
A modal dialog over the whole window, with a title, arbitrary content and up to three buttons.
It isn't placed in the tree. Hand it to Window::show_modal(), which takes ownership and routes all input to it until it closes. Tab moves only between the dialog's controls, and focus goes back where it was afterward. The buttons are always Primary, Secondary, Close from left to right, whatever order they were set in.
The default_button() is accented and clicked by an Enter that nothing else in the dialog uses. Escape clicks the Close button, or closes with Close when there is none. Every button and Escape close the dialog. on_closing() runs first and may cancel. Then the window takes the dialog down, and on_closed() reports the result once it is gone. Window::close_modal() closes it from outside, reporting None. With no default button set, the primary button is the default when there is one. A dialog whose primary button destroys something sets Close or None instead.
The dialog is at least 320x184 and at most 548x756 DIPs, unless its width, max_width, height or max_height say otherwise. It fades in over 250 ms.
Example:
auto dialog = std::make_unique<ui::DialogControl>();
dialog->set_title("Close without saving?");
dialog->set_content(std::make_unique<ui::TextControl>("Your changes will be lost."));
dialog->set_primary_button("Close", nullptr);
dialog->set_close_button("Cancel", nullptr);
dialog->set_default_button(ui::DialogDefaultButton::Close);
dialog->on_closed([&](ui::DialogResult result) {
if (result == ui::DialogResult::Primary)
quit();
});
window.show_modal(std::move(dialog));
// Declaratively:
bool busy = false;
window.show_modal(ui::Dialog{
.title = "Close without saving?",
.default_button = ui::DialogDefaultButton::Close,
.primary = {.text = "Close"},
.close = {.text = "Cancel"},
.on_closing = [&](const ui::DialogClosingArgs& args)
{ return args.result == ui::DialogResult::Primary && busy ? ui::Cancel : ui::Proceed; },
.on_closed = [&](ui::DialogResult result)
{
if (result == ui::DialogResult::Primary)
quit();
},
.content = ui::Text{.text = "Your changes will be lost."},
});
ui::Dialog¶
A modal dialog. Builds a ui::DialogControl, which a caller then hands to Window::show_modal().
Every button and Escape close the dialog by themselves. on_closing runs first and may cancel, for example while a save is still running. Then on_closed reports the result once the dialog is gone.
Example:
window.show_modal(ui::Dialog{
.title = "Close without saving?",
.default_button = ui::DialogDefaultButton::Close,
.primary = {.text = "Close"},
.close = {.text = "Cancel"},
.on_closed = [&](ui::DialogResult result) {
if (result == ui::DialogResult::Primary)
quit();
},
.content = ui::Text{.text = "Your changes will be lost."},
});
Every field, in the order a designated initializer names them:
ui::Dialog{
.title, .default_button, .primary, .secondary, .close, .style, .margin, .width, .height,
.min_width, .min_height, .max_width, .max_height, .horizontal_alignment,
.vertical_alignment, .visible, .tool_tip, .accessibility, .on_closing, .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 | |
|---|---|---|---|
title |
ui::Binding<std::string> |
The heading above the content. | |
default_button |
ui::Binding<std::optional<ui::DialogDefaultButton>> |
The default button: accented, and clicked by an Enter nothing else in the dialog uses. Unset makes it the primary button when there is one, and no button otherwise. | |
primary |
ui::DialogButton |
Always shown Primary, Secondary, Close from left to right; an empty text leaves one out. | |
secondary |
ui::DialogButton |
The middle button; closes with DialogResult::Secondary. | |
close |
ui::DialogButton |
The right-hand button, also clicked by Escape; closes with DialogResult::Close. | |
on_closing |
ui::Callback<ui::Decision(const ui::DialogClosingArgs&)> |
Gets the result of every close before it happens; return ui::Cancel to keep the dialog open. | |
on_closed |
ui::Callback<void(ui::DialogResult)> |
Fired once the dialog is closed and gone, with what closed it. | |
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::Dialog> |
Out: the handle that receives the built control. The window owns the control once shown, until it closes. | |
content |
ui::Spec |
The body between the title and the buttons. |
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¶
Consumes the spec and returns the built ui::DialogControl; the caller owns it.
ui::DialogButton¶
One of a dialog's up-to-three buttons. Left default (empty text) means "no such button".
Example:
ui::Property<bool> can_save{false};
ui::DialogButton save_button{ .text = "Save", .enabled = can_save, .on_click = [&] { save(); } };
Every field, in the order a designated initializer names them:
Fields, in declaration order (designators must follow it):
| Field | Type | Default | |
|---|---|---|---|
text |
std::string |
The button's caption; empty leaves the button out. | |
on_click |
ui::Action |
Fired on click, before the dialog starts closing. |
Plus the common fields enabled every spec carries (conventions).
operator bool¶
True when the button exists, which means it has a caption.
ui::DialogControl¶
Derives from ui::Element.
A modal dialog over the whole window, with a title, arbitrary content and up to three buttons.
It isn't placed in the tree. Hand it to Window::show_modal(), which takes ownership and routes all input to it until it closes. Tab moves only between the dialog's controls, and focus goes back where it was afterward. The buttons are always Primary, Secondary, Close from left to right, whatever order they were set in.
The default_button() is accented and clicked by an Enter that nothing else in the dialog uses. Escape clicks the Close button, or closes with Close when there is none. Every button and Escape close the dialog. on_closing() runs first and may cancel. Then the window takes the dialog down, and on_closed() reports the result once it is gone. Window::close_modal() closes it from outside, reporting None. With no default button set, the primary button is the default when there is one. A dialog whose primary button destroys something sets Close or None instead.
The dialog is at least 320x184 and at most 548x756 DIPs, unless its width, max_width, height or max_height say otherwise. It fades in over 250 ms.
Example:
auto dialog = std::make_unique<ui::DialogControl>();
dialog->set_title("Close without saving?");
dialog->set_content(std::make_unique<ui::TextControl>("Your changes will be lost."));
dialog->set_primary_button("Close", nullptr);
dialog->set_close_button("Cancel", nullptr);
dialog->set_default_button(ui::DialogDefaultButton::Close);
dialog->on_closed([&](ui::DialogResult result) {
if (result == ui::DialogResult::Primary)
quit();
});
window.show_modal(std::move(dialog));
// Declaratively:
bool busy = false;
window.show_modal(ui::Dialog{
.title = "Close without saving?",
.default_button = ui::DialogDefaultButton::Close,
.primary = {.text = "Close"},
.close = {.text = "Cancel"},
.on_closing = [&](const ui::DialogClosingArgs& args)
{ return args.result == ui::DialogResult::Primary && busy ? ui::Cancel : ui::Proceed; },
.on_closed = [&](ui::DialogResult result)
{
if (result == ui::DialogResult::Primary)
quit();
},
.content = ui::Text{.text = "Your changes will be lost."},
});
DialogControl¶
An empty dialog: no title, no content, no buttons.
set_title¶
Sets the heading above the content (UTF-8), drawn 20 DIPs, semibold and wrapping.
title¶
The title; empty by default. Screen readers also use it as the name when none is set.
set_content¶
Takes ownership of the body content and returns a non-owning observer to it, the same convention as StackControl::add(). Each further call appends below the previous content.
set_primary_button¶
Sets the primary (leftmost) button, which closes with Primary. Each button's click runs on_click (if any), then closes the dialog with its result. Calling one again replaces that button. The visual order is fixed whatever order these are called in. Returns the button as a non-owning observer, owned by the dialog. on_click may be null.
set_secondary_button¶
Sets the secondary button, which closes with Secondary.
set_close_button¶
Sets the close button, also clicked by Escape, which closes with Close.
primary_button¶
The primary button, or nullptr when none was set.
secondary_button¶
The secondary button, or nullptr when none was set.
close_button¶
The close button, or nullptr when none was set.
set_default_button¶
Sets the default button. It is drawn accented and clicked by an Enter that the focused element does not use itself (a Button, a list or a TextBox with a submit handler keep theirs). While focus is on one of the dialog's own buttons the accent follows focus instead. std::nullopt (the default) makes the primary button the default when there is one, and no button otherwise.
default_button¶
The default button in effect: the one set, else Primary when there is a primary button, else None.
hide¶
Close with None (on_closing still runs, and may cancel).
result¶
What the last close reported; None until one happened.
on_closing¶
Raised before every close with its result; return ui::Cancel to keep the dialog open (a Primary click on a form that does not validate, say).
on_closed¶
Raised once the dialog is closed and out of the window, with what closed it. The dialog is destroyed by then, so the handler may show the next one. A dialog replaced by another show_modal() does not raise it.
for_each_child¶
Visits the title, the content host and the shown buttons, in visual order.
collect_focusables¶
Adds the content's focusable controls, then the shown buttons, to the dialog's tab order. The title is never a stop.
default_focus¶
Where focus starts: the first focusable thing in the content, else the default button.
transition_opacity¶
The opening fade's opacity, rising linearly from 0 to 1 over 250 ms. The dimmed backdrop behind the dialog fades in with it. There is no closing fade. Window::close_modal() removes the dialog and restores focus at once.
automation_role¶
Always AutomationRole::Pane.
automation_name_fallback¶
title(), used when Accessibility::name is not set.
measure_override¶
The frame, title, content and button area, clamped between 320x184 and 548x756 DIPs. A width or max width set on the dialog replaces the 548, and a height or max height the 756. When the dialog is wider than 548, the buttons keep their size at 548 and group at the right.
arrange_override¶
Places the title and content, with 24 DIPs of padding and 12 DIPs apart, and the buttons in the button area. Three buttons take a third each, two a half each, and one alone the right half.
render_override¶
Draws the frame, the title and content background, the button area and the children.
tick_override¶
Advances the open fade.
key_override¶
Enter the focused element had no use for: the default button's click, if it is enabled.
struct DialogClosingArgs¶
What a Dialog's on_closing gets: the result the close reports. The handler returns ui::Cancel to keep the dialog open.
| Field | Type | Default | |
|---|---|---|---|
result |
DialogResult |
DialogResult::None |
Which button (or Escape) is closing it. |
class DialogAwaiter¶
What co_await ui::show_dialog(window, spec) waits on; see show_dialog().
DialogAwaiter¶
Holds spec until the coroutine suspends.
await_ready¶
Never ready: the dialog is shown when the coroutine suspends.
await_suspend¶
Shows the dialog and hands coroutine to its close.
await_resume¶
What closed the dialog.
Enumerations¶
DialogDefaultButton¶
Which button of a dialog is the default one.
| Value | |
|---|---|
None |
No default button: Enter clicks nothing, no button is accented. |
Primary |
The primary (leftmost) button. |
Secondary |
The secondary button. |
Close |
The close (cancel) button. |
DialogResult¶
What closed the dialog. The Close button and Escape report Close; hide() and Window::close_modal() report None.
| Value | |
|---|---|
None |
Closed from outside: hide() or Window::close_modal(). |
Primary |
Closed by the primary button. |
Secondary |
Closed by the secondary button. |
Close |
Closed by the Close button or Escape. |
Functions¶
show_dialog¶
Shows spec modally over window and lets a coroutine wait for the answer. co_await ui::show_dialog(window, spec) gives the DialogResult the dialog closed with, so a flow of questions reads top to bottom without nested on_closed handlers. The spec's own on_closing, which may still cancel, and on_closed still run. on_closed runs before the coroutine goes on.
The coroutine resumes on the UI thread inside the window's next tick(), as after on_ui_thread(). When the dialog goes without an answer, the coroutine is dropped instead and its frame destroyed without resuming. That happens when another modal replaces the dialog, when the Window is destroyed with it open, or when the window is closed by the time the answer would resume it. A coroutine that has to clean up does it in a destructor, as with on_ui_thread(). Call it on the UI thread, from a coroutine such as a ui::FireAndForget.
With MSVC, compute texts that come from a function call into local variables before the co_await. MSVC can destroy a temporary of a co_await expression too early, and the call that reads it crashes.
Example:
ui::FireAndForget delete_files(ui::Window& window, ui::Property<std::string>& status)
{
const ui::DialogResult answer = co_await ui::show_dialog(window, ui::Dialog{
.title = "Delete 3 files?",
.default_button = ui::DialogDefaultButton::Close,
.primary = {.text = "Delete"},
.close = {.text = "Cancel"},
.content = ui::Text{.text = "They go to the recycle bin."},
});
if (answer != ui::DialogResult::Primary)
co_return;
status = "Deleted";
}
Tests¶
tests/test_dialog.cpp