yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_workbench_panel.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cctype>
5#include <cmath>
6#include <cstdint>
7#include <cstdio>
8#include <cstring>
9#include <utility>
10#include <vector>
11
18#include "app/gui/core/icons.h"
19#include "app/gui/core/input.h"
24#include "imgui/imgui.h"
25#include "rom/rom.h"
31
32namespace yaze::editor {
33
34namespace {
35
36// Object type category names based on ID range
37const char* GetObjectCategory(int object_id) {
38 if (object_id < 0x100)
39 return "Standard";
40 if (object_id < 0x200)
41 return "Extended";
42 if (object_id >= 0xF80)
43 return "Special";
44 return "Unknown";
45}
46
47// Derive a short dungeon-group label from a room's blockset value.
48// Blockset 0-12 map to vanilla ALTTP dungeons; higher values are custom.
49const char* GetBlocksetGroupName(uint8_t blockset) {
50 static const char* kGroupNames[] = {
51 "HC/Sewers", // 0
52 "Eastern", // 1
53 "Desert", // 2
54 "Hera", // 3
55 "A-Tower", // 4
56 "PoD", // 5
57 "Swamp", // 6
58 "Skull", // 7
59 "Thieves", // 8
60 "Ice", // 9
61 "Misery", // 10
62 "Turtle", // 11
63 "GT", // 12
64 };
65 constexpr size_t kCount = sizeof(kGroupNames) / sizeof(kGroupNames[0]);
66 return blockset < kCount ? kGroupNames[blockset] : "Custom";
67}
68
69// Pot item names for the inspector
70const char* GetPotItemName(uint8_t item) {
71 static const char* kNames[] = {
72 "Nothing", "Green Rupee", "Rock", "Bee",
73 "Heart (4)", "Bomb (4)", "Heart", "Blue Rupee",
74 "Key", "Arrow (5)", "Bomb (1)", "Heart",
75 "Magic (Small)", "Full Magic", "Cucco", "Green Soldier",
76 "Bush Stal", "Blue Soldier", "Landmine", "Heart",
77 "Fairy", "Heart", "Nothing (22)", "Hole",
78 "Warp", "Staircase", "Bombable", "Switch",
79 };
80 constexpr size_t kCount = sizeof(kNames) / sizeof(kNames[0]);
81 return item < kCount ? kNames[item] : "Unknown";
82}
83
84} // namespace
85
87 DungeonRoomSelector* room_selector, int* current_room_id,
88 std::function<void(int)> on_room_selected,
89 std::function<void(int, RoomSelectionIntent)> on_room_selected_with_intent,
90 std::function<void(int)> on_save_room,
91 std::function<DungeonCanvasViewer*()> get_viewer,
92 std::function<DungeonCanvasViewer*()> get_compare_viewer,
93 std::function<const std::deque<int>&()> get_recent_rooms,
94 std::function<void(int)> forget_recent_room,
95 std::function<void(const std::string&)> show_panel,
96 std::function<void(bool)> set_workflow_mode, Rom* rom)
97 : room_selector_(room_selector),
98 current_room_id_(current_room_id),
99 on_room_selected_(std::move(on_room_selected)),
100 on_room_selected_with_intent_(std::move(on_room_selected_with_intent)),
101 on_save_room_(std::move(on_save_room)),
102 get_viewer_(std::move(get_viewer)),
103 get_compare_viewer_(std::move(get_compare_viewer)),
104 get_recent_rooms_(std::move(get_recent_rooms)),
105 forget_recent_room_(std::move(forget_recent_room)),
106 show_panel_(std::move(show_panel)),
107 set_workflow_mode_(std::move(set_workflow_mode)),
108 rom_(rom) {}
109
110std::string DungeonWorkbenchPanel::GetId() const {
111 return "dungeon.workbench";
112}
114 return "Dungeon Workbench";
115}
117 return ICON_MD_WORKSPACES;
118}
120 return "Dungeon";
121}
123 return 10;
124}
125
127 rom_ = rom;
128 room_dungeon_cache_.clear();
130}
131
132void DungeonWorkbenchPanel::Draw(bool* p_open) {
133 (void)p_open;
134 const auto& theme = AgentUI::GetTheme();
135
136 if (!rom_ || !rom_->is_loaded()) {
137 ImGui::TextDisabled(ICON_MD_INFO " Load a ROM to edit dungeon rooms.");
138 return;
139 }
141 ImGui::TextColored(theme.text_error_red, "Dungeon Workbench not wired");
142 return;
143 }
144
145 DungeonCanvasViewer* primary_viewer = get_viewer_ ? get_viewer_() : nullptr;
146 DungeonCanvasViewer* compare_viewer =
148
149 if (current_room_id_) {
151 params.layout = &layout_state_;
156 params.primary_viewer = primary_viewer;
157 params.compare_viewer = compare_viewer;
163 const bool request_panel_workflow = DungeonWorkbenchToolbar::Draw(params);
164 if (request_panel_workflow && set_workflow_mode_) {
165 // Defer panel visibility mutation until toolbar child/table scopes closed.
166 set_workflow_mode_(false);
167 return;
168 }
169 ImGui::Spacing();
170 }
171
172 constexpr ImGuiTableFlags kLayoutFlags =
173 ImGuiTableFlags_Resizable | ImGuiTableFlags_NoBordersInBody |
174 ImGuiTableFlags_NoPadInnerX | ImGuiTableFlags_NoPadOuterX;
175
176 const bool show_left = layout_state_.show_left_sidebar;
177 const bool show_right = layout_state_.show_right_inspector;
178
179 // Detect collapse->expand transitions. When a sidebar re-expands, bump a
180 // table generation counter so ImGui sees a fresh table ID and applies the
181 // TableSetupColumn initial widths instead of restoring cached collapsed widths.
182 const bool left_just_expanded = show_left && !prev_show_left_;
183 const bool right_just_expanded = show_right && !prev_show_right_;
184 prev_show_left_ = show_left;
185 prev_show_right_ = show_right;
186 if (left_just_expanded || right_just_expanded) {
188 }
189 char table_id[64];
190 std::snprintf(table_id, sizeof(table_id), "##DungeonWorkbenchLayout_%d",
192
193 if (!ImGui::BeginTable(table_id, 3, kLayoutFlags)) {
194 return;
195 }
196
198 const float rail_w =
199 std::max({32.0f, btn + 8.0f, gui::LayoutHelpers::GetMinTouchTarget()});
200
201 const float left_w = show_left ? layout_state_.left_width : rail_w;
202 const float right_w = show_right ? layout_state_.right_width : rail_w;
203
204 ImGuiTableColumnFlags left_flags = ImGuiTableColumnFlags_WidthFixed;
205 if (!show_left) {
206 left_flags |= ImGuiTableColumnFlags_NoResize;
207 }
208 ImGuiTableColumnFlags right_flags = ImGuiTableColumnFlags_WidthFixed;
209 if (!show_right) {
210 right_flags |= ImGuiTableColumnFlags_NoResize;
211 }
212
213 ImGui::TableSetupColumn("Sidebar", left_flags, left_w);
214 ImGui::TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch);
215 ImGui::TableSetupColumn("Inspector", right_flags, right_w);
216 ImGui::TableNextRow();
217
218 float measured_left_w = 0.0f;
219 float measured_right_w = 0.0f;
220
221 // Sidebar: room navigation (list + filter)
222 ImGui::TableNextColumn();
223 if (show_left) {
224 measured_left_w = ImGui::GetContentRegionAvail().x;
225 const bool sidebar_open = gui::LayoutHelpers::BeginContentChild(
226 "##DungeonWorkbenchSidebar",
227 ImVec2(gui::UIConfig::kContentMinWidthSidebar, 0.0f), true);
228 if (sidebar_open) {
229 // Header with collapse button
230 ImGui::TextDisabled(ICON_MD_LIST " Rooms");
231 ImGui::SameLine(ImGui::GetWindowWidth() - btn - 8.0f);
232 if (ImGui::Button(ICON_MD_CHEVRON_LEFT "##CollapseRooms",
233 ImVec2(btn, btn))) {
235 }
236 if (ImGui::IsItemHovered()) {
237 ImGui::SetTooltip("Collapse room browser");
238 }
239
240 ImGui::Separator();
241 ImGui::PushID("RoomSelectorEmbedded");
243 ImGui::PopID();
244 }
246 } else {
247 // Collapsed sidebar rail
248 ImGui::BeginChild("##DungeonWorkbenchSidebarCollapsed", ImVec2(0, 0), true);
249 const float avail = ImGui::GetContentRegionAvail().x;
250 const float expand_btn_w = btn;
251
252 ImGui::SetCursorPosX(std::max(0.0f, (avail - expand_btn_w) * 0.5f));
253 ImGui::SetCursorPosY(8.0f);
254 if (ImGui::Button(ICON_MD_CHEVRON_RIGHT "##ExpandRooms",
255 ImVec2(expand_btn_w, btn))) {
257 }
258 if (ImGui::IsItemHovered()) {
259 ImGui::SetTooltip("Show room browser");
260 }
261
262 // Add a vertical label if there's enough height (TBD)
263
264 ImGui::EndChild();
265 }
266
267 // Canvas: main room view (minimum height so canvas never collapses)
268 ImGui::TableNextColumn();
269 const bool canvas_open = gui::LayoutHelpers::BeginContentChild(
270 "##DungeonWorkbenchCanvas",
271 ImVec2(0.0f, gui::UIConfig::kContentMinHeightCanvas), false);
272 if (canvas_open) {
273 if (primary_viewer) {
276 DrawSplitView(*primary_viewer);
277 } else {
278 primary_viewer->DrawDungeonCanvas(*current_room_id_);
279 }
280
281 // Status bar at the bottom of the canvas area
282 {
283 const char* tool_mode = get_tool_mode_ ? get_tool_mode_() : "Select";
284 auto status =
285 DungeonStatusBar::BuildState(*primary_viewer, tool_mode, false);
286 if (can_undo_)
287 status.can_undo = can_undo_();
288 if (can_redo_)
289 status.can_redo = can_redo_();
290 if (undo_desc_) {
291 static std::string s_undo_desc;
292 s_undo_desc = undo_desc_();
293 status.undo_desc =
294 s_undo_desc.empty() ? nullptr : s_undo_desc.c_str();
295 }
296 if (redo_desc_) {
297 static std::string s_redo_desc;
298 s_redo_desc = redo_desc_();
299 status.redo_desc =
300 s_redo_desc.empty() ? nullptr : s_redo_desc.c_str();
301 }
302 if (undo_depth_)
303 status.undo_depth = undo_depth_();
304 status.on_undo = on_undo_;
305 status.on_redo = on_redo_;
307 }
308 } else {
309 ImGui::TextDisabled("No active viewer");
310 }
311 }
313
314 // Inspector: placeholder (step 3 will replace this)
315 ImGui::TableNextColumn();
316 if (show_right) {
317 measured_right_w = ImGui::GetContentRegionAvail().x;
318 const bool inspector_open = gui::LayoutHelpers::BeginContentChild(
319 "##DungeonWorkbenchInspector",
320 ImVec2(gui::UIConfig::kContentMinWidthSidebar, 0.0f), true);
321 if (inspector_open) {
322 // Header with collapse button
323 ImGui::TextDisabled(ICON_MD_TUNE " Inspector");
324 ImGui::SameLine(ImGui::GetWindowWidth() - btn - 8.0f);
325 if (ImGui::Button(ICON_MD_CHEVRON_RIGHT "##CollapseInspector",
326 ImVec2(btn, btn))) {
328 }
329 if (ImGui::IsItemHovered()) {
330 ImGui::SetTooltip("Collapse inspector");
331 }
332
333 ImGui::Separator();
334 if (primary_viewer) {
335 DrawInspector(*primary_viewer);
336 } else {
337 ImGui::TextDisabled("No active viewer");
338 }
339 }
341 } else {
342 // Collapsed inspector rail
343 ImGui::BeginChild("##DungeonWorkbenchInspectorCollapsed", ImVec2(0, 0),
344 true);
345 const float avail = ImGui::GetContentRegionAvail().x;
346 const float expand_btn_w = btn;
347
348 ImGui::SetCursorPosX(std::max(0.0f, (avail - expand_btn_w) * 0.5f));
349 ImGui::SetCursorPosY(8.0f);
350 if (ImGui::Button(ICON_MD_CHEVRON_LEFT "##ExpandInspector",
351 ImVec2(expand_btn_w, btn))) {
353 }
354 if (ImGui::IsItemHovered()) {
355 ImGui::SetTooltip("Show inspector");
356 }
357 ImGui::EndChild();
358 }
359
360 // Remember widths for next frame.
361 if (show_left && measured_left_w > 0.0f) {
362 layout_state_.left_width = measured_left_w;
363 }
364 if (show_right && measured_right_w > 0.0f) {
365 layout_state_.right_width = measured_right_w;
366 }
367
368 ImGui::EndTable();
369}
370
373 return;
374 }
375
376 const auto& recent = get_recent_rooms_();
377 if (recent.empty()) {
378 return;
379 }
380 // Copy IDs up-front so we can safely mutate the underlying MRU list (close
381 // tabs) without invalidating iterators mid-loop.
382 std::vector<int> recent_ids(recent.begin(), recent.end());
383 std::vector<int> to_forget;
384
385 constexpr ImGuiTabBarFlags kFlags = ImGuiTabBarFlags_AutoSelectNewTabs |
386 ImGuiTabBarFlags_FittingPolicyScroll |
387 ImGuiTabBarFlags_TabListPopupButton;
388
389 // Adaptive frame padding: larger tabs on touch/iPad for easier tapping
390 const ImVec2 frame_pad = ImGui::GetStyle().FramePadding;
391 const bool is_touch = gui::LayoutHelpers::IsTouchDevice();
392 const float extra_y = is_touch ? 6.0f : 1.0f;
393 const float extra_x = is_touch ? 4.0f : 0.0f;
394 gui::StyleVarGuard pad_guard(
395 ImGuiStyleVar_FramePadding,
396 ImVec2(frame_pad.x + extra_x, frame_pad.y + extra_y));
397
398 if (gui::BeginThemedTabBar("##DungeonRecentRooms", kFlags)) {
399 for (int room_id : recent_ids) {
400 bool open = true;
401 const ImGuiTabItemFlags tab_flags =
402 (room_id == *current_room_id_) ? ImGuiTabItemFlags_SetSelected : 0;
403 const auto room_name = zelda3::GetRoomLabel(room_id);
404 char tab_label[64];
405 if (room_name.empty() || room_name == "Unknown") {
406 snprintf(tab_label, sizeof(tab_label), "%03X##recent_%03X", room_id,
407 room_id);
408 } else {
409 snprintf(tab_label, sizeof(tab_label), "%03X %.12s##recent_%03X",
410 room_id, room_name.c_str(), room_id);
411 }
412 const bool selected = ImGui::BeginTabItem(tab_label, &open, tab_flags);
413
414 if (!open && forget_recent_room_) {
415 to_forget.push_back(room_id);
416 }
417
418 if (ImGui::IsItemHovered()) {
419 const auto label = zelda3::GetRoomLabel(room_id);
420 ImGui::SetTooltip("[%03X] %s", room_id, label.c_str());
421 }
422
423 if (ImGui::IsItemActivated() && room_id != *current_room_id_) {
424 on_room_selected_(room_id);
425 }
426
427 if (ImGui::BeginPopupContextItem()) {
428 if (ImGui::MenuItem(ICON_MD_COMPARE_ARROWS " Compare")) {
429 split_view_enabled_ = true;
430 compare_room_id_ = room_id;
431 }
433 ImGui::MenuItem(ICON_MD_OPEN_IN_NEW " Open as Panel")) {
436 }
437 if (forget_recent_room_ && ImGui::MenuItem(ICON_MD_CLOSE " Close")) {
438 to_forget.push_back(room_id);
439 }
440 ImGui::EndPopup();
441 }
442
443 if (selected) {
444 ImGui::EndTabItem();
445 }
446 }
447
449 }
450
451 if (!to_forget.empty() && forget_recent_room_) {
452 for (int rid : to_forget) {
454 }
455 }
456}
457
461 split_view_enabled_ = false;
462 }
463 return;
464 }
465
466 // Choose a sensible default compare room (most-recent non-current).
468 if (get_recent_rooms_) {
469 for (int rid : get_recent_rooms_()) {
470 if (rid != *current_room_id_) {
471 compare_room_id_ = rid;
472 break;
473 }
474 }
475 }
476 }
477
478 if (compare_room_id_ < 0) {
479 // Nothing to compare yet.
480 split_view_enabled_ = false;
481 primary_viewer.DrawDungeonCanvas(*current_room_id_);
482 return;
483 }
484
485 constexpr ImGuiTableFlags kSplitFlags =
486 ImGuiTableFlags_Resizable | ImGuiTableFlags_NoPadOuterX |
487 ImGuiTableFlags_NoPadInnerX | ImGuiTableFlags_BordersInnerV;
488
489 if (!ImGui::BeginTable("##DungeonWorkbenchSplit", 2, kSplitFlags)) {
490 primary_viewer.DrawDungeonCanvas(*current_room_id_);
491 return;
492 }
493
494 ImGui::TableSetupColumn("Active", ImGuiTableColumnFlags_WidthStretch);
495 ImGui::TableSetupColumn("Compare", ImGuiTableColumnFlags_WidthStretch);
496 ImGui::TableNextRow();
497
498 // Active pane (minimum height so canvas never collapses)
499 ImGui::TableNextColumn();
500 const bool split_active_open = gui::LayoutHelpers::BeginContentChild(
501 "##SplitActive", ImVec2(0.0f, gui::UIConfig::kContentMinHeightCanvas),
502 false);
503 if (split_active_open) {
504 primary_viewer.DrawDungeonCanvas(*current_room_id_);
505 }
507
508 // Compare pane
509 ImGui::TableNextColumn();
510 const bool split_compare_open = gui::LayoutHelpers::BeginContentChild(
511 "##SplitCompare", ImVec2(0.0f, gui::UIConfig::kContentMinHeightCanvas),
512 false);
513 if (split_compare_open) {
514 if (auto* compare_viewer =
517 compare_viewer->canvas().ApplyScaleSnapshot(
518 primary_viewer.canvas().GetConfig());
519 }
520 compare_viewer->DrawDungeonCanvas(compare_room_id_);
521 } else {
522 ImGui::TextDisabled("No compare viewer");
523 }
524 }
526
527 ImGui::EndTable();
528}
529
531 room_dungeon_cache_.clear();
532 room_dungeon_cache_built_ = true; // Always set, even if ROM missing.
533 if (!rom_ || !rom_->is_loaded())
534 return;
535
536 // Short dungeon names for display in the inspector badge.
537 // Indices 0-13 = vanilla ALTTP dungeons; higher indices = custom/Oracle.
538 static const char* const kShortNames[] = {
539 "Sewers", "HC", "Eastern", "Desert", "A-Tower", "Swamp", "PoD",
540 "Misery", "Skull", "Ice", "Hera", "Thieves", "Turtle", "GT",
541 };
542 constexpr int kVanillaCount =
543 static_cast<int>(sizeof(kShortNames) / sizeof(kShortNames[0]));
544
545 auto AddRoom = [&](int room_id, int dungeon_id) {
546 if (room_id < 0)
547 return;
548 if (room_dungeon_cache_.contains(room_id))
549 return; // Entrance wins over spawn.
550 if (dungeon_id >= 0 && dungeon_id < kVanillaCount) {
551 room_dungeon_cache_[room_id] = kShortNames[dungeon_id];
552 } else {
553 char buf[16];
554 snprintf(buf, sizeof(buf), "Dungeon %02X", dungeon_id);
555 room_dungeon_cache_[room_id] = buf;
556 }
557 };
558
559 // Standard entrances (0x00–0x83) — authoritative dungeon assignment.
560 for (int i = 0; i < 0x84; ++i) {
561 zelda3::RoomEntrance ent(rom_, static_cast<uint8_t>(i), false);
562 int did = ent.dungeon_id_;
563 if (did >= 0 && did < kVanillaCount) {
564 room_dungeon_cache_[ent.room_] = kShortNames[did];
565 } else {
566 char buf[16];
567 snprintf(buf, sizeof(buf), "Dungeon %02X", did);
568 room_dungeon_cache_[ent.room_] = buf;
569 }
570 }
571
572 // Spawn points (0x00–0x13) — fill in rooms not covered by entrances.
573 for (int i = 0; i < 0x14; ++i) {
574 zelda3::RoomEntrance ent(rom_, static_cast<uint8_t>(i), true);
575 AddRoom(static_cast<int>(ent.room_), static_cast<int>(ent.dungeon_id_));
576 }
577}
578
582
584 constexpr ImGuiTabBarFlags kFlags = ImGuiTabBarFlags_FittingPolicyResizeDown;
585
586 if (!gui::BeginThemedTabBar("##DungeonWorkbenchInspectorTabs", kFlags)) {
587 return;
588 }
589
590 if (ImGui::BeginTabItem(ICON_MD_CASTLE " Room")) {
592
593 // --- View toggles (collapsed by default) ---
594 if (ImGui::CollapsingHeader(ICON_MD_VISIBILITY " View Toggles")) {
596 }
597
598 // --- Quick launch buttons (collapsed by default) ---
599 if (ImGui::CollapsingHeader(ICON_MD_BUILD " Tools")) {
601 }
602
603 ImGui::EndTabItem();
604 }
605 if (ImGui::BeginTabItem(ICON_MD_SELECT_ALL " Selection")) {
607 ImGui::EndTabItem();
608 }
609
611}
612
614 DungeonCanvasViewer& viewer) {
615 const auto& theme = AgentUI::GetTheme();
616
617 int room_id = viewer.current_room_id();
618 if (room_id < 0 && current_room_id_) {
619 room_id = *current_room_id_;
620 }
621
622 const std::string room_label =
623 (room_id >= 0) ? zelda3::GetRoomLabel(room_id) : std::string("None");
624
625 // Room badge: hex ID + copy button (only for valid room IDs).
626 if (room_id >= 0) {
627 ImGui::Text("Room: 0x%03X (%d)", room_id, room_id);
628 ImGui::SameLine();
629 if (ImGui::SmallButton(ICON_MD_CONTENT_COPY "##CopyRoomId")) {
630 char buf[16];
631 snprintf(buf, sizeof(buf), "0x%03X", room_id);
632 ImGui::SetClipboardText(buf);
633 }
634 if (ImGui::IsItemHovered()) {
635 ImGui::SetTooltip("Copy room ID (0x%03X) to clipboard", room_id);
636 }
637 } else {
638 ImGui::TextUnformatted("Room: None");
639 }
640
641 // Dungeon group context: prefer ROM entrance-based lookup (accurate for
642 // custom Oracle dungeons); fall back to blockset-derived name.
645 }
646 if (room_id >= 0) {
647 const char* group_name = nullptr;
648 {
649 auto cache_it = room_dungeon_cache_.find(room_id);
650 if (cache_it != room_dungeon_cache_.end() && !cache_it->second.empty()) {
651 group_name = cache_it->second.c_str();
652 }
653 }
654 if (!group_name) {
655 auto* rooms = viewer.rooms();
656 if (rooms && room_id < static_cast<int>(rooms->size())) {
657 group_name = GetBlocksetGroupName((*rooms)[room_id].blockset());
658 }
659 }
660 if (group_name) {
661 ImGui::TextDisabled(ICON_MD_CASTLE " %s – %s", group_name,
662 room_label.c_str());
663 } else {
664 ImGui::TextDisabled("%s", room_label.c_str());
665 }
666 } else {
667 ImGui::TextDisabled("%s", room_label.c_str());
668 }
669
670 // Quick actions.
671 ImGui::Spacing();
672 if (on_save_room_ && room_id >= 0) {
673 if (ImGui::Button(ICON_MD_SAVE " Save Room", ImVec2(-1, 0))) {
674 on_save_room_(room_id);
675 }
676 }
677
678 if (show_panel_) {
679 if (ImGui::Button(ICON_MD_IMAGE " Room Graphics", ImVec2(-1, 0))) {
680 show_panel_("dungeon.room_graphics");
681 }
682 if (ImGui::Button(ICON_MD_SETTINGS " Settings", ImVec2(-1, 0))) {
683 show_panel_("dungeon.settings");
684 }
685 }
686
687 // D6 Goron Mines quick-nav: jump to any of the four flagged minecart rooms.
688 if (viewer.CanNavigateRooms()) {
689 ImGui::Spacing();
690 ImGui::TextDisabled(ICON_MD_TRAIN " D6 Goron Mines");
691 if (ImGui::IsItemHovered()) {
692 ImGui::SetTooltip(
693 "Jump to a D6 Goron Mines minecart room.\n"
694 "Rooms flagged for minecart audit (2026-02-13).");
695 }
696
697 struct D6Room {
698 int id;
699 const char* label;
700 };
701 static constexpr D6Room kD6Rooms[] = {
702 {0xA8, "0xA8 Entry"},
703 {0xB8, "0xB8 L-Shape"},
704 {0xD8, "0xD8 3-Net"},
705 {0xDA, "0xDA U-Shape"},
706 };
707
708 constexpr ImGuiTableFlags kFlags =
709 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_NoPadOuterX;
710 if (ImGui::BeginTable("##D6QuickNav", 2, kFlags)) {
711 for (const auto& r : kD6Rooms) {
712 ImGui::TableNextColumn();
713 ImGui::PushID(r.id);
714 const bool is_current = (room_id == r.id);
715 if (is_current) {
716 ImGui::BeginDisabled();
717 }
718 if (ImGui::Button(r.label, ImVec2(-1, 0))) {
719 // NavigateToRoom dispatches through the viewer's configured
720 // room-swap/room-navigation callback; avoid double-calling
721 // on_room_selected_ here.
722 viewer.NavigateToRoom(r.id);
723 }
724 if (is_current) {
725 ImGui::EndDisabled();
726 }
727 if (ImGui::IsItemHovered() && !is_current) {
728 ImGui::SetTooltip("Jump to room 0x%03X", r.id);
729 }
730 ImGui::PopID();
731 }
732 ImGui::EndTable();
733 }
734 }
735
736 // Recent rooms quick-jump
738 const auto& recent = get_recent_rooms_();
739 if (recent.size() > 1) {
740 ImGui::Spacing();
741 ImGui::TextDisabled(ICON_MD_HISTORY " Recent Rooms");
742
743 ImGui::BeginChild("##RecentRoomsQuickJump", ImVec2(0, 0),
744 ImGuiChildFlags_AutoResizeY);
745 for (int rid : recent) {
746 if (rid == room_id)
747 continue;
748 ImGui::PushID(rid);
749 auto name = zelda3::GetRoomLabel(rid);
750 char label[64];
751 if (name.empty() || name == "Unknown") {
752 snprintf(label, sizeof(label), ICON_MD_ROOM " 0x%03X", rid);
753 } else {
754 snprintf(label, sizeof(label), ICON_MD_ROOM " 0x%03X %.16s", rid,
755 name.c_str());
756 }
757 if (ImGui::Selectable(label)) {
759 }
760 if (ImGui::IsItemHovered()) {
761 ImGui::SetTooltip("Jump to room 0x%03X\n%s", rid, name.c_str());
762 }
763 ImGui::PopID();
764 }
765 ImGui::EndChild();
766 }
767 }
768
769 ImGui::Separator();
770
771 // Core room properties (moved from canvas header).
772 if (auto* rooms = viewer.rooms();
773 rooms && room_id >= 0 && room_id < static_cast<int>(rooms->size())) {
774 auto& room = (*rooms)[room_id];
775
776 uint8_t blockset_val = room.blockset();
777 uint8_t palette_val = room.palette();
778 uint8_t layout_val = room.layout_id();
779 uint8_t spriteset_val = room.spriteset();
780
781 constexpr float kHexW = 92.0f;
782
783 constexpr ImGuiTableFlags kPropsFlags = ImGuiTableFlags_BordersInnerV |
784 ImGuiTableFlags_RowBg |
785 ImGuiTableFlags_NoPadOuterX;
786 if (ImGui::BeginTable("##WorkbenchRoomProps", 2, kPropsFlags)) {
787 ImGui::TableSetupColumn("Prop", ImGuiTableColumnFlags_WidthFixed, 90.0f);
788 ImGui::TableSetupColumn("Val", ImGuiTableColumnFlags_WidthStretch);
789
790 gui::LayoutHelpers::PropertyRow("Blockset", [&]() {
791 if (auto res = gui::InputHexByteEx("##Blockset", &blockset_val, 81,
792 kHexW, true);
793 res.ShouldApply()) {
794 room.SetBlockset(blockset_val);
795 if (room.rom() && room.rom()->is_loaded()) {
796 room.RenderRoomGraphics();
797 }
798 }
799 if (ImGui::IsItemHovered()) {
800 ImGui::SetTooltip("Blockset (0-51)");
801 }
802 });
803 gui::LayoutHelpers::PropertyRow("Palette", [&]() {
804 if (auto res =
805 gui::InputHexByteEx("##Palette", &palette_val, 71, kHexW, true);
806 res.ShouldApply()) {
807 room.SetPalette(palette_val);
808 if (room.rom() && room.rom()->is_loaded()) {
809 room.RenderRoomGraphics();
810 }
811 // Re-run editor sync so palette group + dependent panels update.
812 if (on_room_selected_) {
813 on_room_selected_(room_id);
814 }
815 }
816 if (ImGui::IsItemHovered()) {
817 ImGui::SetTooltip("Palette (0-47)");
818 }
819 });
820 gui::LayoutHelpers::PropertyRow("Layout", [&]() {
821 if (auto res =
822 gui::InputHexByteEx("##Layout", &layout_val, 7, kHexW, true);
823 res.ShouldApply()) {
824 room.SetLayoutId(layout_val);
825 room.MarkLayoutDirty();
826 if (room.rom() && room.rom()->is_loaded()) {
827 room.RenderRoomGraphics();
828 }
829 }
830 if (ImGui::IsItemHovered()) {
831 ImGui::SetTooltip("Layout (0-7)");
832 }
833 });
834 gui::LayoutHelpers::PropertyRow("Spriteset", [&]() {
835 if (auto res = gui::InputHexByteEx("##Spriteset", &spriteset_val, 143,
836 kHexW, true);
837 res.ShouldApply()) {
838 room.SetSpriteset(spriteset_val);
839 if (room.rom() && room.rom()->is_loaded()) {
840 room.RenderRoomGraphics();
841 }
842 }
843 if (ImGui::IsItemHovered()) {
844 ImGui::SetTooltip("Spriteset (0-8F)");
845 }
846 });
847
848 ImGui::EndTable();
849 }
850 } else {
851 ImGui::TextDisabled("Room properties unavailable");
852 }
853
854 ImGui::Spacing();
855 auto& interaction = viewer.object_interaction();
856 const bool placing = interaction.mode_manager().IsPlacementActive();
857 if (placing) {
858 ImGui::TextColored(theme.text_info, "Placement active");
859 ImGui::SameLine();
860 if (ImGui::SmallButton(ICON_MD_CLOSE " Cancel")) {
861 interaction.mode_manager().CancelCurrentMode();
862 }
863 }
864}
865
867 DungeonCanvasViewer& viewer) {
868 auto& interaction = viewer.object_interaction();
869 const auto& theme = AgentUI::GetTheme();
870
871 const int room_id = viewer.current_room_id();
872 const size_t obj_count = interaction.GetSelectionCount();
873 const bool has_entity = interaction.HasEntitySelection();
874
875 if (!has_entity && obj_count == 0) {
876 ImGui::TextDisabled(ICON_MD_INFO " Click an object or entity to inspect");
877 return;
878 }
879
880 // ── Tile Object Selection ──
881 if (obj_count > 0) {
882 ImGui::Text(ICON_MD_WIDGETS " %zu object(s)", obj_count);
883 ImGui::SameLine();
884 if (ImGui::SmallButton(ICON_MD_CLEAR " Clear")) {
885 interaction.ClearSelection();
886 }
887
888 const auto indices = interaction.GetSelectedObjectIndices();
889
890 // Multi-object summary
891 if (indices.size() > 1 && room_id >= 0 && viewer.rooms()) {
892 auto& room = (*viewer.rooms())[room_id];
893 auto& objects = room.GetTileObjects();
894 ImGui::Separator();
895 for (size_t i = 0; i < indices.size() && i < 8; ++i) {
896 size_t idx = indices[i];
897 if (idx < objects.size()) {
898 auto& obj = objects[idx];
899 std::string name = zelda3::GetObjectName(obj.id_);
900 ImGui::BulletText("0x%03X %s", obj.id_, name.c_str());
901 }
902 }
903 if (indices.size() > 8) {
904 ImGui::TextDisabled(" ... and %zu more", indices.size() - 8);
905 }
906 }
907
908 // Single-object detailed inspector
909 if (indices.size() == 1 && room_id >= 0 && viewer.rooms()) {
910 auto& room = (*viewer.rooms())[room_id];
911 auto& objects = room.GetTileObjects();
912 const size_t idx = indices.front();
913 if (idx < objects.size()) {
914 auto& obj = objects[idx];
915 const std::string obj_name = zelda3::GetObjectName(obj.id_);
916 const int subtype = zelda3::GetObjectSubtype(obj.id_);
917
918 // Name + category header
919 ImGui::Separator();
920 ImGui::TextColored(theme.text_primary, "%s", obj_name.c_str());
921 ImGui::TextDisabled("%s (Type %d) #%zu in list",
922 GetObjectCategory(obj.id_), subtype, idx);
923
924 ImGui::Spacing();
925
926 // Property table
927 constexpr ImGuiTableFlags kPropsFlags = ImGuiTableFlags_BordersInnerV |
928 ImGuiTableFlags_RowBg |
929 ImGuiTableFlags_NoPadOuterX;
930 if (ImGui::BeginTable("##SelObjProps", 2, kPropsFlags)) {
931 ImGui::TableSetupColumn("Prop", ImGuiTableColumnFlags_WidthFixed,
932 56.0f);
933 ImGui::TableSetupColumn("Val", ImGuiTableColumnFlags_WidthStretch);
934
935 // ID
937 uint16_t obj_id = static_cast<uint16_t>(obj.id_ & 0x0FFF);
938 if (auto res =
939 gui::InputHexWordEx("##SelObjId", &obj_id, 80.0f, true);
940 res.ShouldApply()) {
941 obj_id &= 0x0FFF;
942 interaction.SetObjectId(idx, static_cast<int16_t>(obj_id));
943 }
944 });
945
946 // Position
948 int pos_x = obj.x_;
949 int pos_y = obj.y_;
950 ImGui::SetNextItemWidth(60);
951 bool x_changed =
952 ImGui::DragInt("##SelObjX", &pos_x, 0.1f, 0, 63, "X:%d");
953 ImGui::SameLine();
954 ImGui::SetNextItemWidth(60);
955 bool y_changed =
956 ImGui::DragInt("##SelObjY", &pos_y, 0.1f, 0, 63, "Y:%d");
957 if (x_changed || y_changed) {
958 int delta_x = pos_x - obj.x_;
959 int delta_y = pos_y - obj.y_;
960 interaction.entity_coordinator().tile_handler().MoveObjects(
961 room_id, {idx}, delta_x, delta_y);
962 }
963 });
964
965 // Size
966 gui::LayoutHelpers::PropertyRow("Size", [&]() {
967 uint8_t size = obj.size_ & 0x0F;
968 if (auto res = gui::InputHexByteEx("##SelObjSize", &size, 0x0F,
969 60.0f, true);
970 res.ShouldApply()) {
971 interaction.SetObjectSize(idx, size);
972 }
973 });
974
975 // Layer
976 gui::LayoutHelpers::PropertyRow("Layer", [&]() {
977 int layer = static_cast<int>(obj.GetLayerValue());
978 const char* layer_names[] = {"BG1", "BG2", "BG3"};
979 ImGui::SetNextItemWidth(-1);
980 if (ImGui::Combo("##SelObjLayer", &layer, layer_names,
981 IM_ARRAYSIZE(layer_names))) {
982 layer = std::clamp(layer, 0, 2);
983 interaction.SetObjectLayer(
984 idx, static_cast<zelda3::RoomObject::LayerType>(layer));
985 }
986 });
987
988 // Pixel coords (read-only info)
989 gui::LayoutHelpers::PropertyRow("Pixel", [&]() {
990 ImGui::TextDisabled("(%d, %d)", obj.x_ * 8, obj.y_ * 8);
991 });
992
993 ImGui::EndTable();
994 }
995 }
996 }
997 }
998
999 // ── Entity Selection (Doors, Sprites, Items) ──
1000 if (has_entity && room_id >= 0 && viewer.rooms()) {
1001 const auto sel = interaction.GetSelectedEntity();
1002 auto& room = (*viewer.rooms())[room_id];
1003 ImGui::Separator();
1004
1005 switch (sel.type) {
1006 case EntityType::Door: {
1007 const auto& doors = room.GetDoors();
1008 if (sel.index < doors.size()) {
1009 const auto& door = doors[sel.index];
1010 std::string type_name(zelda3::GetDoorTypeName(door.type));
1011 std::string dir_name(zelda3::GetDoorDirectionName(door.direction));
1012
1013 ImGui::TextColored(theme.text_primary, ICON_MD_DOOR_FRONT " %s",
1014 type_name.c_str());
1015 ImGui::TextDisabled("Direction: %s Position: 0x%02X",
1016 dir_name.c_str(), door.position);
1017
1018 auto [tile_x, tile_y] = door.GetTileCoords();
1019 auto [pixel_x, pixel_y] = door.GetPixelCoords();
1020 ImGui::TextDisabled("Tile: (%d, %d) Pixel: (%d, %d)", tile_x, tile_y,
1021 pixel_x, pixel_y);
1022 }
1023 break;
1024 }
1025 case EntityType::Sprite: {
1026 const auto& sprites = room.GetSprites();
1027 if (sel.index < sprites.size()) {
1028 const auto& sprite = sprites[sel.index];
1029 std::string sprite_name = zelda3::GetSpriteLabel(sprite.id());
1030
1031 ImGui::TextColored(theme.text_primary, ICON_MD_PERSON " %s",
1032 sprite_name.c_str());
1033 ImGui::TextDisabled("ID: 0x%02X Subtype: %d Layer: %d", sprite.id(),
1034 sprite.subtype(), sprite.layer());
1035 ImGui::TextDisabled("Pos: (%d, %d) Pixel: (%d, %d)", sprite.x(),
1036 sprite.y(), sprite.x() * 16, sprite.y() * 16);
1037
1038 // Overlord check
1039 if (sprite.subtype() == 0x07 && sprite.id() >= 0x01 &&
1040 sprite.id() <= 0x1A) {
1041 std::string overlord_name = zelda3::GetOverlordLabel(sprite.id());
1042 ImGui::TextColored(theme.text_warning_yellow,
1043 ICON_MD_STAR " Overlord: %s",
1044 overlord_name.c_str());
1045 }
1046 }
1047 break;
1048 }
1049 case EntityType::Item: {
1050 const auto& items = room.GetPotItems();
1051 if (sel.index < items.size()) {
1052 const auto& pot_item = items[sel.index];
1053 const char* item_name = GetPotItemName(pot_item.item);
1054
1055 ImGui::TextColored(theme.text_primary, ICON_MD_INVENTORY_2 " %s",
1056 item_name);
1057 ImGui::TextDisabled("Item ID: 0x%02X Raw Pos: 0x%04X", pot_item.item,
1058 pot_item.position);
1059 ImGui::TextDisabled("Pixel: (%d, %d) Tile: (%d, %d)",
1060 pot_item.GetPixelX(), pot_item.GetPixelY(),
1061 pot_item.GetTileX(), pot_item.GetTileY());
1062 }
1063 break;
1064 }
1065 default:
1066 break;
1067 }
1068
1069 ImGui::Spacing();
1070 if (ImGui::SmallButton(ICON_MD_DELETE " Delete Entity")) {
1071 interaction.entity_coordinator().DeleteSelectedEntity();
1072 interaction.ClearEntitySelection();
1073 }
1074 }
1075}
1076
1078 DungeonCanvasViewer& viewer) {
1079 bool val = viewer.show_grid();
1080 if (ImGui::Checkbox("Grid (8x8)", &val))
1081 viewer.set_show_grid(val);
1082
1083 val = viewer.show_object_bounds();
1084 if (ImGui::Checkbox("Object Bounds", &val)) {
1085 viewer.set_show_object_bounds(val);
1086 }
1087
1088 val = viewer.show_coordinate_overlay();
1089 if (ImGui::Checkbox("Hover Coordinates", &val)) {
1090 viewer.set_show_coordinate_overlay(val);
1091 }
1092
1093 val = viewer.show_camera_quadrant_overlay();
1094 if (ImGui::Checkbox("Camera Quadrants", &val)) {
1096 }
1097
1098 val = viewer.show_track_collision_overlay();
1099 if (ImGui::Checkbox("Track Collision", &val)) {
1101 }
1102
1103 val = viewer.show_custom_collision_overlay();
1104 if (ImGui::Checkbox("Custom Collision", &val)) {
1106 }
1107
1108 val = viewer.show_water_fill_overlay();
1109 if (ImGui::Checkbox("Water Fill (Oracle)", &val)) {
1110 viewer.set_show_water_fill_overlay(val);
1111 }
1112
1113 val = viewer.show_minecart_sprite_overlay();
1114 if (ImGui::Checkbox("Minecart Pathing", &val)) {
1116 }
1117
1118 val = viewer.show_track_gap_overlay();
1119 if (ImGui::Checkbox("Track Gaps", &val)) {
1120 viewer.set_show_track_gap_overlay(val);
1121 }
1122
1123 val = viewer.show_track_route_overlay();
1124 if (ImGui::Checkbox("Track Routes", &val)) {
1125 viewer.set_show_track_route_overlay(val);
1126 }
1127
1128 val = viewer.show_custom_objects_overlay();
1129 if (ImGui::Checkbox("Custom Objects (Oracle)", &val)) {
1131 }
1132 if (ImGui::IsItemHovered()) {
1133 ImGui::SetTooltip(
1134 "Highlight custom-draw objects (IDs 0x31/0x32)\n"
1135 "with a cyan overlay showing position and subtype.");
1136 }
1137}
1138
1140 DungeonCanvasViewer& /*viewer*/) {
1141 if (!show_panel_) {
1142 ImGui::TextDisabled("No panel launcher available");
1143 return;
1144 }
1145
1146 constexpr ImGuiTableFlags kFlags =
1147 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_NoPadOuterX;
1148 if (!ImGui::BeginTable("##WorkbenchToolsGrid", 2, kFlags)) {
1149 return;
1150 }
1151
1152 ImGui::TableNextRow();
1153 ImGui::TableNextColumn();
1154 if (ImGui::Button(ICON_MD_WIDGETS " Objects", ImVec2(-1, 0))) {
1155 show_panel_("dungeon.object_editor");
1156 }
1157 ImGui::TableNextColumn();
1158 if (ImGui::Button(ICON_MD_PERSON " Sprites", ImVec2(-1, 0))) {
1159 show_panel_("dungeon.sprite_editor");
1160 }
1161
1162 ImGui::TableNextRow();
1163 ImGui::TableNextColumn();
1164 if (ImGui::Button(ICON_MD_INVENTORY " Items", ImVec2(-1, 0))) {
1165 show_panel_("dungeon.item_editor");
1166 }
1167 ImGui::TableNextColumn();
1168 if (ImGui::Button(ICON_MD_SETTINGS " Settings", ImVec2(-1, 0))) {
1169 show_panel_("dungeon.settings");
1170 }
1171
1172 ImGui::EndTable();
1173
1174 ImGui::Spacing();
1175 if (ImGui::Button(ICON_MD_KEYBOARD " Keyboard Shortcuts", ImVec2(-1, 0))) {
1176 show_shortcut_legend_ = true;
1177 }
1179}
1180
1181} // namespace yaze::editor
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
bool is_loaded() const
Definition rom.h:132
std::array< zelda3::Room, 0x128 > * rooms() const
DungeonObjectInteraction & object_interaction()
Handles room and entrance selection UI.
void DrawRoomSelector(RoomSelectionIntent single_click_intent=RoomSelectionIntent::kFocusInWorkbench)
static void Draw(const DungeonStatusBarState &state)
static DungeonStatusBarState BuildState(const DungeonCanvasViewer &viewer, const char *tool_mode, bool room_dirty)
std::string GetDisplayName() const override
Human-readable name shown in menus and title bars.
std::function< void(const std::string &) show_panel_)
void DrawInspectorShelfView(DungeonCanvasViewer &viewer)
DungeonWorkbenchPanel(DungeonRoomSelector *room_selector, int *current_room_id, std::function< void(int)> on_room_selected, std::function< void(int, RoomSelectionIntent)> on_room_selected_with_intent, std::function< void(int)> on_save_room, std::function< DungeonCanvasViewer *()> get_viewer, std::function< DungeonCanvasViewer *()> get_compare_viewer, std::function< const std::deque< int > &()> get_recent_rooms, std::function< void(int)> forget_recent_room, std::function< void(const std::string &)> show_panel, std::function< void(bool)> set_workflow_mode, Rom *rom=nullptr)
void DrawInspectorShelf(DungeonCanvasViewer &viewer)
DungeonWorkbenchLayoutState layout_state_
std::string GetIcon() const override
Material Design icon for this panel.
std::function< const char *()> get_tool_mode_
std::function< DungeonCanvasViewer *()> get_compare_viewer_
void DrawSplitView(DungeonCanvasViewer &primary_viewer)
std::function< const std::deque< int > &()> get_recent_rooms_
std::function< void(int, RoomSelectionIntent)> on_room_selected_with_intent_
void DrawInspector(DungeonCanvasViewer &viewer)
void DrawInspectorShelfRoom(DungeonCanvasViewer &viewer)
std::unordered_map< int, std::string > room_dungeon_cache_
int GetPriority() const override
Get display priority for menu ordering.
std::string GetEditorCategory() const override
Editor category this panel belongs to.
std::function< std::string()> redo_desc_
std::string GetId() const override
Unique identifier for this panel.
void Draw(bool *p_open) override
Draw the panel content.
void DrawInspectorShelfTools(DungeonCanvasViewer &viewer)
void DrawInspectorShelfSelection(DungeonCanvasViewer &viewer)
std::function< void(bool)> set_workflow_mode_
std::function< DungeonCanvasViewer *()> get_viewer_
std::function< std::string()> undo_desc_
static bool Draw(const DungeonWorkbenchToolbarParams &params)
bool IsPlacementActive() const
Check if any placement mode is active.
CanvasConfig & GetConfig()
Definition canvas.h:324
static float GetMinTouchTarget()
static float GetTouchSafeWidgetHeight()
static bool BeginContentChild(const char *id, const ImVec2 &min_size, bool border=false, ImGuiWindowFlags flags=0)
static void EndContentChild()
static void PropertyRow(const char *label, std::function< void()> widget_callback)
RAII guard for ImGui style vars.
Definition style_guard.h:68
Dungeon Room Entrance or Spawn Point.
#define ICON_MD_ROOM
Definition icons.h:1617
#define ICON_MD_SETTINGS
Definition icons.h:1699
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_TRAIN
Definition icons.h:2005
#define ICON_MD_STAR
Definition icons.h:1848
#define ICON_MD_COMPARE_ARROWS
Definition icons.h:448
#define ICON_MD_TUNE
Definition icons.h:2022
#define ICON_MD_VISIBILITY
Definition icons.h:2101
#define ICON_MD_WIDGETS
Definition icons.h:2156
#define ICON_MD_CASTLE
Definition icons.h:380
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_INVENTORY
Definition icons.h:1011
#define ICON_MD_KEYBOARD
Definition icons.h:1028
#define ICON_MD_DOOR_FRONT
Definition icons.h:613
#define ICON_MD_CHEVRON_LEFT
Definition icons.h:405
#define ICON_MD_IMAGE
Definition icons.h:982
#define ICON_MD_CLEAR
Definition icons.h:416
#define ICON_MD_PERSON
Definition icons.h:1415
#define ICON_MD_BUILD
Definition icons.h:328
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_SELECT_ALL
Definition icons.h:1680
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_OPEN_IN_NEW
Definition icons.h:1354
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
#define ICON_MD_INVENTORY_2
Definition icons.h:1012
#define ICON_MD_CLOSE
Definition icons.h:418
#define ICON_MD_CHEVRON_RIGHT
Definition icons.h:406
#define ICON_MD_WORKSPACES
Definition icons.h:2186
#define ICON_MD_HISTORY
Definition icons.h:946
const AgentUITheme & GetTheme()
Editors are the view controllers for the application.
RoomSelectionIntent
Intent for room selection in the dungeon editor.
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
void EndThemedTabBar()
InputHexResult InputHexByteEx(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:397
InputHexResult InputHexWordEx(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:429
std::string GetSpriteLabel(int id)
Convenience function to get a sprite label.
std::string GetRoomLabel(int id)
Convenience function to get a room label.
int GetObjectSubtype(int object_id)
std::string GetOverlordLabel(int id)
Convenience function to get an overlord label.
constexpr std::string_view GetDoorDirectionName(DoorDirection dir)
Get human-readable name for door direction.
Definition door_types.h:161
std::string GetObjectName(int object_id)
constexpr std::string_view GetDoorTypeName(DoorType type)
Get human-readable name for door type.
Definition door_types.h:106
std::function< const std::deque< int > &()> get_recent_rooms
bool ShouldApply() const
Definition input.h:48
static constexpr float kContentMinHeightCanvas
Definition ui_config.h:56
static constexpr float kContentMinWidthSidebar
Definition ui_config.h:58