morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
layout.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5/// @file forms/layout.hpp
6/// @brief Visual structure (sections, tabs, accordions, column spans) layered
7/// over an action's flat field list.
8///
9/// `morph::forms::schemaJson<A>()` (`forms.hpp`) already lays every field out
10/// in a single flat list, ordered by `x-order`. This header adds two
11/// **optional** compile-time declarations an action may expose: `formLayout`,
12/// a list of `FieldGroup`s that bucket fields into titled sections, tabs, or
13/// an accordion panel; and `fieldSpans`, a list of `FieldSpan`s that widen
14/// individual fields in a grid renderer. Both mirror the existing
15/// `optionalFields` convention (`forms.hpp`) — a `static constexpr` list
16/// `mergeSchemaExtras` looks for by name, present only when an action opts
17/// in.
18///
19/// Absent either declaration, `schemaJson<A>()`'s output is unchanged: no
20/// `x-layout`, `x-group`, `x-section`, or `x-colspan` key is emitted, and a
21/// renderer lays every field out exactly as it does today (flat, `x-order`
22/// order). See docs/spec/forms/forms.md, "Layout & grouping".
23
24#include <cstdint>
25#include <iterator>
26#include <span>
27#include <string_view>
28
29namespace morph::forms {
30
31/// @brief The container kind a `FieldGroup` renders as.
32enum class GroupKind : std::uint8_t {
33 Section, ///< A titled fieldset, stacked vertically (the default).
34 Tab, ///< A pane of a shared tab bar; consecutive `Tab` groups share one bar.
35 Accordion ///< A collapsible panel.
36};
37
38/// @brief Maps a `GroupKind` to the string `mergeSchemaExtras` emits for it
39/// in `x-layout.groups[].kind`.
40/// @param kind Group kind to name.
41/// @return `"section"`, `"tab"`, or `"accordion"`. The `default` branch (all
42/// three enumerators are already listed explicitly) is an
43/// out-of-range-value fallback to `"section"`, the renderer's
44/// documented downgrade for an unsupported kind.
45[[nodiscard]] constexpr std::string_view groupKindName(GroupKind kind) noexcept {
46 switch (kind) {
48 return "section";
49 case GroupKind::Tab:
50 return "tab";
52 return "accordion";
53 default:
54 // Unreachable through any real code path: GroupKind is a closed
55 // enum and every enumerator is already handled explicitly above.
56 // This arm only exists to satisfy the compiler that the function
57 // returns on every enum value, including one manufactured by an
58 // out-of-range `static_cast` -- mirrors ruleKindName's identical
59 // default: arm in forms.hpp.
60 return "section";
61 }
62}
63
64/// @brief One named group of fields in an action's `formLayout`.
65///
66/// `fields` gives **membership only** — which wire keys belong to this
67/// group — not intra-group order: the renderer still lays fields out by
68/// ascending `x-order` within the group. The array position of a `FieldGroup`
69/// inside `A::formLayout` gives the cross-group order.
70struct FieldGroup {
71 /// @brief Section / tab / panel heading shown to the user.
72 std::string_view title;
73
74 /// @brief Container this group renders as. Defaults to `Section`.
76
77 /// @brief Member wire keys belonging to this group (membership only; see
78 /// the struct documentation for intra-group order).
79 std::span<const std::string_view> fields;
80};
81
82/// @brief A field's declared column span in a grid renderer.
83struct FieldSpan {
84 /// @brief Wire key of the field this span applies to.
85 std::string_view field;
86
87 /// @brief Number of grid columns the field should span. `1` (the
88 /// default) is the ordinary single-column width and is never
89 /// emitted as `x-colspan` — only values greater than `1` are.
90 int colspan{1};
91};
92
93namespace detail {
94
95/// @brief Concept: action declares a `static constexpr` iterable
96/// `formLayout` list of `FieldGroup`.
97template <typename A>
98concept HasFormLayout = requires {
99 std::begin(A::formLayout);
100 std::end(A::formLayout);
101};
102
103/// @brief Concept: action declares a `static constexpr` iterable
104/// `fieldSpans` list of `FieldSpan`.
105template <typename A>
106concept HasFieldSpans = requires {
107 std::begin(A::fieldSpans);
108 std::end(A::fieldSpans);
109};
110
111} // namespace detail
112
113} // namespace morph::forms
GroupKind
The container kind a FieldGroup renders as.
Definition layout.hpp:32
@ Accordion
A collapsible panel.
Definition layout.hpp:35
@ Tab
A pane of a shared tab bar; consecutive Tab groups share one bar.
Definition layout.hpp:34
@ Section
A titled fieldset, stacked vertically (the default).
Definition layout.hpp:33
constexpr std::string_view groupKindName(GroupKind kind) noexcept
Maps a GroupKind to the string mergeSchemaExtras emits for it in x-layout.groups[]....
Definition layout.hpp:45
One named group of fields in an action's formLayout.
Definition layout.hpp:70
std::span< const std::string_view > fields
Member wire keys belonging to this group (membership only; see the struct documentation for intra-gro...
Definition layout.hpp:79
GroupKind kind
Container this group renders as. Defaults to Section.
Definition layout.hpp:75
std::string_view title
Section / tab / panel heading shown to the user.
Definition layout.hpp:72
A field's declared column span in a grid renderer.
Definition layout.hpp:83
int colspan
Number of grid columns the field should span. 1 (the default) is the ordinary single-column width and...
Definition layout.hpp:90
std::string_view field
Wire key of the field this span applies to.
Definition layout.hpp:85
One section of a SectionGroup: a registered action, a display title, and zero or more Bind prefill de...
Definition sections.hpp:46