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[] = "View";
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
154 if (!p.primary_viewer) {
155 return;
156 }
157
159 if (p.draw_layout_options) {
160 ImGui::Separator();
162 }
163}
164
166 const ToolbarLayout& layout) {
167 if (!p.primary_viewer) {
168 return;
169 }
170
171 const float button_width = workbench::CalcIconButtonWidth(
173 if (DrawToolbarActionButton("ViewOptionsButton", kToolbarViewOptionsLabel,
174 ImVec2(button_width, layout.button_size),
175 "Canvas view options")) {
176 ImGui::OpenPopup(kToolbarPopupIdViewOptions);
177 }
178
179 if (ImGui::BeginPopup(kToolbarPopupIdViewOptions)) {
181 ImGui::EndPopup();
182 }
183}
184
186 const ToolbarLayout& layout) {
187 const float button_width = workbench::CalcIconButtonWidth(
189 if (DrawToolbarActionButton("SaveContentButton", kToolbarSaveContentLabel,
190 ImVec2(button_width, layout.button_size),
191 "Choose which edited room content is saved")) {
192 ImGui::OpenPopup(kToolbarPopupIdSaveContent);
193 }
194 if (ImGui::BeginPopup(kToolbarPopupIdSaveContent)) {
196 ImGui::EndPopup();
197 }
198}
199
207
209 const ToolbarLayout& toolbar_layout) {
210 const float height = toolbar_layout.button_size;
212 "CanvasModeRoom", kToolbarModeRoomLabel,
214 height),
215 "Edit the current room",
218 *p.split_view_enabled = false;
219 }
220 ImGui::SameLine(0.0f, kToolbarActionGap);
222 "CanvasModeConnected", kToolbarModeConnectedLabel,
224 height),
225 height),
226 "Dungeon overview: click a room preview to edit it",
229 *p.split_view_enabled = false;
230 }
231}
232
233bool DrawToolbarActionButton(const char* id, const char* label,
234 const ImVec2& size, const char* tooltip,
235 bool active) {
236 ImGui::PushID(id);
237 const bool pressed = gui::ToggleButton(label, active, size);
238 {
239 gui::AutoWidgetScope scope("Dungeon/WorkbenchToolbar");
240 gui::AutoRegisterLastItem("button", id, tooltip ? tooltip : label);
241 }
242 ImGui::PopID();
243 if (tooltip && *tooltip && ImGui::IsItemHovered()) {
244 ImGui::SetTooltip("%s", tooltip);
245 }
246 return pressed;
247}
248
249// One searchable room list is used by the main action and narrow overflow.
250// Selecting a room both starts comparison and chooses its target.
252 ImGui::TextUnformatted(tr("Compare with room"));
253 uint16_t room =
254 static_cast<uint16_t>(std::clamp(*p.compare_room_id, 0, 0x127));
255 if (auto result = gui::InputHexWordEx("Room ID", &room, 90.0f, true);
256 result.ShouldApply() && room < kDungeonRoomCount &&
257 room != *p.current_room_id) {
258 *p.compare_room_id = room;
259 *p.split_view_enabled = true;
261 }
262 if (*p.split_view_enabled) {
263 ImGui::SameLine();
264 if (ImGui::Button(tr("End compare"))) {
265 *p.split_view_enabled = false;
266 ImGui::CloseCurrentPopup();
267 }
268 if (ImGui::Button(tr("Swap rooms"))) {
269 const int target = *p.compare_room_id;
271 if (p.on_room_selected)
272 p.on_room_selected(target);
273 else
274 *p.current_room_id = target;
275 }
276 ImGui::SameLine();
277 ImGui::Checkbox(tr("Sync view"), &p.layout->sync_split_view);
278 }
279 const bool search_available =
281 ImGui::SetNextItemWidth(-1);
282 if (search_available)
283 ImGui::InputTextWithHint("##CompareSearch", kToolbarRoomSearchHint,
285 std::string filter = search_available ? p.compare_search_buf : "";
286 std::transform(filter.begin(), filter.end(), filter.begin(),
287 [](unsigned char c) { return std::tolower(c); });
288 ImGui::BeginChild(kToolbarPopupIdCompareSearchList,
289 ImVec2(0, kCompareSearchListHeight), false);
290 for (int id = 0; id < kDungeonRoomCount; ++id) {
291 if (id == *p.current_room_id)
292 continue;
293 const std::string label = absl::StrFormat(
294 "[%03X] %s", id,
296 p.primary_viewer ? p.primary_viewer->project() : nullptr, id));
297 std::string lower = label;
298 std::transform(lower.begin(), lower.end(), lower.begin(),
299 [](unsigned char c) { return std::tolower(c); });
300 if (lower.find(filter) == std::string::npos)
301 continue;
302 ImGui::PushID(id);
303 if (ImGui::Selectable(label.c_str(),
304 *p.split_view_enabled && *p.compare_room_id == id)) {
305 *p.compare_room_id = id;
306 *p.split_view_enabled = true;
308 ImGui::CloseCurrentPopup();
309 }
310 ImGui::PopID();
311 }
312 ImGui::EndChild();
313}
314
316 ImGui::SetNextWindowSize(ImVec2(kMaxComparePickerWidth, 0),
317 ImGuiCond_Appearing);
318 if (ImGui::BeginPopup(kToolbarPopupIdCompareMenu)) {
320 ImGui::EndPopup();
321 }
322}
323
325 const ToolbarLayout& layout) {
327 "CompareMenuButton", kToolbarStartCompareLabel,
329 layout.button_size),
330 layout.button_size),
331 *p.split_view_enabled ? "Choose comparison room or end comparison"
332 : "Compare rooms",
333 *p.split_view_enabled)) {
334 const auto previous = PickDefaultCompareRoom(
337 if (!*p.split_view_enabled && previous.found) {
338 *p.compare_room_id = previous.room_id;
339 *p.split_view_enabled = true;
341 } else {
342 ImGui::OpenPopup(kToolbarPopupIdCompareMenu);
343 }
344 }
346}
347
349 bool show_heading) {
350 if (show_heading) {
351 ImGui::TextDisabled(tr("Recent Rooms"));
352 ImGui::Separator();
353 }
354 DungeonRoomStore* rooms =
355 p.primary_viewer ? p.primary_viewer->rooms() : nullptr;
356 const project::YazeProject* project =
357 p.primary_viewer ? p.primary_viewer->project() : nullptr;
358 std::vector<int> recent_ids;
359 if (p.get_recent_rooms) {
360 const auto& recent = p.get_recent_rooms();
361 recent_ids.assign(recent.begin(), recent.end());
362 }
363 std::vector<int> to_forget;
364
365 for (int room_id : recent_ids) {
366 ImGui::PushID(room_id);
367 const bool is_current = p.current_room_id && room_id == *p.current_room_id;
368 const bool room_dirty =
369 rooms != nullptr && rooms->GetIfMaterialized(room_id) != nullptr &&
370 rooms->GetIfMaterialized(room_id)->HasUnsavedChanges();
371 const auto room_name =
372 dungeon_project_labels::GetRoomLabel(project, room_id);
373 char item_label[160];
374 snprintf(item_label, sizeof(item_label), "[%03X]%s %s##RecentRoom", room_id,
375 room_dirty ? "*" : "", room_name.c_str());
376
377 if (ImGui::Selectable(item_label, is_current) && !is_current) {
378 if (p.on_room_selected) {
379 p.on_room_selected(room_id);
380 } else if (p.current_room_id) {
381 *p.current_room_id = room_id;
382 }
383 }
384 if (ImGui::IsItemHovered()) {
385 ImGui::SetTooltip("Click to open; right-click for actions%s",
386 room_dirty ? "\nPending room changes" : "");
387 }
388
389 gui::ItemContextMenu("##RecentRoomActions", [&, room_id]() {
390 std::vector<gui::MenuItemSpec> items;
391 if (p.on_open_room_panel) {
392 items.emplace_back("Open as Panel", ICON_MD_OPEN_IN_NEW,
393 [&p, room_id]() { p.on_open_room_panel(room_id); });
394 }
395 items.emplace_back("Compare", ICON_MD_COMPARE_ARROWS, [&p, room_id]() {
396 if (p.layout) {
397 p.layout->show_connected_canvas_view = false;
398 }
399 if (p.split_view_enabled) {
400 *p.split_view_enabled = true;
401 }
402 if (p.compare_room_id) {
403 *p.compare_room_id = room_id;
404 }
405 });
406 items.back().separator_after = true;
407 items.push_back(gui::CopyToClipboardItem(
408 "Copy Room ID", absl::StrFormat("0x%03X", room_id)));
409 if (p.forget_recent_room) {
410 items.back().separator_after = true;
411 items.emplace_back(
412 "Remove from Recent", ICON_MD_CLOSE,
413 [&to_forget, room_id]() { to_forget.push_back(room_id); });
414 }
415 return items;
416 });
417 ImGui::PopID();
418 }
419
420 for (int room_id : to_forget) {
421 p.forget_recent_room(room_id);
422 }
423}
424
426 const ToolbarLayout& layout) {
427 const bool has_history = p.get_recent_rooms && !p.get_recent_rooms().empty();
428 const float button_width = workbench::CalcIconButtonWidth(
430
431 if (!has_history) {
432 ImGui::BeginDisabled();
433 }
434 const bool open_history = DrawToolbarActionButton(
435 "RecentRoomsButton", kToolbarRecentRoomsLabel,
436 ImVec2(button_width, layout.button_size),
437 has_history ? "Recent rooms" : kToolbarNoRecentRoomsTooltip);
438 if (!has_history) {
439 ImGui::EndDisabled();
440 }
441 if (open_history) {
442 ImGui::OpenPopup(kToolbarPopupIdRecentRooms);
443 }
444
445 if (!ImGui::BeginPopup(kToolbarPopupIdRecentRooms)) {
446 return;
447 }
448 DrawRecentRoomsContents(p, /*show_heading=*/true);
449 ImGui::EndPopup();
450}
451
453 const ToolbarLayout& layout,
454 const ToolbarVisibility& visibility) {
455 float width = 0.0f;
456 auto append = [&](float item_width) {
457 if (width > 0.0f) {
458 width += kToolbarActionGap;
459 }
460 width += item_width;
461 };
462
463 if (visibility.view_options && p.primary_viewer) {
465 layout.button_size));
466 }
467 if (visibility.tools) {
468 append(workbench::CalcIconButtonWidth("Room tools", layout.button_size));
469 }
470 if (visibility.save_content) {
472 layout.button_size));
473 }
474 if (visibility.panel_mode && p.set_workflow_mode) {
475 append(
477 }
478 if (visibility.overflow) {
480 layout.button_size));
481 }
482 return width;
483}
484
486 const ToolbarLayout& layout,
487 const ToolbarVisibility& visibility) {
488 float left_width = 0.0f;
489 auto append_left = [&](float item_width, float gap) {
490 if (left_width > 0.0f) {
491 left_width += gap;
492 }
493 left_width += item_width;
494 };
495
496 if (visibility.pane_toggles) {
498 layout.button_size),
499 0.0f);
501 layout.button_size),
503 }
504
505 const float navigation_width =
506 4.0f * ImGui::GetFrameHeight() + 4.0f * ImGui::GetStyle().ItemSpacing.x +
507 ImGui::CalcTextSize("00000").x + ImGui::GetStyle().FramePadding.x * 2.0f;
508 append_left(navigation_width, kToolbarClusterGap);
509 if (visibility.stitched_rooms) {
510 append_left(CanvasModeSelectorWidth(layout), kToolbarClusterGap);
511 }
512
513 if (visibility.entrance)
515 if (visibility.layers)
517
518 if (visibility.recent_rooms) {
520 layout.button_size),
522 }
523 if (visibility.apply_room) {
524 append_left(
527 }
528 if (visibility.dungeon_map) {
531 }
532 if (visibility.compare) {
534 layout.button_size),
536 }
537
538 const float right_width = ToolbarRightActionsWidth(p, layout, visibility);
539 return left_width +
540 (right_width > 0.0f ? kToolbarActionGap + right_width : 0.0f);
541}
542
544 const DungeonWorkbenchToolbarParams& p, const ToolbarLayout& layout) {
545 ToolbarVisibility visibility;
546 visibility.layers = p.primary_viewer != nullptr;
547 visibility.entrance = p.draw_entrance_context != nullptr;
548 visibility.compare = true;
549 visibility.apply_room =
551 visibility.dungeon_map = p.on_request_dungeon_map != nullptr;
552 visibility.view_options = p.primary_viewer != nullptr;
553 visibility.tools = p.draw_tools != nullptr;
554 visibility.save_content = p.draw_save_content != nullptr;
555 visibility.panel_mode = p.set_workflow_mode != nullptr;
556
557 constexpr float kSafetyMargin = 4.0f;
558 auto fits = [&]() {
559 return ToolbarEstimatedWidth(p, layout, visibility) + kSafetyMargin <=
560 layout.width;
561 };
562 if (fits()) {
563 return visibility;
564 }
565
566 auto collapse = [&](bool& item) {
567 if (!item) {
568 return false;
569 }
570 item = false;
571 visibility.overflow = true;
572 return fits();
573 };
574
575 // Keep the explicit view choice beside room navigation at laptop widths.
576 // Entrance/layer controls remain available in the overflow on narrow panes.
577 if (collapse(visibility.dungeon_map))
578 return visibility;
579 if (collapse(visibility.save_content))
580 return visibility;
581 if (collapse(visibility.apply_room))
582 return visibility;
583 if (collapse(visibility.panel_mode))
584 return visibility;
585 if (collapse(visibility.recent_rooms))
586 return visibility;
587 if (collapse(visibility.compare))
588 return visibility;
589 if (collapse(visibility.view_options))
590 return visibility;
591 if (collapse(visibility.entrance))
592 return visibility;
593 if (collapse(visibility.layers))
594 return visibility;
595 if (collapse(visibility.tools))
596 return visibility;
597 if (collapse(visibility.pane_toggles))
598 return visibility;
599 collapse(visibility.stitched_rooms);
600 return visibility;
601}
602
604 const ToolbarLayout& layout,
605 const ToolbarVisibility& visibility) {
606 const float button_width =
608 if (DrawToolbarActionButton("ToolbarOverflow", kToolbarOverflowLabel,
609 ImVec2(button_width, layout.button_size),
610 "More dungeon actions")) {
611 ImGui::OpenPopup(kToolbarPopupIdOverflow);
612 }
613
614 bool request_panel_mode = false;
615 if (!ImGui::BeginPopup(kToolbarPopupIdOverflow)) {
616 return request_panel_mode;
617 }
618
619 if (!visibility.entrance && p.draw_entrance_context) {
621 ImGui::Separator();
622 }
623 if (!visibility.layers && p.primary_viewer) {
625 layout.button_size);
626 ImGui::Separator();
627 }
628
629 if (!visibility.pane_toggles) {
630 if (ImGui::MenuItem(ICON_MD_LIST " Room Browser", nullptr,
633 }
634 if (ImGui::MenuItem(ICON_MD_TUNE " Inspector", nullptr,
637 }
638 ImGui::Separator();
639 }
640
641 if (!visibility.recent_rooms) {
642 const bool has_history =
643 p.get_recent_rooms && !p.get_recent_rooms().empty();
644 if (ImGui::BeginMenu(ICON_MD_HISTORY " Recent Rooms", has_history)) {
645 DrawRecentRoomsContents(p, /*show_heading=*/false);
646 ImGui::EndMenu();
647 }
648 if (!has_history &&
649 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
650 ImGui::SetTooltip("%s", kToolbarNoRecentRoomsTooltip);
651 }
652 }
653 if (!visibility.stitched_rooms) {
654 if (ImGui::MenuItem(
655 "Room view", nullptr,
658 *p.split_view_enabled = false;
659 }
660 if (ImGui::MenuItem("Dungeon overview", nullptr,
663 *p.split_view_enabled = false;
664 }
665 }
666 if (!visibility.compare) {
668 }
669
670 const bool has_hidden_room_action =
671 (!visibility.apply_room && p.on_save_room && p.current_room_id &&
672 *p.current_room_id >= 0) ||
673 (!visibility.dungeon_map && p.on_request_dungeon_map);
674 if (has_hidden_room_action) {
675 ImGui::Separator();
676 }
677 if (!visibility.apply_room && p.on_save_room && p.current_room_id &&
678 *p.current_room_id >= 0 && ImGui::MenuItem(ICON_MD_SAVE " Apply Room")) {
680 }
681 if (!visibility.dungeon_map && p.on_request_dungeon_map &&
682 ImGui::MenuItem(ICON_MD_MAP " Dungeon Map")) {
684 }
685
686 if (!visibility.view_options && p.primary_viewer) {
687 ImGui::Separator();
688 if (ImGui::BeginMenu("Display")) {
690 ImGui::EndMenu();
691 }
692 }
693 if (!visibility.tools && p.draw_tools) {
694 p.draw_tools();
695 }
696 if (!visibility.save_content && p.draw_save_content &&
697 ImGui::BeginMenu(kToolbarSaveContentLabel)) {
699 ImGui::EndMenu();
700 }
701 if (!visibility.panel_mode && p.set_workflow_mode) {
702 if (ImGui::MenuItem(ICON_MD_VIEW_QUILT " Window Workflow")) {
703 request_panel_mode = true;
704 }
705 if (ImGui::IsItemHovered()) {
706 ImGui::SetTooltip("%s", kToolbarPanelWorkflowTooltip);
707 }
708 }
709
710 ImGui::EndPopup();
711 return request_panel_mode;
712}
713
714} // namespace
715
716bool DungeonWorkbenchToolbar::ShouldShowInlineRoomNav(float toolbar_width) {
717 (void)toolbar_width;
718 return true;
719}
720
721bool DungeonWorkbenchToolbar::Draw(const DungeonWorkbenchToolbarParams& p) {
722 if (!p.layout || !p.current_room_id || !p.split_view_enabled ||
723 !p.compare_room_id) {
724 ImGui::TextDisabled(tr("Workbench toolbar not wired"));
725 return false;
726 }
727
728 const ToolbarLayout layout =
729 ResolveToolbarLayout(std::max(ImGui::GetContentRegionAvail().x, 1.0f));
730 bool request_panel_mode = false;
731
732 // Gentle compaction only; kToolbarActionGap/ClusterGap own inter-button
733 // spacing, so don't shrink ItemSpacing.x below the theme default here.
734 const ImVec2 frame_pad = ImGui::GetStyle().FramePadding;
735 gui::StyleVarGuard frame_pad_guard(
736 ImGuiStyleVar_FramePadding, ImVec2(std::max(4.0f, frame_pad.x - 1.0f),
737 std::max(3.0f, frame_pad.y - 1.0f)));
738 gui::StyleVarGuard item_spacing_guard(
739 ImGuiStyleVar_ItemSpacing,
740 ImVec2(std::max(layout.spacing, 4.0f),
741 std::max(3.0f, ImGui::GetStyle().ItemSpacing.y - 1.0f)));
742 const ToolbarVisibility visibility = ResolveToolbarVisibility(p, layout);
743 const float right_actions_width =
744 ToolbarRightActionsWidth(p, layout, visibility);
745
746 // Connected toolbar controls: render as a floating canvas overlay (the
747 // viewer's built-in fallback) instead of inline. Inline rendering races
748 // against the right cluster because the controls are variable-width and
749 // only present when stitched mode is active. Overlay mode anchors them to
750 // the canvas viewport corner where they belong.
751 if (p.primary_viewer) {
753 }
754
755 // ── Render row ───────────────────────────────────────────────────
756 if (visibility.pane_toggles) {
757 (void)IconToggleButton("RoomsToggle", ICON_MD_LIST, ICON_MD_LIST,
758 &p.layout->show_left_sidebar, layout.button_size,
759 "Hide room browser", "Show room browser");
760 ImGui::SameLine(0.0f, kToolbarActionGap);
761 (void)IconToggleButton("InspectorToggle", ICON_MD_TUNE, ICON_MD_TUNE,
762 &p.layout->show_right_inspector, layout.button_size,
763 "Hide inspector", "Show inspector");
764 ImGui::SameLine(0.0f, kToolbarClusterGap);
765 }
766 DungeonRoomNavWidget::Draw("WorkbenchNav", *p.current_room_id,
768 if (visibility.stitched_rooms) {
769 ImGui::SameLine(0.0f, kToolbarClusterGap);
770 DrawCanvasModeSelector(p, layout);
771 }
772
773 if (visibility.entrance) {
774 ImGui::SameLine(0.0f, kToolbarClusterGap);
775 p.draw_entrance_context(kEntranceWidth);
776 }
777 if (visibility.layers) {
778 ImGui::SameLine(0.0f, kToolbarClusterGap);
780 layout.button_size);
781 }
782
783 if (visibility.recent_rooms) {
784 ImGui::SameLine(0.0f, kToolbarActionGap);
785 DrawRecentRoomsMenu(p, layout);
786 }
787
788 if (visibility.apply_room) {
789 ImGui::SameLine(0.0f, kToolbarActionGap);
790 if (workbench::DrawHeaderIconAction(
791 "ApplyRoomToolbar", ICON_MD_SAVE, layout.button_size,
792 "Apply this room into the loaded ROM buffer "
793 "(File > Save ROM persists to disk)")) {
795 }
796 }
797 if (visibility.dungeon_map) {
798 ImGui::SameLine(0.0f, kToolbarActionGap);
799 if (workbench::DrawHeaderIconAction("DungeonMapToolbar", ICON_MD_MAP,
800 layout.button_size,
801 "Open the Dungeon Map popup")) {
803 }
804 }
805
806 if (visibility.compare) {
807 ImGui::SameLine(0.0f, kToolbarClusterGap);
808 DrawCompareMenu(p, layout);
809 }
810
811 if (right_actions_width > 0.0f) {
812 const float last_left_edge =
813 ImGui::GetItemRectMax().x - ImGui::GetWindowPos().x;
814 const float right_start =
815 std::max(last_left_edge + kToolbarActionGap,
816 ImGui::GetWindowContentRegionMax().x - right_actions_width);
817 ImGui::SameLine(right_start, 0.0f);
818
819 bool drew_right_action = false;
820 if (visibility.view_options) {
821 DrawViewOptionsButton(p, layout);
822 drew_right_action = true;
823 }
824 if (visibility.tools) {
825 if (drew_right_action) {
826 ImGui::SameLine(0.0f, kToolbarActionGap);
827 }
828 p.draw_tools();
829 drew_right_action = true;
830 }
831 if (visibility.save_content) {
832 if (drew_right_action) {
833 ImGui::SameLine(0.0f, kToolbarActionGap);
834 }
835 DrawSaveContentButton(p, layout);
836 drew_right_action = true;
837 }
838 if (visibility.panel_mode) {
839 if (drew_right_action) {
840 ImGui::SameLine(0.0f, kToolbarActionGap);
841 }
842 if (workbench::DrawHeaderIconAction("PanelMode", ICON_MD_VIEW_QUILT,
843 layout.button_size,
844 kToolbarPanelWorkflowTooltip)) {
845 request_panel_mode = true;
846 }
847 drew_right_action = true;
848 }
849 if (visibility.overflow) {
850 if (drew_right_action) {
851 ImGui::SameLine(0.0f, kToolbarActionGap);
852 }
853 request_panel_mode |= DrawToolbarOverflowMenu(p, layout, visibility);
854 }
855 }
856 return request_panel_mode;
857}
858
859} // 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:29
RAII guard for ImGui style vars.
Definition style_guard.h:71
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:544
std::function< const std::deque< int > &()> get_recent_rooms
bool ShouldApply() const
Definition input.h:87
static constexpr float kIconButtonSmall
Definition ui_config.h:61
Modern project structure with comprehensive settings consolidation.
Definition project.h:290
Automatic widget registration helpers for ImGui Test Engine integration.