morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
wizard_view.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5/// @file forms/engine/wizard_view.hpp
6/// @brief `wizardView`: a `WizardSession` rendered as a view tree (`ui::Node`): the title and step indicator, the
7/// current step's form, and Back and Next.
8///
9/// Specified in `docs/spec/forms/engine.md`, "Rendering collections and wizards".
10
11#include <cstddef>
12#include <cstdint>
13#include <functional>
14#include <optional>
15#include <string>
16#include <utility>
17#include <vector>
18
19#include "../../ui/view.hpp"
20#include "form_view.hpp"
21#include "wizard.hpp"
22
23namespace morph::forms {
24
25/// @brief Replacements for the parts of a wizard view a host draws from its own UI kit. Each one left empty keeps
26/// the built-in part.
28 /// @brief Replaces the title and the step indicator. Called once with the session, whose `title`, `current`,
29 /// `stepCount` and `stepTitle` it reads.
30 std::function<ui::Node(WizardSession&)> header;
31 /// @brief Replaces Back and Next. Called once with the session, whose `canBack`, `canNext`, `onLastStep`, `back`
32 /// and `next` are the same gates and moves the built-in buttons use.
33 std::function<ui::Node(WizardSession&)> nav;
34};
35
36/// @brief How `wizardView` draws a wizard and labels its chrome.
38 /// @brief Every step's form view options; its overrides reach every step's fields.
40 /// @brief The host's replacements for the view's own parts.
42 /// @brief The Back button's caption.
43 std::string backLabel = "Back";
44 /// @brief The Next button's caption.
45 std::string nextLabel = "Next";
46 /// @brief The note shown on the last step, which has no Next.
47 std::string lastStepText = "Last step — fill it in to finish";
48 /// @brief The built-in header's title (`wizard_title`, before the step's position); empty shows the schema's.
50};
51
52namespace detail::engine {
53
54/// @brief The built-in header: the title with the step's position (`Flow (1 / 2)`) and a step indicator (done,
55/// current, pending).
56/// @param session The session.
57/// @param heading The title in place of the schema's; empty shows the schema's.
58/// @return The header.
59[[nodiscard]] inline ui::Node wizardHeader(WizardSession const& session, ui::Prop<std::string> const& heading = {}) {
60 WizardSession const* const wizard = &session;
61 ui::Text title{.text =
62 [wizard, heading] {
63 std::string const named = isSet(heading) ? heading.evaluate() : wizard->title();
64 return named + " (" + std::to_string(wizard->current() + 1) + " / " +
65 std::to_string(wizard->stepCount()) + ")";
66 },
67 .role = ui::TextRole::Heading};
68 title.common.testId = "wizard_title";
69 ui::Steps steps{
70 .items =
71 [wizard] {
72 std::vector<ui::Step> items;
73 std::size_t const here = wizard->current();
74 for (std::size_t i = 0; i < wizard->stepCount(); ++i) {
75 ui::StepState state = ui::StepState::Pending;
76 if (i == here) {
77 state = ui::StepState::Active;
78 } else if (wizard->stepDone(i)) {
79 state = ui::StepState::Done;
80 }
81 items.push_back(ui::Step{.label = wizard->stepTitle(i), .state = state});
82 }
83 return items;
84 },
85 .current = [wizard] { return std::optional<std::size_t>{wizard->current()}; },
86 };
87 steps.common.testId = "wizard_steps";
88 return ui::column(ui::Column{.children = {ui::text(std::move(title)), ui::steps(std::move(steps))}, .gap = 4});
89}
90
91/// @brief The built-in navigation: Back, Next and the last step's note.
92/// @param session The session.
93/// @param options The view options.
94/// @return The navigation row.
95[[nodiscard]] inline ui::Node wizardNav(WizardSession& session, WizardViewOptions const& options) {
96 WizardSession* const wizard = &session;
97 ui::Button back{.label = options.backLabel, .onClick = [wizard] { wizard->back(); }};
98 back.common.enabled = [wizard] { return wizard->canBack(); };
99 back.common.testId = "wizardBack";
100 ui::Button next{.label = options.nextLabel, .onClick = [wizard] { wizard->next(); }};
101 next.common.enabled = [wizard] { return wizard->canNext(); };
102 next.common.testId = "wizardNext";
103 ui::Text last{.text = options.lastStepText, .role = ui::TextRole::Muted};
104 last.common.visible = [wizard] { return wizard->onLastStep(); };
105 last.common.testId = "wizard_last";
106 return ui::row(ui::Row{
107 .children = {ui::button(std::move(back)), ui::button(std::move(next)), ui::text(std::move(last))}, .gap = 4});
108}
109
110} // namespace detail::engine
111
112/// @brief Renders @p session: the header (title with the step's position, a step indicator), the current step's
113/// `formView` (one per step, mounted while it is current; the values live in the steps' sessions, so leaving
114/// a step loses nothing), and Back and Next, enabled by `canBack` and `canNext`.
115///
116/// Test ids: `wizard_title`, `wizard_steps`, `wizard_step_<index>` (the current step's form), `wizardBack`,
117/// `wizardNext` and `wizard_last`; the step's fields have their form ids.
118///
119/// The nodes bind to the session by pointer: mount the result after the session and destroy the mount before it.
120/// @param session The session.
121/// @param options Every step's form options, the chrome and the labels.
122/// @return The view.
123[[nodiscard]] inline ui::Node wizardView(WizardSession& session, WizardViewOptions const& options = {}) {
124 WizardSession const* const wizard = &session;
125 std::vector<ui::SwitchCase> cases;
126 for (std::size_t i = 0; i < session.stepCount(); ++i) {
127 ui::Column step{.children = {formView(session.step(i), options.form)}};
128 step.common.testId = "wizard_step_" + std::to_string(i);
129 cases.push_back(
130 ui::SwitchCase{.key = ui::Key{static_cast<std::int64_t>(i)}, .node = ui::column(std::move(step))});
131 }
132 ui::Node body = ui::switchOf(ui::Switch{
133 .selector = [wizard] { return ui::Key{static_cast<std::int64_t>(wizard->current())}; },
134 .cases = std::move(cases),
135 });
136 return ui::column(ui::Column{
137 .children = {options.chrome.header ? options.chrome.header(session)
138 : detail::engine::wizardHeader(session, options.title),
139 std::move(body),
140 options.chrome.nav ? options.chrome.nav(session) : detail::engine::wizardNav(session, options)},
141 .gap = 4});
142}
143
144} // namespace morph::forms
AnyHandle session(std::string const &name)
A session attribute, {"session": name}.
Definition author.hpp:1214
Node steps(AnyHandle const &items, AnyHandle const &current={})
A steps node.
Definition author.hpp:4034
Node wizard(ModelHandle< Model > const &model, FormPresentation const &presentation={})
A wizard node: the forms engine's wizard over an action.
Definition author.hpp:4324
The reactive state of one wizard: its steps' sessions, the position, and what each step resolved.
Definition wizard.hpp:140
A node property: a constant, or a slot — a reactive read, with a stable id when it was given one.
Definition view.hpp:106
Node testId(std::string const &name) const
A stable identifier for automated tests.
Definition author.hpp:1907
formView: a FormSession rendered as a view tree (ui::Node), so one form draws on every frontend.
How formView lays a form out and labels its chrome.
Definition form_view.hpp:38
Replacements for the parts of a wizard view a host draws from its own UI kit. Each one left empty kee...
std::function< ui::Node(WizardSession &)> header
Replaces the title and the step indicator. Called once with the session, whose title,...
std::function< ui::Node(WizardSession &)> nav
Replaces Back and Next. Called once with the session, whose canBack, canNext, onLastStep,...
How wizardView draws a wizard and labels its chrome.
FormViewOptions form
Every step's form view options; its overrides reach every step's fields.
std::string backLabel
The Back button's caption.
std::string nextLabel
The Next button's caption.
WizardChrome chrome
The host's replacements for the view's own parts.
ui::Prop< std::string > title
The built-in header's title (wizard_title, before the step's position); empty shows the schema's.
std::string lastStepText
The note shown on the last step, which has no Next.
Children stacked top to bottom.
Definition view.hpp:564
Common common
Visibility, enablement, layout, drag-and-drop.
Definition view.hpp:570
std::string testId
The stable identifier an automated test finds the widget by; empty means none. Set once.
Definition view.hpp:364
Static or bound text.
Definition view.hpp:406
Common common
Visibility, enablement, layout, drag-and-drop.
Definition view.hpp:414
The toolkit-agnostic view tree: props, node kinds and their builders.
std::shared_ptr< NodeData const > Node
A view-tree node: immutable, shared data.
Definition view.hpp:403
Wizards, headless: a w-* document as one FormSession per step, sequenced by successful submissions,...
ui::Node wizardView(WizardSession &session, WizardViewOptions const &options={})
Renders session: the header (title with the step's position, a step indicator), the current step's fo...