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
12#include "absl/strings/str_format.h"
21#include "app/gui/core/icons.h"
22#include "app/gui/core/input.h"
27#include "imgui/imgui.h"
28#include "imgui/imgui_internal.h"
30
31namespace yaze::editor {
32
33namespace {
34
35constexpr float kEntranceWidth = 220.0f;
36constexpr int kDungeonRoomCount = 0x128;
37constexpr float kMaxComparePickerWidth = 420.0f;
38constexpr float kCompareSearchListHeight = 220.0f;
39// Within-cluster gap: tighter for buttons that share a purpose (pane toggles,
40// room write actions, right-cluster trio).
41constexpr float kToolbarActionGap = 6.0f;
42// Between-cluster gap: clear breathing room at conceptual boundaries
43// (toggles → nav, room actions → canvas modes, canvas modes → compare).
44constexpr float kToolbarClusterGap = 12.0f;
45
46constexpr char kToolbarPopupIdViewOptions[] = "##WorkbenchViewOptions";
47constexpr char kToolbarPopupIdSaveContent[] = "##WorkbenchSaveContent";
48constexpr char kToolbarPopupIdCompareSearchList[] = "##CompareSearchList";
49constexpr char kToolbarPopupIdCompareMenu[] = "##WorkbenchCompareMenu";
50constexpr char kToolbarPopupIdRecentRooms[] = "##WorkbenchRecentRooms";
51constexpr char kToolbarPopupIdOverflow[] = "##WorkbenchToolbarOverflow";
52constexpr char kToolbarStartCompareLabel[] = "Compare";
54constexpr char kToolbarViewOptionsLabel[] = "Display";
55constexpr char kToolbarSaveContentLabel[] = "Save content";
56constexpr char kToolbarModeRoomLabel[] = "Room";
57constexpr char kToolbarModeConnectedLabel[] = "Overview";
59constexpr char kToolbarRoomSearchHint[] = "Type to filter rooms...";
61 "Switch to standalone panel workflow";
63 "Visit another room to build room history.";
65 float width = 0.0f;
66 float spacing = 0.0f;
67 float button_size = 0.0f;
68};
69
71 bool found = false;
72 int room_id = -1;
73};
74
76 bool layers = false;
77 bool entrance = false;
78 bool pane_toggles = true;
79 bool recent_rooms = true;
80 bool stitched_rooms = true;
81 bool compare = false;
82 bool apply_room = false;
83 bool dungeon_map = false;
84 bool view_options = false;
85 bool tools = false;
86 bool save_content = false;
87 bool panel_mode = false;
88 bool overflow = false;
89};
90
91ToolbarLayout ResolveToolbarLayout(float toolbar_width) {
92 ToolbarLayout layout;
93 layout.width = toolbar_width;
94 layout.spacing = ImGui::GetStyle().ItemSpacing.x;
95 layout.button_size =
98 return layout;
99}
100
101bool DrawToolbarActionButton(const char* id, const char* label,
102 const ImVec2& size, const char* tooltip,
103 bool active = false);
104
106 int current_room, int previous_room,
107 const std::function<const std::deque<int>&()>& get_recent_rooms) {
108 if (previous_room >= 0 && previous_room != current_room) {
109 return {true, previous_room};
110 }
111 if (get_recent_rooms) {
112 const auto& mru = get_recent_rooms();
113 for (int rid : mru) {
114 if (rid != current_room) {
115 return {true, rid};
116 }
117 }
118 }
119 return {};
120}
121
122bool IconToggleButton(const char* id, const char* icon_on, const char* icon_off,
123 bool* value, float btn_size, const char* tooltip_on,
124 const char* tooltip_off) {
125 if (!value) {
126 return false;
127 }
128
129 const float btn = btn_size;
130 const float btn_w =
131 workbench::CalcIconToggleButtonWidth(icon_on, icon_off, btn);
132 const bool active = *value;
133
134 const ImVec4 col_btn = ImGui::GetStyleColorVec4(ImGuiCol_Button);
135 const ImVec4 col_active = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive);
136
137 ImGui::PushID(id);
138 gui::StyleColorGuard btn_guard(ImGuiCol_Button,
139 active ? col_active : col_btn);
140 const bool pressed =
141 ImGui::Button(active ? icon_on : icon_off, ImVec2(btn_w, btn));
142
143 if (ImGui::IsItemHovered()) {
144 ImGui::SetTooltip("%s", active ? tooltip_on : tooltip_off);
145 }
146 if (pressed) {
147 *value = !*value;
148 }
149 ImGui::PopID();
150 return pressed;
151}
152
160
162 const ToolbarLayout& layout) {
163 if (!p.primary_viewer) {
164 return;
165 }
166
167 const float button_width = workbench::CalcIconButtonWidth(
169 if (DrawToolbarActionButton("ViewOptionsButton", kToolbarViewOptionsLabel,
170 ImVec2(button_width, layout.button_size),
171 "Canvas view options")) {
172 ImGui::OpenPopup(kToolbarPopupIdViewOptions);
173 }
174
175 if (ImGui::BeginPopup(kToolbarPopupIdViewOptions)) {
177 ImGui::EndPopup();
178 }
179}
180
182 const ToolbarLayout& layout) {
183 const float button_width = workbench::CalcIconButtonWidth(
185 if (DrawToolbarActionButton("SaveContentButton", kToolbarSaveContentLabel,
186 ImVec2(button_width, layout.button_size),
187 "Choose which edited room content is saved")) {
188 ImGui::OpenPopup(kToolbarPopupIdSaveContent);
189 }
190 if (ImGui::BeginPopup(kToolbarPopupIdSaveContent)) {
192 ImGui::EndPopup();
193 }
194}
195
203
205 const ToolbarLayout& toolbar_layout) {
206 const float height = toolbar_layout.button_size;
208 "CanvasModeRoom", kToolbarModeRoomLabel,
210 height),
211 "Edit the current room",
214 *p.split_view_enabled = false;
215 }
216 ImGui::SameLine(0.0f, kToolbarActionGap);
218 "CanvasModeConnected", kToolbarModeConnectedLabel,
220 height),
221 height),
222 "Dungeon overview: click a room preview to edit it",
225 *p.split_view_enabled = false;
226 }
227}
228
229bool DrawToolbarActionButton(const char* id, const char* label,
230 const ImVec2& size, const char* tooltip,
231 bool active) {
232 ImGui::PushID(id);
233 const bool pressed = gui::ToggleButton(label, active, size);
234 {
235 gui::AutoWidgetScope scope("Dungeon/WorkbenchToolbar");
236 gui::AutoRegisterLastItem("button", id, tooltip ? tooltip : label);
237 }
238 ImGui::PopID();
239 if (tooltip && *tooltip && ImGui::IsItemHovered()) {
240 ImGui::SetTooltip("%s", tooltip);
241 }
242 return pressed;
243}
244
245// One searchable room list is used by the main action and narrow overflow.
246// Selecting a room both starts comparison and chooses its target.
248 ImGui::TextUnformatted(tr("Compare with room"));
249 uint16_t room =
250 static_cast<uint16_t>(std::clamp(*p.compare_room_id, 0, 0x127));
251 if (auto result = gui::InputHexWordEx("Room ID", &room, 90.0f, true);
252 result.ShouldApply() && room < kDungeonRoomCount &&
253 room != *p.current_room_id) {
254 *p.compare_room_id = room;
255 *p.split_view_enabled = true;
257 }
258 if (*p.split_view_enabled) {
259 ImGui::SameLine();
260 if (ImGui::Button(tr("End compare"))) {
261 *p.split_view_enabled = false;
262 ImGui::CloseCurrentPopup();
263 }
264 if (ImGui::Button(tr("Swap rooms"))) {
265 const int target = *p.compare_room_id;
267 if (p.on_room_selected)
268 p.on_room_selected(target);
269 else
270 *p.current_room_id = target;
271 }
272 ImGui::SameLine();
273 ImGui::Checkbox(tr("Sync view"), &p.layout->sync_split_view);
274 }
275 const bool search_available =
277 ImGui::SetNextItemWidth(-1);
278 if (search_available)
279 ImGui::InputTextWithHint("##CompareSearch", kToolbarRoomSearchHint,
281 std::string filter = search_available ? p.compare_search_buf : "";
282 std::transform(filter.begin(), filter.end(), filter.begin(),
283 [](unsigned char c) { return std::tolower(c); });
284 ImGui::BeginChild(kToolbarPopupIdCompareSearchList,
285 ImVec2(0, kCompareSearchListHeight), false);
286 for (int id = 0; id < kDungeonRoomCount; ++id) {
287 if (id == *p.current_room_id)
288 continue;
289 const std::string label = absl::StrFormat(
290 "[%03X] %s", id,
292 p.primary_viewer ? p.primary_viewer->project() : nullptr, id));
293 std::string lower = label;
294 std::transform(lower.begin(), lower.end(), lower.begin(),
295 [](unsigned char c) { return std::tolower(c); });
296 if (lower.find(filter) == std::string::npos)
297 continue;
298 ImGui::PushID(id);
299 if (ImGui::Selectable(label.c_str(),
300 *p.split_view_enabled && *p.compare_room_id == id)) {
301 *p.compare_room_id = id;
302 *p.split_view_enabled = true;
304 ImGui::CloseCurrentPopup();
305 }
306 ImGui::PopID();
307 }
308 ImGui::EndChild();
309}
310
312 ImGui::SetNextWindowSize(ImVec2(kMaxComparePickerWidth, 0),
313 ImGuiCond_Appearing);
314 if (ImGui::BeginPopup(kToolbarPopupIdCompareMenu)) {
316 ImGui::EndPopup();
317 }
318}
319
321 const ToolbarLayout& layout) {
323 "CompareMenuButton", kToolbarStartCompareLabel,
325 layout.button_size),
326 layout.button_size),
327 *p.split_view_enabled ? "Choose comparison room or end comparison"
328 : "Compare rooms",
329 *p.split_view_enabled)) {
330 const auto previous = PickDefaultCompareRoom(
333 if (!*p.split_view_enabled && previous.found) {
334 *p.compare_room_id = previous.room_id;
335 *p.split_view_enabled = true;
337 } else {
338 ImGui::OpenPopup(kToolbarPopupIdCompareMenu);
339 }
340 }
342}
343
345 bool show_heading) {
346 if (show_heading) {
347 ImGui::TextDisabled(tr("Recent Rooms"));
348 ImGui::Separator();
349 }
350 DungeonRoomStore* rooms =
351 p.primary_viewer ? p.primary_viewer->rooms() : nullptr;
352 const project::YazeProject* project =
353 p.primary_viewer ? p.primary_viewer->project() : nullptr;
354 std::vector<int> recent_ids;
355 if (p.get_recent_rooms) {
356 const auto& recent = p.get_recent_rooms();
357 recent_ids.assign(recent.begin(), recent.end());
358 }
359 std::vector<int> to_forget;
360
361 for (int room_id : recent_ids) {
362 ImGui::PushID(room_id);
363 const bool is_current = p.current_room_id && room_id == *p.current_room_id;
364 const bool room_dirty =
365 rooms != nullptr && rooms->GetIfMaterialized(room_id) != nullptr &&
366 rooms->GetIfMaterialized(room_id)->HasUnsavedChanges();
367 const auto room_name =
368 dungeon_project_labels::GetRoomLabel(project, room_id);
369 char item_label[160];
370 snprintf(item_label, sizeof(item_label), "[%03X]%s %s##RecentRoom", room_id,
371 room_dirty ? "*" : "", room_name.c_str());
372
373 if (ImGui::Selectable(item_label, is_current) && !is_current) {
374 if (p.on_room_selected) {
375 p.on_room_selected(room_id);
376 } else if (p.current_room_id) {
377 *p.current_room_id = room_id;
378 }
379 }
380 if (ImGui::IsItemHovered()) {
381 ImGui::SetTooltip("Click to open; right-click for actions%s",
382 room_dirty ? "\nPending room changes" : "");
383 }
384
385 gui::ItemContextMenu("##RecentRoomActions", [&, room_id]() {
386 std::vector<gui::MenuItemSpec> items;
387 if (p.on_open_room_panel) {
388 items.emplace_back("Open as Panel", ICON_MD_OPEN_IN_NEW,
389 [&p, room_id]() { p.on_open_room_panel(room_id); });
390 }
391 items.emplace_back("Compare", ICON_MD_COMPARE_ARROWS, [&p, room_id]() {
392 if (p.layout) {
393 p.layout->show_connected_canvas_view = false;
394 }
395 if (p.split_view_enabled) {
396 *p.split_view_enabled = true;
397 }
398 if (p.compare_room_id) {
399 *p.compare_room_id = room_id;
400 }
401 });
402 items.back().separator_after = true;
403 items.push_back(gui::CopyToClipboardItem(
404 "Copy Room ID", absl::StrFormat("0x%03X", room_id)));
405 if (p.forget_recent_room) {
406 items.back().separator_after = true;
407 items.emplace_back(
408 "Remove from Recent", ICON_MD_CLOSE,
409 [&to_forget, room_id]() { to_forget.push_back(room_id); });
410 }
411 return items;
412 });
413 ImGui::PopID();
414 }
415
416 for (int room_id : to_forget) {
417 p.forget_recent_room(room_id);
418 }
419}
420
422 const ToolbarLayout& layout) {
423 const bool has_history = p.get_recent_rooms && !p.get_recent_rooms().empty();
424 const float button_width = workbench::CalcIconButtonWidth(
426
427 if (!has_history) {
428 ImGui::BeginDisabled();
429 }
430 const bool open_history = DrawToolbarActionButton(
431 "RecentRoomsButton", kToolbarRecentRoomsLabel,
432 ImVec2(button_width, layout.button_size),
433 has_history ? "Recent rooms" : kToolbarNoRecentRoomsTooltip);
434 if (!has_history) {
435 ImGui::EndDisabled();
436 }
437 if (open_history) {
438 ImGui::OpenPopup(kToolbarPopupIdRecentRooms);
439 }
440
441 if (!ImGui::BeginPopup(kToolbarPopupIdRecentRooms)) {
442 return;
443 }
444 DrawRecentRoomsContents(p, /*show_heading=*/true);
445 ImGui::EndPopup();
446}
447
449 const ToolbarLayout& layout,
450 const ToolbarVisibility& visibility) {
451 float width = 0.0f;
452 auto append = [&](float item_width) {
453 if (width > 0.0f) {
454 width += kToolbarActionGap;
455 }
456 width += item_width;
457 };
458
459 if (visibility.view_options && p.primary_viewer) {
461 layout.button_size));
462 }
463 if (visibility.tools) {
464 append(workbench::CalcIconButtonWidth("Room tools", layout.button_size));
465 }
466 if (visibility.save_content) {
468 layout.button_size));
469 }
470 if (visibility.panel_mode && p.set_workflow_mode) {
471 append(
473 }
474 if (visibility.overflow) {
476 layout.button_size));
477 }
478 return width;
479}
480
482 const ToolbarLayout& layout,
483 const ToolbarVisibility& visibility) {
484 float left_width = 0.0f;
485 auto append_left = [&](float item_width, float gap) {
486 if (left_width > 0.0f) {
487 left_width += gap;
488 }
489 left_width += item_width;
490 };
491
492 if (visibility.pane_toggles) {
494 layout.button_size),
495 0.0f);
497 layout.button_size),
499 }
500
501 const float navigation_width =
502 4.0f * ImGui::GetFrameHeight() + 4.0f * ImGui::GetStyle().ItemSpacing.x +
503 ImGui::CalcTextSize("00000").x + ImGui::GetStyle().FramePadding.x * 2.0f;
504 append_left(navigation_width, kToolbarClusterGap);
505 if (visibility.stitched_rooms) {
506 append_left(CanvasModeSelectorWidth(layout), kToolbarClusterGap);
507 }
508
509 if (visibility.entrance)
511 if (visibility.layers)
513
514 if (visibility.recent_rooms) {
516 layout.button_size),
518 }
519 if (visibility.apply_room) {
520 append_left(
523 }
524 if (visibility.dungeon_map) {
527 }
528 if (visibility.compare) {
530 layout.button_size),
532 }
533
534 const float right_width = ToolbarRightActionsWidth(p, layout, visibility);
535 return left_width +
536 (right_width > 0.0f ? kToolbarActionGap + right_width : 0.0f);
537}
538
540 const DungeonWorkbenchToolbarParams& p, const ToolbarLayout& layout) {
541 ToolbarVisibility visibility;
542 visibility.layers = p.primary_viewer != nullptr;
543 visibility.entrance = p.draw_entrance_context != nullptr;
544 visibility.compare = true;
545 visibility.apply_room =
547 visibility.dungeon_map = p.on_request_dungeon_map != nullptr;
548 visibility.view_options = p.primary_viewer != nullptr;
549 visibility.tools = p.draw_tools != nullptr;
550 visibility.save_content = p.draw_save_content != nullptr;
551 visibility.panel_mode = p.set_workflow_mode != nullptr;
552
553 constexpr float kSafetyMargin = 4.0f;
554 auto fits = [&]() {
555 return ToolbarEstimatedWidth(p, layout, visibility) + kSafetyMargin <=
556 layout.width;
557 };
558 if (fits()) {
559 return visibility;
560 }
561
562 auto collapse = [&](bool& item) {
563 if (!item) {
564 return false;
565 }
566 item = false;
567 visibility.overflow = true;
568 return fits();
569 };
570
571 // Keep the explicit view choice beside room navigation at laptop widths.
572 // Entrance/layer controls remain available in the overflow on narrow panes.
573 if (collapse(visibility.dungeon_map))
574 return visibility;
575 if (collapse(visibility.save_content))
576 return visibility;
577 if (collapse(visibility.apply_room))
578 return visibility;
579 if (collapse(visibility.panel_mode))
580 return visibility;
581 if (collapse(visibility.recent_rooms))
582 return visibility;
583 if (collapse(visibility.compare))
584 return visibility;
585 if (collapse(visibility.view_options))
586 return visibility;
587 if (collapse(visibility.entrance))
588 return visibility;
589 if (collapse(visibility.layers))
590 return visibility;
591 if (collapse(visibility.tools))
592 return visibility;
593 if (collapse(visibility.pane_toggles))
594 return visibility;
595 collapse(visibility.stitched_rooms);
596 return visibility;
597}
598
600 const ToolbarLayout& layout,
601 const ToolbarVisibility& visibility) {
602 const float button_width =
604 if (DrawToolbarActionButton("ToolbarOverflow", kToolbarOverflowLabel,
605 ImVec2(button_width, layout.button_size),
606 "More dungeon actions")) {
607 ImGui::OpenPopup(kToolbarPopupIdOverflow);
608 }
609
610 bool request_panel_mode = false;
611 if (!ImGui::BeginPopup(kToolbarPopupIdOverflow)) {
612 return request_panel_mode;
613 }
614
615 if (!visibility.entrance && p.draw_entrance_context) {
617 ImGui::Separator();
618 }
619 if (!visibility.layers && p.primary_viewer) {
621 layout.button_size);
622 ImGui::Separator();
623 }
624
625 if (!visibility.pane_toggles) {
626 if (ImGui::MenuItem(ICON_MD_LIST " Room Browser", nullptr,
629 }
630 if (ImGui::MenuItem(ICON_MD_TUNE " Inspector", nullptr,
633 }
634 ImGui::Separator();
635 }
636
637 if (!visibility.recent_rooms) {
638 const bool has_history =
639 p.get_recent_rooms && !p.get_recent_rooms().empty();
640 if (ImGui::BeginMenu(ICON_MD_HISTORY " Recent Rooms", has_history)) {
641 DrawRecentRoomsContents(p, /*show_heading=*/false);
642 ImGui::EndMenu();
643 }
644 if (!has_history &&
645 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
646 ImGui::SetTooltip("%s", kToolbarNoRecentRoomsTooltip);
647 }
648 }
649 if (!visibility.stitched_rooms) {
650 if (ImGui::MenuItem(
651 "Room view", nullptr,
654 *p.split_view_enabled = false;
655 }
656 if (ImGui::MenuItem("Dungeon overview", nullptr,
659 *p.split_view_enabled = false;
660 }
661 }
662 if (!visibility.compare) {
664 }
665
666 const bool has_hidden_room_action =
667 (!visibility.apply_room && p.on_save_room && p.current_room_id &&
668 *p.current_room_id >= 0) ||
669 (!visibility.dungeon_map && p.on_request_dungeon_map);
670 if (has_hidden_room_action) {
671 ImGui::Separator();
672 }
673 if (!visibility.apply_room && p.on_save_room && p.current_room_id &&
674 *p.current_room_id >= 0 && ImGui::MenuItem(ICON_MD_SAVE " Apply Room")) {
676 }
677 if (!visibility.dungeon_map && p.on_request_dungeon_map &&
678 ImGui::MenuItem(ICON_MD_MAP " Dungeon Map")) {
680 }
681
682 if (!visibility.view_options && p.primary_viewer) {
683 ImGui::Separator();
684 if (ImGui::BeginMenu("Display")) {
686 ImGui::EndMenu();
687 }
688 }
689 if (!visibility.tools && p.draw_tools) {
690 p.draw_tools();
691 }
692 if (!visibility.save_content && p.draw_save_content &&
693 ImGui::BeginMenu(kToolbarSaveContentLabel)) {
695 ImGui::EndMenu();
696 }
697 if (!visibility.panel_mode && p.set_workflow_mode) {
698 if (ImGui::MenuItem(ICON_MD_VIEW_QUILT " Window Workflow")) {
699 request_panel_mode = true;
700 }
701 if (ImGui::IsItemHovered()) {
702 ImGui::SetTooltip("%s", kToolbarPanelWorkflowTooltip);
703 }
704 }
705
706 ImGui::EndPopup();
707 return request_panel_mode;
708}
709
710} // namespace
711
712bool DungeonWorkbenchToolbar::ShouldShowInlineRoomNav(float toolbar_width) {
713 (void)toolbar_width;
714 return true;
715}
716
717bool DungeonWorkbenchToolbar::Draw(const DungeonWorkbenchToolbarParams& p) {
718 if (!p.layout || !p.current_room_id || !p.split_view_enabled ||
719 !p.compare_room_id) {
720 ImGui::TextDisabled(tr("Workbench toolbar not wired"));
721 return false;
722 }
723
724 const ToolbarLayout layout =
725 ResolveToolbarLayout(std::max(ImGui::GetContentRegionAvail().x, 1.0f));
726 bool request_panel_mode = false;
727
728 // Gentle compaction only; kToolbarActionGap/ClusterGap own inter-button
729 // spacing, so don't shrink ItemSpacing.x below the theme default here.
730 const ImVec2 frame_pad = ImGui::GetStyle().FramePadding;
731 gui::StyleVarGuard frame_pad_guard(
732 ImGuiStyleVar_FramePadding, ImVec2(std::max(4.0f, frame_pad.x - 1.0f),
733 std::max(3.0f, frame_pad.y - 1.0f)));
734 gui::StyleVarGuard item_spacing_guard(
735 ImGuiStyleVar_ItemSpacing,
736 ImVec2(std::max(layout.spacing, 4.0f),
737 std::max(3.0f, ImGui::GetStyle().ItemSpacing.y - 1.0f)));
738 const ToolbarVisibility visibility = ResolveToolbarVisibility(p, layout);
739 const float right_actions_width =
740 ToolbarRightActionsWidth(p, layout, visibility);
741
742 // Connected toolbar controls: render as a floating canvas overlay (the
743 // viewer's built-in fallback) instead of inline. Inline rendering races
744 // against the right cluster because the controls are variable-width and
745 // only present when stitched mode is active. Overlay mode anchors them to
746 // the canvas viewport corner where they belong.
747 if (p.primary_viewer) {
749 }
750
751 // ── Render row ───────────────────────────────────────────────────
752 if (visibility.pane_toggles) {
753 (void)IconToggleButton("RoomsToggle", ICON_MD_LIST, ICON_MD_LIST,
754 &p.layout->show_left_sidebar, layout.button_size,
755 "Hide room browser", "Show room browser");
756 ImGui::SameLine(0.0f, kToolbarActionGap);
757 (void)IconToggleButton("InspectorToggle", ICON_MD_TUNE, ICON_MD_TUNE,
758 &p.layout->show_right_inspector, layout.button_size,
759 "Hide inspector", "Show inspector");
760 ImGui::SameLine(0.0f, kToolbarClusterGap);
761 }
762 DungeonRoomNavWidget::Draw("WorkbenchNav", *p.current_room_id,
764 if (visibility.stitched_rooms) {
765 ImGui::SameLine(0.0f, kToolbarClusterGap);
766 DrawCanvasModeSelector(p, layout);
767 }
768
769 if (visibility.entrance) {
770 ImGui::SameLine(0.0f, kToolbarClusterGap);
771 p.draw_entrance_context(kEntranceWidth);
772 }
773 if (visibility.layers) {
774 ImGui::SameLine(0.0f, kToolbarClusterGap);
776 layout.button_size);
777 }
778
779 if (visibility.recent_rooms) {
780 ImGui::SameLine(0.0f, kToolbarActionGap);
781 DrawRecentRoomsMenu(p, layout);
782 }
783
784 if (visibility.apply_room) {
785 ImGui::SameLine(0.0f, kToolbarActionGap);
786 if (workbench::DrawHeaderIconAction(
787 "ApplyRoomToolbar", ICON_MD_SAVE, layout.button_size,
788 "Apply this room into the loaded ROM buffer "
789 "(File > Save ROM persists to disk)")) {
791 }
792 }
793 if (visibility.dungeon_map) {
794 ImGui::SameLine(0.0f, kToolbarActionGap);
795 if (workbench::DrawHeaderIconAction("DungeonMapToolbar", ICON_MD_MAP,
796 layout.button_size,
797 "Open the Dungeon Map popup")) {
799 }
800 }
801
802 if (visibility.compare) {
803 ImGui::SameLine(0.0f, kToolbarClusterGap);
804 DrawCompareMenu(p, layout);
805 }
806
807 if (right_actions_width > 0.0f) {
808 const float last_left_edge =
809 ImGui::GetItemRectMax().x - ImGui::GetWindowPos().x;
810 const float right_start =
811 std::max(last_left_edge + kToolbarActionGap,
812 ImGui::GetWindowContentRegionMax().x - right_actions_width);
813 ImGui::SameLine(right_start, 0.0f);
814
815 bool drew_right_action = false;
816 if (visibility.view_options) {
817 DrawViewOptionsButton(p, layout);
818 drew_right_action = true;
819 }
820 if (visibility.tools) {
821 if (drew_right_action) {
822 ImGui::SameLine(0.0f, kToolbarActionGap);
823 }
824 p.draw_tools();
825 drew_right_action = true;
826 }
827 if (visibility.save_content) {
828 if (drew_right_action) {
829 ImGui::SameLine(0.0f, kToolbarActionGap);
830 }
831 DrawSaveContentButton(p, layout);
832 drew_right_action = true;
833 }
834 if (visibility.panel_mode) {
835 if (drew_right_action) {
836 ImGui::SameLine(0.0f, kToolbarActionGap);
837 }
838 if (workbench::DrawHeaderIconAction("PanelMode", ICON_MD_VIEW_QUILT,
839 layout.button_size,
840 kToolbarPanelWorkflowTooltip)) {
841 request_panel_mode = true;
842 }
843 drew_right_action = true;
844 }
845 if (visibility.overflow) {
846 if (drew_right_action) {
847 ImGui::SameLine(0.0f, kToolbarActionGap);
848 }
849 request_panel_mode |= DrawToolbarOverflowMenu(p, layout, visibility);
850 }
851 }
852 return request_panel_mode;
853}
854
855} // namespace yaze::editor
void SetConnectedControlsInline(bool inline_controls)
const project::YazeProject * project() const
zelda3::Room * GetIfMaterialized(int room_id)
RAII scope that enables automatic widget registration.
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
bool HasUnsavedChanges() const
Definition room.h:662
#define ICON_MD_VIEW_QUILT
Definition icons.h:2094
#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_MORE_HORIZ
Definition icons.h:1241
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_CLOSE
Definition icons.h:418
#define ICON_MD_HISTORY
Definition icons.h:946
void DrawCompareMenu(const DungeonWorkbenchToolbarParams &p, const ToolbarLayout &layout)
void DrawViewOptionsButton(const DungeonWorkbenchToolbarParams &p, const ToolbarLayout &layout)
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)
float ToolbarRightActionsWidth(const DungeonWorkbenchToolbarParams &p, const ToolbarLayout &layout, const ToolbarVisibility &visibility)
ToolbarVisibility ResolveToolbarVisibility(const DungeonWorkbenchToolbarParams &p, const ToolbarLayout &layout)
bool DrawToolbarActionButton(const char *id, const char *label, const ImVec2 &size, const char *tooltip, bool active=false)
CompareDefaultResult PickDefaultCompareRoom(int current_room, int previous_room, const std::function< const std::deque< int > &()> &get_recent_rooms)
void DrawRecentRoomsContents(const DungeonWorkbenchToolbarParams &p, bool show_heading)
bool DrawToolbarOverflowMenu(const DungeonWorkbenchToolbarParams &p, const ToolbarLayout &layout, const ToolbarVisibility &visibility)
void DrawRecentRoomsMenu(const DungeonWorkbenchToolbarParams &p, const ToolbarLayout &layout)
void DrawSaveContentButton(const DungeonWorkbenchToolbarParams &p, const ToolbarLayout &layout)
void DrawCanvasModeSelector(const DungeonWorkbenchToolbarParams &p, const ToolbarLayout &toolbar_layout)
float ToolbarEstimatedWidth(const DungeonWorkbenchToolbarParams &p, const ToolbarLayout &layout, const ToolbarVisibility &visibility)
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)
Editors are the view controllers for the application.
void DrawDungeonOverlayControls(DungeonCanvasViewer &viewer)
void DrawDungeonLayerControls(DungeonCanvasViewer &viewer, int room_id, float height)
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
bool ToggleButton(const char *label, bool active, const ImVec2 &size)
bool ItemContextMenu(const char *id, const std::function< std::vector< MenuItemSpec >()> &build, ImGuiPopupFlags flags)
Right-click menu for the last submitted ImGui item.
MenuItemSpec CopyToClipboardItem(const std::string &label, const std::string &text)
InputHexResult InputHexWordEx(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:561
std::function< const std::deque< int > &()> get_recent_rooms
bool ShouldApply() const
Definition input.h:82
static constexpr float kIconButtonSmall
Definition ui_config.h:61
Modern project structure with comprehensive settings consolidation.
Definition project.h:287
Automatic widget registration helpers for ImGui Test Engine integration.