VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
MenuBuilder.cpp
Go to the documentation of this file.
2#include <algorithm>
3#include <cstddef>
4#include <memory>
5#include <utility>
6
7namespace VoltMod
8{
9
10/** Shared description callback for menu specs. */
11static std::function<MenuRow(int)> DescribeRow(std::string label, MenuRowKind kind, EnabledCondition enabled,
12 std::function<void(int slot, MenuRow& row)> live = {})
13{
14 return [label = std::move(label), kind, enabled = std::move(enabled), live = std::move(live)](int slot) {
15 MenuRow row{.Label = label,
16 .Kind = kind,
17 .Enabled = enabled(slot),
18 // Only a caption refuses the cursor.
19 .Selectable = kind != MenuRowKind::Text};
20 if (live)
21 {
22 live(slot, row);
23 }
24 return row;
25 };
26}
27
28/** @p action, run only while @p enabled takes the slot. */
30 std::function<void(int, MenuSurface&)> action)
31{
32 return [enabled = std::move(enabled), action = std::move(action)](int slot, MenuSurface& surface) {
33 if (action && enabled(slot))
34 {
36 }
37 };
38}
39
41{
42 return MenuItem{
44 .Activate = WhenEnabled(Enabled,
46 if (activate)
47 {
49 }
50 }),
51 };
52}
53
55{
56 // MenuStack::Describe supplies the localized state text.
57 return MenuItem{
59 [get = Get](int slot, MenuRow& row) {
60 row.Steppable = true;
61 row.State = get && get(slot);
62 }),
63 .Activate = WhenEnabled(Enabled,
64 [flip = Flip](int slot, MenuSurface&) {
65 if (flip)
66 {
67 flip(slot);
68 }
69 }),
70 .Step =
71 [flip = Flip, enabled = Enabled](int slot, int) {
72 if (!flip || !enabled(slot))
73 {
74 return false;
75 }
76 flip(slot);
77 return true;
78 },
79 };
80}
81
83{
84 return MenuItem{
86 [get = Get](int slot, MenuRow& row) {
87 // An unset value still needs to look like a field waiting for one.
88 std::string value = get ? get(slot) : std::string{};
89 row.Value = value.empty() ? "…" : std::move(value);
90 }),
91 .Activate = WhenEnabled(Enabled,
93 surface.Prompt(slot, prompt, [set, maxLength](int s, std::string_view text) {
94 // Reject over-long client input before calling the setter.
95 if (maxLength > 0 && static_cast<int>(text.size()) > maxLength)
96 {
97 return false;
98 }
99 return set ? set(s, text) : true;
100 });
101 }),
102 };
103}
104
106{
107 return MenuItem{
108 .Describe =
110 .Activate = WhenEnabled(Enabled,
111 [build = Build](int slot, MenuSurface& surface) {
112 if (!build)
113 {
114 return;
115 }
116 if (auto submenu = build(slot))
117 {
118 surface.Open(slot, std::move(submenu));
119 }
120 }),
121 };
122}
123
128
129/** One shared copy, so every callback of the open menu sees the same selection. */
131{
132 std::vector<std::string> Choices;
133 std::function<void(int slot, int index)> Commit;
134 std::optional<ChoiceIndex> Bind;
135 int Own;
136
137 int Selected(int slot) const
138 {
139 if (Choices.empty())
140 {
141 return 0;
142 }
143 const int index = Bind ? Bind->Get(slot) : Own;
144 return std::clamp(index, 0, static_cast<int>(Choices.size()) - 1);
145 }
146
147 void Select(int slot, int index)
148 {
149 if (Bind)
150 {
151 Bind->Set(slot, index);
152 }
153 else
154 {
155 Own = index;
156 }
157 }
158
159 void Apply(int slot) const
160 {
161 if (Commit && !Choices.empty())
162 {
164 }
165 }
166
167 bool Step(int slot, int direction)
168 {
169 if (Choices.empty())
170 {
171 return false;
172 }
173 Select(slot, WrapIndex(Selected(slot) + direction, static_cast<int>(Choices.size())));
174 return true;
175 }
176};
177
178MenuItem Internal::ChoiceItem(std::string label, std::vector<std::string> choices,
179 std::function<void(int slot, int index)> commit, std::optional<ChoiceIndex> bind,
181{
182 auto state = std::make_shared<ChoiceState>(
183 ChoiceState{.Choices = std::move(choices), .Commit = std::move(commit), .Bind = std::move(bind), .Own = index});
184
187 [state](int slot, MenuRow& row) {
188 // An empty list cannot step, so A/D pages instead.
189 if (state->Choices.empty())
190 {
191 return;
192 }
193 row.Value = state->Choices[static_cast<std::size_t>(state->Selected(slot))];
194 row.Steppable = true;
195 }),
196 .Activate = WhenEnabled(enabled,
197 [state](int slot, MenuSurface&) {
198 // Without a commit callback, E advances like D for a live pick-a-value row.
199 if (state->Commit)
200 {
201 state->Apply(slot);
202 }
203 else
204 {
205 state->Step(slot, +1);
206 }
207 }),
208 .Step = [state, enabled](int slot, int direction) { return enabled(slot) && state->Step(slot, direction); },
209 };
210
211 // Left empty for OnActivate: a row without Commit is applied only when activated.
213 {
214 item.Commit = [state, enabled](int slot) {
215 if (enabled(slot))
216 {
217 state->Apply(slot);
218 }
219 };
220 }
221 return item;
222}
223
224} // namespace VoltMod
MenuItem ChoiceItem(std::string label, std::vector< std::string > choices, std::function< void(int slot, int index)> commit, std::optional< ChoiceIndex > bind, int index, EnabledCondition enabled, ChoiceApply apply)
static std::function< MenuRow(int)> DescribeRow(std::string label, MenuRowKind kind, EnabledCondition enabled, std::function< void(int slot, MenuRow &row)> live={})
@ Input
Carries a value the player types in chat.
@ Submenu
Opens another menu.
@ Choice
Carries one of several values, stepped left and right.
@ Button
Runs something.
@ Text
Not selectable; a caption or a summary line.
@ Toggle
Carries an on/off value.
static std::function< void(int, MenuSurface &)> WhenEnabled(EnabledCondition enabled, std::function< void(int, MenuSurface &)> action)
constexpr int WrapIndex(int value, int count)
Definition MenuModel.hpp:20
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
std::function< void(int slot)> Activate
MenuItem ToItem() const
EnabledCondition Enabled
int Selected(int slot) const
std::optional< ChoiceIndex > Bind
bool Step(int slot, int direction)
void Apply(int slot) const
std::function< void(int slot, int index)> Commit
std::vector< std::string > Choices
void Select(int slot, int index)
EnabledCondition Enabled
MenuItem ToItem() const
std::function< bool(int slot, std::string_view text)> Set
std::function< std::string(int slot)> Get
std::function< MenuRow(int slot)> Describe
std::string Label
Definition MenuModel.hpp:39
EnabledCondition Enabled
MenuItem ToItem() const
std::function< std::shared_ptr< Menu >(int slot)> Build
std::string Label
MenuItem ToItem() const
MenuItem ToItem() const
std::function< bool(int slot)> Get
std::function< void(int slot)> Flip
EnabledCondition Enabled