Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
OptionsSavePlayback.cpp
Go to the documentation of this file.
2
3#include "FontRuntime.hpp"
5
6#include <algorithm>
7#include <memory>
8#include <string_view>
9
10namespace monopoly::optionsui
11{
12 namespace
13 {
14 inline constexpr data::DataTag SlotIdleTag = 0x006B;
15 inline constexpr data::DataTag SlotHighlightTag = 0x006C;
16 inline constexpr std::uint16_t TitlePriority = 100;
17 inline constexpr std::uint16_t TextPriorityBase = 101;
18 inline constexpr std::uint16_t ButtonPriority = 150;
19 inline constexpr std::uint32_t OkayTextId = 933;
20 inline constexpr std::uint32_t CancelTextId = 932;
21
23 {
25 }
26
28 data::BoardEdition edition, FileDialogMode mode) noexcept
29 {
30 const data::DataTag tag = edition == data::BoardEdition::Usa
31 ? (mode == FileDialogMode::Load ? 0x0255 : 0x02A4)
32 : (mode == FileDialogMode::Load ? 0x03E0 : 0x0430);
34 }
35
36 [[nodiscard]] data::LegacyBitmapRGBA8 blankImage(
37 std::uint32_t width, std::uint32_t height)
38 {
39 data::LegacyBitmapRGBA8 result{width, height, {}};
40 result.pixels.assign(
41 static_cast<std::size_t>(width) * height * 4U, 0U);
42 return result;
43 }
44
45 [[nodiscard]] std::expected<std::u16string, std::string> languageText(
46 const engine::SequencePlayback& playback, std::uint32_t messageId)
47 {
48 const auto resources = playback.resources();
49 if (!resources) return std::unexpected("Options save dialog has no resources");
50 const auto language = resources->language();
51 if (!language || !language->catalog)
52 return std::unexpected("Options save dialog has no language catalog");
53 const auto text = language->catalog->message(messageId);
54 if (!text) return std::unexpected(text.error().detail);
55 return **text;
56 }
57
58 [[nodiscard]] std::expected<data::LegacyBitmapRGBA8, std::string>
59 renderInBox(fonts::Runtime& fontRuntime, std::u16string_view text,
60 std::uint32_t width, std::uint32_t height, int x, int y)
61 {
62 auto result = blankImage(width, height);
63 if (text.empty()) return result;
64 const auto blitted = fontRuntime.blitText(
65 result, text, x, y, 0x00FFFFFFU);
66 if (!blitted) return std::unexpected(blitted.error().detail);
67 return result;
68 }
69
70 [[nodiscard]] std::u16string slotText(
71 const SaveRuntimeState& state, std::size_t slot)
72 {
73 if (state.dialog == FileDialogMode::Save &&
74 state.selectedSlot == static_cast<int>(slot))
75 {
76 auto text = state.draftDescription;
77 text.push_back(u'_');
78 return text;
79 }
80 if (state.slots[slot].occupied)
81 return state.slots[slot].metadata.description;
82 return {};
83 }
84 }
85
86 std::expected<void, std::string> SavePlayback::ensureTextSurfaces(
87 fonts::Runtime& fontRuntime, engine::SequencePlayback& playback)
88 {
89 if (!fontRuntime.ready())
90 return std::unexpected("Options save dialog font runtime is not ready");
91 for (auto& surface : slotText_)
92 {
93 if (surface) continue;
94 const auto created = playback.runtimeBitmaps().create(496, 34, true);
95 if (!created) return std::unexpected(created.error());
96 surface = *created;
97 }
98
99 if (!okayText_)
100 {
101 const auto text = languageText(playback, OkayTextId);
102 if (!text) return std::unexpected(text.error());
103 const auto rendered = fontRuntime.render(std::u16string_view(*text), 0x00FFFFFFU);
104 if (!rendered) return std::unexpected(rendered.error().detail);
105 const auto created = playback.runtimeBitmaps().create(
106 rendered->width, rendered->height, true);
107 if (!created) return std::unexpected(created.error());
108 okayText_ = *created;
109 const auto updated = playback.runtimeBitmaps().update(*okayText_, *rendered);
110 if (!updated) return std::unexpected(updated.error());
111 }
112
113 if (!cancelText_)
114 {
115 const auto text = languageText(playback, CancelTextId);
116 if (!text) return std::unexpected(text.error());
117 const auto rendered = fontRuntime.render(std::u16string_view(*text), 0x00FFFFFFU);
118 if (!rendered) return std::unexpected(rendered.error().detail);
119 const auto created = playback.runtimeBitmaps().create(
120 rendered->width, rendered->height, true);
121 if (!created) return std::unexpected(created.error());
122 cancelText_ = *created;
123 const auto updated = playback.runtimeBitmaps().update(*cancelText_, *rendered);
124 if (!updated) return std::unexpected(updated.error());
125 }
126
127 return {};
128 }
129
130 std::expected<void, std::string> SavePlayback::refreshTextSurfaces(
131 const SaveRuntimeState& state, fonts::Runtime& fontRuntime,
132 engine::SequencePlayback& playback)
133 {
134 const auto ready = ensureTextSurfaces(fontRuntime, playback);
135 if (!ready) return ready;
136
137 for (std::size_t slot = 0; slot < SaveSlotCount; ++slot)
138 {
139 const auto image = renderInBox(
140 fontRuntime, slotText(state, slot), 496, 34, 13, 8);
141 if (!image) return std::unexpected(image.error());
142 const auto updated = playback.runtimeBitmaps().update(
143 *slotText_[slot], *image);
144 if (!updated) return std::unexpected(updated.error());
145 if (visible_)
146 {
147 const auto forced = playback.forceRedraw(
148 *slotText_[slot],
149 static_cast<std::uint16_t>(TextPriorityBase + slot));
150 if (!forced) return forced;
151 }
152 }
153 return {};
154 }
155
156 std::expected<void, std::string> SavePlayback::show(
157 const SaveRuntimeState& state, fonts::Runtime& fontRuntime,
158 engine::SequencePlayback& playback)
159 {
160 const auto resources = playback.resources();
161 if (!resources) return std::unexpected("Options save dialog has no resources");
162
163 const auto textReady = refreshTextSurfaces(state, fontRuntime, playback);
164 if (!textReady) return textReady;
165
166 title_ = titleSequence(resources->context().board, state.dialog);
167 const auto titleStarted = playback.startXY(*title_, TitlePriority, 70, 0);
168 if (!titleStarted) return std::unexpected(titleStarted.error());
169
170 for (std::size_t slot = 0; slot < SaveSlotCount; ++slot)
171 {
172 const bool selected = state.selectedSlot == static_cast<int>(slot);
173 const auto background = mainSequence(
174 selected ? SlotHighlightTag : SlotIdleTag);
175 slotBackgrounds_[slot] = background;
176 const auto top = 100 + static_cast<int>(slot) * 68;
177 const auto started = playback.startXY(
178 background, static_cast<std::uint16_t>(100 + slot), 100, top);
179 if (!started) return std::unexpected(started.error());
180
181 const auto textStarted = playback.startXY(
182 *slotText_[slot], static_cast<std::uint16_t>(TextPriorityBase + slot),
183 152, 112 + static_cast<int>(slot) * 68);
184 if (!textStarted) return std::unexpected(textStarted.error());
185 }
186
187 const auto okayStarted = playback.startXY(
188 *okayText_, ButtonPriority, 500, 430);
189 if (!okayStarted) return std::unexpected(okayStarted.error());
190 const auto cancelStarted = playback.startXY(
191 *cancelText_, ButtonPriority, 600, 430);
192 if (!cancelStarted) return std::unexpected(cancelStarted.error());
193
194 visible_ = true;
195 mode_ = state.dialog;
196 selectedSlot_ = state.selectedSlot;
197 revision_ = state.revision;
198 return {};
199 }
200
201 std::expected<void, std::string> SavePlayback::hide(
202 engine::SequencePlayback& playback)
203 {
204 if (!visible_) return {};
205 if (title_)
206 {
207 const auto stopped = playback.stop(*title_, TitlePriority);
208 if (!stopped) return std::unexpected(stopped.error());
209 }
210 for (std::size_t slot = 0; slot < SaveSlotCount; ++slot)
211 {
212 const auto stoppedBackground = playback.stop(
213 slotBackgrounds_[slot], static_cast<std::uint16_t>(100 + slot));
214 if (!stoppedBackground) return std::unexpected(stoppedBackground.error());
215 if (slotText_[slot])
216 {
217 const auto stoppedText = playback.stop(
218 *slotText_[slot], static_cast<std::uint16_t>(TextPriorityBase + slot));
219 if (!stoppedText) return std::unexpected(stoppedText.error());
220 }
221 }
222 if (okayText_)
223 {
224 const auto stopped = playback.stop(*okayText_, ButtonPriority);
225 if (!stopped) return std::unexpected(stopped.error());
226 }
227 if (cancelText_)
228 {
229 const auto stopped = playback.stop(*cancelText_, ButtonPriority);
230 if (!stopped) return std::unexpected(stopped.error());
231 }
232
233 visible_ = false;
234 mode_ = FileDialogMode::None;
235 selectedSlot_ = -1;
236 title_.reset();
237 return {};
238 }
239
240 std::expected<void, std::string> SavePlayback::refreshSelection(
241 const SaveRuntimeState& state, engine::SequencePlayback& playback)
242 {
243 if (state.selectedSlot == selectedSlot_) return {};
244 for (std::size_t slot = 0; slot < SaveSlotCount; ++slot)
245 {
246 const bool selected = state.selectedSlot == static_cast<int>(slot);
247 const auto desired = mainSequence(
248 selected ? SlotHighlightTag : SlotIdleTag);
249 if (desired == slotBackgrounds_[slot]) continue;
250 const auto priority = static_cast<std::uint16_t>(100 + slot);
251 const auto top = 100 + static_cast<int>(slot) * 68;
252 const auto transitioned = playback.transitionXY(
253 slotBackgrounds_[slot], desired, priority, 100, top);
254 if (!transitioned) return std::unexpected(transitioned.error());
255 slotBackgrounds_[slot] = desired;
256 }
257 selectedSlot_ = state.selectedSlot;
258 return {};
259 }
260
261 std::expected<void, std::string> SavePlayback::sync(
264 {
265 const bool desiredVisible =
267 state.dialog != FileDialogMode::None;
268
269 if (!desiredVisible)
270 return hide(playback);
271 if (fontRuntime == nullptr)
272 return std::unexpected("Options save dialog has no font runtime");
273
274 if (!visible_ || mode_ != state.dialog)
275 {
276 if (visible_)
277 {
278 const auto hidden = hide(playback);
279 if (!hidden) return hidden;
280 }
281 return show(state, *fontRuntime, playback);
282 }
283
284 const auto selection = refreshSelection(state, playback);
285 if (!selection) return selection;
286 if (revision_ != state.revision)
287 {
288 const auto refreshed = refreshTextSurfaces(state, *fontRuntime, playback);
289 if (!refreshed) return refreshed;
290 revision_ = state.revision;
291 }
292 return {};
293 }
294
296 {
297 visible_ = false;
298 mode_ = FileDialogMode::None;
299 selectedSlot_ = -1;
300 revision_ = 0;
301 title_.reset();
302 slotBackgrounds_ = {};
303 slotText_ = {};
304 okayText_.reset();
305 cancelText_.reset();
306 }
307}
std::string_view text
LegacyBitmapRGBA8 image
int top
std::uint16_t priority
PlayerNumber slot
std::uint32_t height
std::uint32_t width
unsigned state
unsigned index
std::expected< void, std::string > sync(const SaveRuntimeState &state, display::Screen2D desiredView, fonts::Runtime *fontRuntime, engine::SequencePlayback &playback)
std::uint32_t DataId
Definition DataBanks.hpp:10
std::uint16_t DataTag
Definition DataBanks.hpp:11
constexpr DataId packDataId(std::uint16_t group, DataTag tag) noexcept
Definition DataBanks.hpp:39
constexpr std::size_t SaveSlotCount
std::shared_ptr< const data::ResourceSnapshot > resources() noexcept
constexpr data::DataId mainSequence(data::DataTag tag) noexcept