morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
app.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5/// @file forms/app.hpp
6/// @brief App-shell descriptor: menu -> screens (form / wizard / view refs).
7///
8/// A `morph::app::App` names an ordered menu and a map of screen-id ->
9/// screen, where a screen is a reference to an already-registered action
10/// form (`FormScreen`) or wizard (`WizardScreen`). `appSchemaJson<AppT>()`
11/// emits the `app-*` document a renderer loads as its navigation root,
12/// replacing "enumerate every schema on one scroll." Purely additive
13/// metadata: no new dispatch path, the shell only routes to existing
14/// action-forms and wizards. See docs/spec/forms/workflows_navigation.md.
15
16#include <glaze/glaze.hpp>
17#include <string>
18#include <string_view>
19#include <tuple>
20
22#include "flows.hpp"
23
24namespace morph::app {
25
26/// @brief One `app-menu` entry: a label and the screen-id it routes to.
27/// @tparam Label Human label shown in the menu.
28/// @tparam ScreenId Key into the app's `screens` map.
29template <morph::forms::FixedString Label, morph::forms::FixedString ScreenId>
30struct MenuEntry {
31 /// @brief The menu entry's display label.
32 /// @return The declared label.
33 [[nodiscard]] static constexpr std::string_view label() noexcept { return Label.view(); }
34
35 /// @brief The screen-id this entry routes to.
36 /// @return The declared screen-id.
37 [[nodiscard]] static constexpr std::string_view screen() noexcept { return ScreenId.view(); }
38};
39
40/// @brief A screen backed by one registered action's form.
41/// @tparam Id Screen-id, referenced from an `App::menu`'s `MenuEntry::screen()`.
42/// @tparam Action Registered action type (`BRIDGE_REGISTER_ACTION`) this screen renders.
43template <morph::forms::FixedString Id, typename Action>
44struct FormScreen {
45 /// @brief The screen's id.
46 /// @return The declared id.
47 [[nodiscard]] static constexpr std::string_view id() noexcept { return Id.view(); }
48
49 /// @brief The screen's kind, for the `app-screens[id].kind` key.
50 /// @return The literal `"form"`.
51 [[nodiscard]] static constexpr std::string_view kind() noexcept { return "form"; }
52
53 /// @brief The referenced action's registered type-id.
54 /// @return `ActionTraits<Action>::typeId()`.
55 [[nodiscard]] static constexpr std::string_view ref() noexcept {
56 return ::morph::model::ActionTraits<Action>::typeId();
57 }
58};
59
60/// @brief A screen backed by a registered `morph::flows::Wizard`.
61/// @tparam Id Screen-id, referenced from an `App::menu`'s `MenuEntry::screen()`.
62/// @tparam Wizard Registered wizard type (`BRIDGE_REGISTER_WIZARD`) this screen renders.
63template <morph::forms::FixedString Id, typename Wizard>
65 /// @brief The screen's id.
66 /// @return The declared id.
67 [[nodiscard]] static constexpr std::string_view id() noexcept { return Id.view(); }
68
69 /// @brief The screen's kind, for the `app-screens[id].kind` key.
70 /// @return The literal `"wizard"`.
71 [[nodiscard]] static constexpr std::string_view kind() noexcept { return "wizard"; }
72
73 /// @brief The referenced wizard's registered type-id.
74 /// @return `WizardTraits<Wizard>::typeId()`.
75 [[nodiscard]] static constexpr std::string_view ref() noexcept {
76 return ::morph::flows::WizardTraits<Wizard>::typeId();
77 }
78};
79
80// A ViewScreen<Id, View> counterpart (kind: "view") could be added here:
81// `morph::views::ViewTraits<V>` (forms/views.hpp) now exists, so nothing
82// blocks it. appSchemaJson
83// below only requires each screen type to expose id()/kind()/ref(), so adding
84// it later needs no change to appSchemaJson itself — this reference demo
85// therefore only exercises "form" and "wizard" screens (see
86// docs/spec/forms/workflows_navigation.md's Limitations).
87
88/// @brief The app-shell descriptor: a title, an ordered menu, and the screens it routes to.
89/// @tparam Title Application title (window/header).
90/// @tparam Menu A `std::tuple<MenuEntry<...>...>` of ordered menu entries.
91/// @tparam Screens A `std::tuple<FormScreen<...> | WizardScreen<...>...>` of screens.
92template <morph::forms::FixedString Title, typename Menu, typename Screens>
93struct App {
94 /// @brief Tuple of this app's ordered `MenuEntry<...>` types.
95 using menu = Menu;
96
97 /// @brief Tuple of this app's screen descriptor types.
98 using screens = Screens;
99
100 /// @brief The application's display title.
101 /// @return The declared title.
102 [[nodiscard]] static constexpr std::string_view title() noexcept { return Title.view(); }
103};
104
105/// @brief Traits specialisation mapping an `App` type to its string type-id.
106///
107/// Specialise via `BRIDGE_REGISTER_APP` rather than by hand. The default is a
108/// forward declaration — using it without a specialisation is an
109/// incomplete-type error.
110/// @tparam A Concrete `App<...>` type.
111template <typename A>
112struct AppTraits; // forward — specialise or use BRIDGE_REGISTER_APP
113
114/// @brief Generates the `app-*` JSON document for app-shell type @p AppT.
115///
116/// Emits `app-title`, an ordered `app-menu` array (`{label, screen}` per
117/// entry), and an `app-screens` object mapping each screen's id to
118/// `{kind, ref}`. See docs/spec/forms/workflows_navigation.md for the full
119/// key vocabulary.
120/// @tparam AppT Concrete `morph::app::App<Title, Menu, Screens>` type.
121/// @return The app's JSON document. Empty string only if glaze's own JSON
122/// writer fails on the assembled DOM (schema generation never throws).
123template <typename AppT>
124[[nodiscard]] std::string appSchemaJson() {
125 glz::generic_u64 dom{};
126 dom["app-title"] = std::string{AppT::title()};
127
128 glz::generic_u64::array_t menu{};
129 ::morph::forms::detail::forEachTupleElement<typename AppT::menu>([&]<typename Entry, std::size_t I>() {
130 static_cast<void>(I);
131 glz::generic_u64 entry{};
132 entry["label"] = std::string{Entry::label()};
133 entry["screen"] = std::string{Entry::screen()};
134 menu.emplace_back(std::move(entry));
135 });
136 dom["app-menu"] = menu;
137
138 auto& screensNode = dom["app-screens"];
139 ::morph::forms::detail::forEachTupleElement<typename AppT::screens>([&]<typename S, std::size_t I>() {
140 static_cast<void>(I);
141 auto& screenNode = screensNode[std::string{S::id()}];
142 screenNode["kind"] = std::string{S::kind()};
143 screenNode["ref"] = std::string{S::ref()};
144 });
145
146 return glz::write_json(dom).value_or(std::string{});
147}
148
149} // namespace morph::app
150
151/// @brief Specialises `morph::app::AppTraits<A>` with the string type-id @p NAME.
152///
153/// Metadata only, exactly like `BRIDGE_REGISTER_WIZARD`: an app-shell
154/// descriptor is never itself executed, only the actions/wizards its screens
155/// reference are (see docs/spec/forms/workflows_navigation.md).
156/// @param A Concrete `morph::app::App<...>` type.
157/// @param NAME String literal used as the app's type-id.
158// clang-format off -- public macro surface; see CONTRIBUTING.md, "Formatting/linting".
159#define BRIDGE_REGISTER_APP(A, NAME) \
160 template <> \
161 struct morph::app::AppTraits<A> { \
162 static constexpr std::string_view typeId() noexcept { return NAME; } \
163 };
164// clang-format on
std::string appSchemaJson()
Generates the app-* JSON document for app-shell type AppT.
Definition app.hpp:124
Multi-step wizards: an ordered sequence of registered actions that share a "flow draft" spanning the ...
Machinery shared by the two form session types: morph::flows::FlowSession (ordered wizard steps) and ...
Traits specialisation mapping an App type to its string type-id.
Definition app.hpp:112
The app-shell descriptor: a title, an ordered menu, and the screens it routes to.
Definition app.hpp:93
static constexpr std::string_view title() noexcept
The application's display title.
Definition app.hpp:102
Menu menu
Tuple of this app's ordered MenuEntry<...> types.
Definition app.hpp:95
Screens screens
Tuple of this app's screen descriptor types.
Definition app.hpp:98
A screen backed by one registered action's form.
Definition app.hpp:44
static constexpr std::string_view ref() noexcept
The referenced action's registered type-id.
Definition app.hpp:55
static constexpr std::string_view kind() noexcept
The screen's kind, for the app-screens[id].kind key.
Definition app.hpp:51
static constexpr std::string_view id() noexcept
The screen's id.
Definition app.hpp:47
One app-menu entry: a label and the screen-id it routes to.
Definition app.hpp:30
static constexpr std::string_view screen() noexcept
The screen-id this entry routes to.
Definition app.hpp:37
static constexpr std::string_view label() noexcept
The menu entry's display label.
Definition app.hpp:33
A screen backed by a registered morph::flows::Wizard.
Definition app.hpp:64
static constexpr std::string_view ref() noexcept
The referenced wizard's registered type-id.
Definition app.hpp:75
static constexpr std::string_view id() noexcept
The screen's id.
Definition app.hpp:67
static constexpr std::string_view kind() noexcept
The screen's kind, for the app-screens[id].kind key.
Definition app.hpp:71