yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_workbench_toolbar.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <cctype>
6#include <cstdio>
7#include <cstring>
8#include <functional>
9#include <string>
10#include <vector>
11
17#include "app/gui/core/icons.h"
18#include "app/gui/core/input.h"
23#include "imgui/imgui.h"
24#include "imgui/imgui_internal.h"
26
27namespace yaze::editor {
28
29namespace {
30
31constexpr float kInlineCompareRoomIdToolbarWidth = 980.0f;
32constexpr float kCompactCompareButtonWidth = 34.0f;
33constexpr int kDungeonRoomCount = 0x128;
34constexpr float kDenseToolbarButtonWidth = 84.0f;
35constexpr float kMaxComparePickerWidth = 420.0f;
36constexpr float kCompareSearchListHeight = 220.0f;
37// Within-cluster gap: tighter for buttons that share a purpose (pane toggles,
38// room write actions, right-cluster trio).
39constexpr float kToolbarActionGap = 6.0f;
40// Between-cluster gap: clear breathing room at conceptual boundaries
41// (toggles → nav, room actions → canvas modes, canvas modes → compare).
42constexpr float kToolbarClusterGap = 12.0f;
43
44constexpr char kToolbarPopupIdViewOptions[] = "##WorkbenchViewOptions";
45constexpr char kToolbarPopupIdCompareSearchList[] = "##CompareSearchList";
46constexpr char kToolbarPopupIdCompareMenu[] = "##WorkbenchCompareMenu";
51constexpr char kToolbarRoomSearchHint[] = "Type to filter rooms...";
52constexpr char kToolbarComparePickerTooltip[] = "Pick a room to compare";
53constexpr char kToolbarCompareRoomIdTooltip[] = "Compare room ID";
55 "Switch to standalone panel workflow (Ctrl+Shift+W)";
56constexpr char kToolbarRoomMatrixTooltip[] = "Open room review tools";
58 "Visit another room to seed compare history.";
60 "Visit another room first to seed compare history.";
62 float width = 0.0f;
63 float spacing = 0.0f;
64 float button_size = 0.0f;
65};
66
68 bool found = false;
69 int room_id = -1;
70};
71
72ToolbarLayout ResolveToolbarLayout(float toolbar_width) {
73 ToolbarLayout layout;
74 layout.width = toolbar_width;
75 layout.spacing = ImGui::GetStyle().ItemSpacing.x;
76 layout.button_size =
79 return layout;
80}
81
82bool DrawToolbarActionButton(const char* id, const char* label,
83 const ImVec2& size, const char* tooltip,
84 bool active = false);
85
87 int current_room, int previous_room,
88 const std::function<const std::deque<int>&()>& get_recent_rooms) {
89 if (previous_room >= 0 && previous_room != current_room) {
90 return {true, previous_room};
91 }
92 if (get_recent_rooms) {
93 const auto& mru = get_recent_rooms();
94 for (int rid : mru) {
95 if (rid != current_room) {
96 return {true, rid};
97 }
98 }
99 }
100 return {};
101}
102
103bool IconToggleButton(const char* id, const char* icon_on, const char* icon_off,
104 bool* value, float btn_size, const char* tooltip_on,
105 const char* tooltip_off) {
106 if (!value) {
107 return false;
108 }
109
110 const float btn = btn_size;
111 const float btn_w =
112 workbench::CalcIconToggleButtonWidth(icon_on, icon_off, btn);
113 const bool active = *value;
114
115 const ImVec4 col_btn = ImGui::GetStyleColorVec4(ImGuiCol_Button);
116 const ImVec4 col_active = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive);
117
118 ImGui::PushID(id);
119 gui::StyleColorGuard btn_guard(ImGuiCol_Button,
120 active ? col_active : col_btn);
121 const bool pressed =
122 ImGui::Button(active ? icon_on : icon_off, ImVec2(btn_w, btn));
123
124 if (ImGui::IsItemHovered()) {
125 ImGui::SetTooltip("%s", active ? tooltip_on : tooltip_off);
126 }
127 if (pressed) {
128 *value = !*value;
129 }
130 ImGui::PopID();
131 return pressed;
132}
133
135 const ToolbarLayout& layout) {
136 if (!viewer) {
137 return;
138 }
139
140 const float button_width = workbench::CalcIconButtonWidth(
142 if (DrawToolbarActionButton("ViewOptionsButton", kToolbarViewOptionsLabel,
143 ImVec2(button_width, layout.button_size),
144 "Canvas view options")) {
145 ImGui::OpenPopup(kToolbarPopupIdViewOptions);
146 }
147
148 if (ImGui::BeginPopup(kToolbarPopupIdViewOptions)) {
149 // Canvas overlays — generic, common, expected on by default.
150 ImGui::TextDisabled(tr("Canvas"));
151 bool v = viewer->show_grid();
152 if (ImGui::Checkbox(tr("Grid (8x8)"), &v)) {
153 viewer->set_show_grid(v);
154 }
155 v = viewer->show_object_bounds();
156 if (ImGui::Checkbox(tr("Object Bounds"), &v)) {
157 viewer->set_show_object_bounds(v);
158 }
159 v = viewer->show_coordinate_overlay();
160 if (ImGui::Checkbox(tr("Hover Coordinates"), &v)) {
162 }
163 v = viewer->show_camera_quadrant_overlay();
164 if (ImGui::Checkbox(tr("Camera Quadrants"), &v)) {
166 }
167
168 // Authoring overlays — specialized, infrequent. Grouped together so the
169 // common four don't get visually drowned by Oracle/track-specific ones.
170 ImGui::Spacing();
171 ImGui::Separator();
172 ImGui::TextDisabled(tr("Authoring"));
173 v = viewer->show_track_collision_overlay();
174 if (ImGui::Checkbox(tr("Track Collision"), &v)) {
176 }
177 v = viewer->show_custom_collision_overlay();
178 if (ImGui::Checkbox(tr("Custom Collision"), &v)) {
180 }
181 v = viewer->show_water_fill_overlay();
182 if (ImGui::Checkbox(tr("Water Fill (Oracle)"), &v)) {
184 }
185 v = viewer->show_minecart_sprite_overlay();
186 if (ImGui::Checkbox(tr("Minecart Pathing"), &v)) {
188 }
189 v = viewer->show_track_gap_overlay();
190 if (ImGui::Checkbox(tr("Track Gaps"), &v)) {
192 }
193 v = viewer->show_track_route_overlay();
194 if (ImGui::Checkbox(tr("Track Routes"), &v)) {
196 }
197 v = viewer->show_custom_objects_overlay();
198 if (ImGui::Checkbox(tr("Custom Objects (Oracle)"), &v)) {
200 }
201 if (ImGui::IsItemHovered()) {
202 ImGui::SetTooltip(
203 tr("Highlight custom-draw objects (IDs 0x31/0x32)\n"
204 "with a cyan overlay showing position and subtype."));
205 }
206 ImGui::EndPopup();
207 }
208}
209
211 const ToolbarLayout& toolbar_layout) {
212 if (!layout) {
213 return;
214 }
215
216 const float mode_height = toolbar_layout.button_size;
217 const float connected_width =
219 if (DrawToolbarActionButton("CanvasModeConnected", kToolbarModeConnectedLabel,
220 ImVec2(connected_width, mode_height),
222 ? "Return to single-room canvas"
223 : "Stitched Rooms: browse the current room "
224 "and its neighbors as one canvas",
227 }
228}
229
230bool DrawToolbarActionButton(const char* id, const char* label,
231 const ImVec2& size, const char* tooltip,
232 bool active) {
233 ImGui::PushID(id);
234 const bool pressed = gui::ToggleButton(label, active, size);
235 ImGui::PopID();
236 if (tooltip && *tooltip && ImGui::IsItemHovered()) {
237 ImGui::SetTooltip("%s", tooltip);
238 }
239 return pressed;
240}
241
243 int current_room_id, int* compare_room_id,
244 const std::function<const std::deque<int>&()>& get_recent_rooms,
245 char* search_buf, size_t search_buf_size,
246 const project::YazeProject* project) {
247 if (!compare_room_id || *compare_room_id < 0) {
248 return;
249 }
250 const bool can_search = search_buf != nullptr && search_buf_size > 1;
251 const char* filter = can_search ? search_buf : "";
252
253 char preview[128];
254 const auto label =
255 dungeon_project_labels::GetRoomLabel(project, *compare_room_id);
256 snprintf(preview, sizeof(preview), "[%03X] %s", *compare_room_id,
257 label.c_str());
258
259 auto to_lower = [](unsigned char c) {
260 return static_cast<char>(std::tolower(c));
261 };
262 auto icontains = [&](const std::string& haystack,
263 const char* needle) -> bool {
264 if (!needle || *needle == '\0') {
265 return true;
266 }
267 const size_t nlen = std::strlen(needle);
268 for (size_t i = 0; i + nlen <= haystack.size(); ++i) {
269 bool match = true;
270 for (size_t j = 0; j < nlen; ++j) {
271 if (to_lower(static_cast<unsigned char>(haystack[i + j])) !=
272 to_lower(static_cast<unsigned char>(needle[j]))) {
273 match = false;
274 break;
275 }
276 }
277 if (match)
278 return true;
279 }
280 return false;
281 };
282
283 // Picker: MRU + searchable full list.
284 ImGui::SetNextItemWidth(std::clamp(ImGui::GetContentRegionAvail().x, 120.0f,
286 if (ImGui::BeginCombo("##CompareRoomPicker", preview,
287 ImGuiComboFlags_HeightLarge)) {
288 ImGui::TextDisabled(ICON_MD_HISTORY " Recent");
289 if (get_recent_rooms) {
290 const auto& mru = get_recent_rooms();
291 for (int rid : mru) {
292 if (rid == current_room_id) {
293 continue;
294 }
295 char item[128];
296 const auto rid_label =
298 snprintf(item, sizeof(item), "[%03X] %s", rid, rid_label.c_str());
299 const bool is_selected = (rid == *compare_room_id);
300 if (ImGui::Selectable(item, is_selected)) {
301 *compare_room_id = rid;
302 }
303 }
304 }
305
306 ImGui::Separator();
307 ImGui::TextDisabled(ICON_MD_SEARCH " Search");
308 ImGui::SetNextItemWidth(-1.0f);
309 if (can_search) {
310 ImGui::InputTextWithHint("##CompareSearch", kToolbarRoomSearchHint,
311 search_buf, search_buf_size);
312 } else {
313 ImGui::TextDisabled(tr("Search unavailable"));
314 }
315
316 ImGui::Spacing();
317 std::vector<int> filtered_rooms;
318 filtered_rooms.reserve(kDungeonRoomCount);
319 for (int rid = 0; rid < kDungeonRoomCount; ++rid) {
320 if (rid == current_room_id) {
321 continue;
322 }
323 const auto rid_label = dungeon_project_labels::GetRoomLabel(project, rid);
324 char hex_buf[8];
325 snprintf(hex_buf, sizeof(hex_buf), "%03X", rid);
326 if (!icontains(rid_label, filter) && !icontains(hex_buf, filter)) {
327 continue;
328 }
329 filtered_rooms.push_back(rid);
330 }
331
332 ImGui::BeginChild(kToolbarPopupIdCompareSearchList,
333 ImVec2(0, kCompareSearchListHeight), true);
334 ImGuiListClipper clipper;
335 clipper.Begin(static_cast<int>(filtered_rooms.size()));
336 while (clipper.Step()) {
337 for (int idx = clipper.DisplayStart; idx < clipper.DisplayEnd; ++idx) {
338 const int rid = filtered_rooms[idx];
339 const auto rid_label =
341 char item[128];
342 snprintf(item, sizeof(item), "[%03X] %s", rid, rid_label.c_str());
343 const bool is_selected = (rid == *compare_room_id);
344 if (ImGui::Selectable(item, is_selected)) {
345 *compare_room_id = rid;
346 }
347 }
348 }
349 ImGui::EndChild();
350
351 ImGui::EndCombo();
352 }
353 if (ImGui::IsItemHovered()) {
354 ImGui::SetTooltip("%s", kToolbarComparePickerTooltip);
355 }
356}
357
359 const ToolbarLayout& layout) {
360 if (!p.layout || !p.current_room_id || !p.compare_room_id ||
362 return;
363 }
364
368
369 const bool compare_active = *p.split_view_enabled;
370 const char* tooltip = compare_active ? "Compare settings" : "Start compare";
372 "CompareMenuButton", kToolbarStartCompareLabel,
373 ImVec2(kCompactCompareButtonWidth, layout.button_size), tooltip,
374 compare_active)) {
375 ImGui::OpenPopup(kToolbarPopupIdCompareMenu);
376 }
377
378 if (!ImGui::BeginPopup(kToolbarPopupIdCompareMenu)) {
379 return;
380 }
381
382 if (!compare_active) {
383 if (!def.found) {
384 ImGui::TextDisabled(tr("No recent room to compare"));
385 ImGui::Separator();
386 }
387 if (!def.found) {
388 ImGui::BeginDisabled();
389 }
390 if (ImGui::MenuItem(tr("Start Compare"))) {
392 *p.split_view_enabled = true;
393 *p.compare_room_id = def.room_id;
394 }
395 if (!def.found) {
396 ImGui::EndDisabled();
397 }
398 } else {
399 ImGui::TextDisabled(tr("Compare Room"));
402 p.primary_viewer ? p.primary_viewer->project() : nullptr);
404 uint16_t cmp = static_cast<uint16_t>(
405 std::clamp(*p.compare_room_id, 0, kDungeonRoomCount - 1));
406 if (auto res = gui::InputHexWordEx("##CompareRoomId", &cmp,
407 kDenseToolbarButtonWidth + 6.0f, true);
408 res.ShouldApply()) {
409 *p.compare_room_id = std::clamp<int>(cmp, 0, kDungeonRoomCount - 1);
410 }
411 if (ImGui::IsItemHovered()) {
412 ImGui::SetTooltip("%s", kToolbarCompareRoomIdTooltip);
413 }
414 }
415
416 ImGui::Separator();
417 if (ImGui::MenuItem(tr("Swap Rooms"))) {
418 const int old_current = *p.current_room_id;
419 const int old_compare = *p.compare_room_id;
420 *p.compare_room_id = old_current;
421 if (p.on_room_selected) {
422 p.on_room_selected(old_compare);
423 } else {
424 *p.current_room_id = old_compare;
425 }
426 }
427 if (ImGui::MenuItem(tr("Sync View"), nullptr, p.layout->sync_split_view)) {
429 }
430 if (ImGui::MenuItem(tr("End Compare"))) {
431 *p.split_view_enabled = false;
432 }
433 }
434
435 ImGui::EndPopup();
436}
437
438} // namespace
439
441 (void)toolbar_width;
442 return true;
443}
444
446 if (!p.layout || !p.current_room_id || !p.split_view_enabled ||
447 !p.compare_room_id) {
448 ImGui::TextDisabled(tr("Workbench toolbar not wired"));
449 return false;
450 }
451
452 const ToolbarLayout layout =
453 ResolveToolbarLayout(std::max(ImGui::GetContentRegionAvail().x, 1.0f));
454 bool request_panel_mode = false;
455
456 // Gentle compaction only; kToolbarActionGap/ClusterGap own inter-button
457 // spacing, so don't shrink ItemSpacing.x below the theme default here.
458 const ImVec2 frame_pad = ImGui::GetStyle().FramePadding;
459 gui::StyleVarGuard frame_pad_guard(
460 ImGuiStyleVar_FramePadding, ImVec2(std::max(4.0f, frame_pad.x - 1.0f),
461 std::max(3.0f, frame_pad.y - 1.0f)));
462 gui::StyleVarGuard item_spacing_guard(
463 ImGuiStyleVar_ItemSpacing,
464 ImVec2(std::max(layout.spacing, 4.0f),
465 std::max(3.0f, ImGui::GetStyle().ItemSpacing.y - 1.0f)));
466 // ── Width budget ──────────────────────────────────────────────────
467 // Compute right-cluster total (View Options + optional Panel Mode + Room
468 // Matrix) so we know how much horizontal space the right side will claim,
469 // then decide which optional left-cluster items fit in what's left.
470 const float right_icon_width =
472 float right_actions_width = right_icon_width;
473 if (p.set_workflow_mode) {
474 right_actions_width +=
475 kToolbarActionGap +
477 }
478 bool show_room_matrix = p.open_room_matrix != nullptr;
479 const float room_matrix_chunk =
480 kToolbarActionGap + workbench::CalcIconButtonWidth(
481 kToolbarRoomReviewLabel, layout.button_size);
482 if (show_room_matrix) {
483 right_actions_width += room_matrix_chunk;
484 }
485
486 // Left-cluster widths. The essentials (sidebar/inspector toggles, NESW nav,
487 // Stitched Rooms toggle) are always shown — NESW is muscle memory and the
488 // toggles are how users restore hidden panes. Apply Room, Dungeon Map, and
489 // Compare are demoted to optional and dropped low-priority-first when the
490 // toolbar narrows.
491 const float toggle_w = layout.button_size;
492 // NESW arrow widget renders 4 ImGui::ArrowButtons (each ~GetFrameHeight wide
493 // ≈ button_size after the StyleVarGuard above) with default item-spacing
494 // gaps between them. Slight overestimate is safer than under here.
495 // Cluster gap leads NESW (toggles → nav boundary).
496 const float nav_w = kToolbarClusterGap + 4.0f * layout.button_size +
497 3.0f * ImGui::GetStyle().ItemSpacing.x;
498 // Cluster gap leads Connected mode (room actions → canvas modes boundary).
499 const float stitched_w =
500 kToolbarClusterGap + workbench::CalcIconButtonWidth(
501 kToolbarModeConnectedLabel, layout.button_size);
502 const float essential_left =
503 toggle_w + kToolbarActionGap + toggle_w + nav_w + stitched_w;
504
505 const float apply_room_chunk =
506 kToolbarActionGap +
507 workbench::CalcIconButtonWidth(ICON_MD_SAVE, layout.button_size);
508 const float dungeon_map_chunk =
509 kToolbarActionGap +
510 workbench::CalcIconButtonWidth(ICON_MD_MAP, layout.button_size);
511 // Cluster gap leads Compare (canvas modes → compare boundary).
512 const float compare_chunk = kToolbarClusterGap + kCompactCompareButtonWidth;
513
514 constexpr float kSafetyMargin = 8.0f;
515 float remaining_for_optional =
516 layout.width - essential_left - right_actions_width - kSafetyMargin;
517
518 auto try_show = [&](float chunk_w) -> bool {
519 if (remaining_for_optional >= chunk_w) {
520 remaining_for_optional -= chunk_w;
521 return true;
522 }
523 return false;
524 };
525
526 // Compare > Apply Room > Dungeon Map > Room Matrix in priority because
527 // Compare is the only multi-room workflow entry point on this toolbar.
528 const bool show_compare = try_show(compare_chunk);
529 const bool show_apply_room =
530 p.on_save_room && *p.current_room_id >= 0 && try_show(apply_room_chunk);
531 const bool show_dungeon_map =
532 p.on_request_dungeon_map && try_show(dungeon_map_chunk);
533 if (show_room_matrix && remaining_for_optional < 0.0f) {
534 show_room_matrix = false;
535 right_actions_width -= room_matrix_chunk;
536 }
537
538 // Connected toolbar controls: render as a floating canvas overlay (the
539 // viewer's built-in fallback) instead of inline. Inline rendering races
540 // against the right cluster because the controls are variable-width and
541 // only present when stitched mode is active. Overlay mode anchors them to
542 // the canvas viewport corner where they belong.
543 if (p.primary_viewer) {
545 }
546
547 // ── Render row ───────────────────────────────────────────────────
548 (void)IconToggleButton("RoomsToggle", ICON_MD_LIST, ICON_MD_LIST,
549 &p.layout->show_left_sidebar, layout.button_size,
550 "Hide room browser", "Show room browser");
551 ImGui::SameLine(0.0f, kToolbarActionGap);
552 (void)IconToggleButton("InspectorToggle", ICON_MD_TUNE, ICON_MD_TUNE,
553 &p.layout->show_right_inspector, layout.button_size,
554 "Hide inspector", "Show inspector");
555
556 ImGui::SameLine(0.0f, kToolbarClusterGap);
559
560 if (show_apply_room) {
561 ImGui::SameLine(0.0f, kToolbarActionGap);
563 "ApplyRoomToolbar", ICON_MD_SAVE, layout.button_size,
564 "Apply this room into the loaded ROM buffer "
565 "(File > Save ROM persists to disk)")) {
567 }
568 }
569 if (show_dungeon_map) {
570 ImGui::SameLine(0.0f, kToolbarActionGap);
571 if (workbench::DrawHeaderIconAction("DungeonMapToolbar", ICON_MD_MAP,
572 layout.button_size,
573 "Open the Dungeon Map popup")) {
575 }
576 }
577
578 ImGui::SameLine(0.0f, kToolbarClusterGap);
579 DrawCanvasModeSelector(p.layout, layout);
580
581 if (show_compare) {
582 ImGui::SameLine(0.0f, kToolbarClusterGap);
583 DrawCompareMenu(p, layout);
584 }
585
586 const float right_start =
587 std::max(ImGui::GetCursorPosX() + kToolbarActionGap,
588 ImGui::GetWindowContentRegionMax().x - right_actions_width);
589 ImGui::SameLine(right_start, 0.0f);
590 DrawViewOptionsButton(p.primary_viewer, layout);
591 if (p.set_workflow_mode) {
592 ImGui::SameLine(0.0f, kToolbarActionGap);
594 layout.button_size,
595 kToolbarPanelWorkflowTooltip)) {
596 request_panel_mode = true;
597 }
598 }
599 if (show_room_matrix) {
600 ImGui::SameLine(0.0f, kToolbarActionGap);
601 const float review_button_width = workbench::CalcIconButtonWidth(
602 kToolbarRoomReviewLabel, layout.button_size);
603 if (DrawToolbarActionButton("RoomMatrix", kToolbarRoomReviewLabel,
604 ImVec2(review_button_width, layout.button_size),
605 kToolbarRoomMatrixTooltip)) {
607 }
608 }
609
610 return request_panel_mode;
611}
612
613} // namespace yaze::editor
void SetConnectedControlsInline(bool inline_controls)
const project::YazeProject * project() const
static bool Draw(const char *id, int room_id, const std::function< void(int)> &on_navigate)
static bool Draw(const DungeonWorkbenchToolbarParams &params)
static bool ShouldShowInlineRoomNav(float toolbar_width)
static float GetStandardWidgetHeight()
RAII guard for ImGui style colors.
Definition style_guard.h:27
RAII guard for ImGui style vars.
Definition style_guard.h:68
#define ICON_MD_GRID_VIEW
Definition icons.h:897
#define ICON_MD_VIEW_QUILT
Definition icons.h:2094
#define ICON_MD_SEARCH
Definition icons.h:1673
#define ICON_MD_COMPARE_ARROWS
Definition icons.h:448
#define ICON_MD_TUNE
Definition icons.h:2022
#define ICON_MD_MAP
Definition icons.h:1173
#define ICON_MD_VISIBILITY
Definition icons.h:2101
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_HISTORY
Definition icons.h:946
void DrawCompareMenu(const DungeonWorkbenchToolbarParams &p, const ToolbarLayout &layout)
void DrawComparePicker(int current_room_id, int *compare_room_id, const std::function< const std::deque< int > &()> &get_recent_rooms, char *search_buf, size_t search_buf_size, const project::YazeProject *project)
bool IconToggleButton(const char *id, const char *icon_on, const char *icon_off, bool *value, float btn_size, const char *tooltip_on, const char *tooltip_off)
void DrawCanvasModeSelector(DungeonWorkbenchLayoutState *layout, const ToolbarLayout &toolbar_layout)
bool DrawToolbarActionButton(const char *id, const char *label, const ImVec2 &size, const char *tooltip, bool active=false)
void DrawViewOptionsButton(DungeonCanvasViewer *viewer, const ToolbarLayout &layout)
CompareDefaultResult PickDefaultCompareRoom(int current_room, int previous_room, const std::function< const std::deque< int > &()> &get_recent_rooms)
std::string GetRoomLabel(const project::YazeProject *project, int room_id)
float CalcIconButtonWidth(const char *icon, float button_height)
float CalcIconToggleButtonWidth(const char *icon_on, const char *icon_off, float button_height)
bool DrawHeaderIconAction(const char *id, const char *icon, float button_size, const char *tooltip, bool active=false)
Editors are the view controllers for the application.
bool ToggleButton(const char *label, bool active, const ImVec2 &size)
InputHexResult InputHexWordEx(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:427
std::function< const std::deque< int > &()> get_recent_rooms
bool ShouldApply() const
Definition input.h:48
static constexpr float kIconButtonSmall
Definition ui_config.h:61
Modern project structure with comprehensive settings consolidation.
Definition project.h:172