yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_canvas_viewer.h
Go to the documentation of this file.
1#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_CANVAS_VIEWER_H
2#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_CANVAS_VIEWER_H
3
4#include <array>
5#include <cstdint>
6#include <functional>
7#include <map>
8#include <memory>
9#include <optional>
10#include <string>
11#include <tuple>
12#include <unordered_map>
13#include <vector>
14
16#include "app/editor/editor.h"
21#include "core/project.h"
24#include "dungeon_room_store.h"
25#include "imgui/imgui.h"
26#include "rom/rom.h"
27#include "util/lru_cache.h"
30#include "zelda3/dungeon/room.h"
32#include "zelda3/game_data.h"
34
35namespace yaze {
36namespace editor {
37
38class MinecartTrackEditorPanel;
39class DungeonCanvasViewerTestPeer;
40
41enum class DungeonConnectedLinkType : uint8_t {
42 Door,
45};
46
48 int from_room_id = -1;
49 int to_room_id = -1;
52 // Provenance fields populated by CollectDungeonConnectedRoomLinkDiagnostics
53 // for hover tooltips and issue reports. Defaults represent "not applicable".
54 int slot_index = -1; // Staircase slot 0..3, -1 otherwise.
55 int16_t object_id = -1; // Placed staircase tile-object id, -1 otherwise.
56 int door_index = -1; // Index in room.GetDoors() for Door links.
58};
59
60// Discriminator for staircase configuration anomalies surfaced through
61// CollectDungeonConnectedRoomLinkDiagnostics. Each kind explains a different
62// failure mode the runtime silently ignores.
63enum class DungeonStaircaseIssueKind : uint8_t {
64 // Header destination is non-zero but no placed interroom-stair object
65 // consumes that slot. Runtime never traverses the link.
67 // Placed interroom-stair object exists but the matched header slot is 0
68 // (unset) or out of range — the runtime would jump to an invalid room.
70 // Placed interroom-stair object beyond the 4 header slots. The runtime
71 // can only consume four; extras can never be reached.
73};
74
75// Captures one anomaly found while resolving a room's staircase connectivity.
76// Field meaning depends on `kind`; defaults represent "not applicable".
78 int from_room_id = -1;
80 int slot_index = -1; // 0..3 for UnusedHeader/MissingDestination.
81 int header_room_id = -1; // Header destination value (raw, may be invalid).
82 int16_t object_id = -1; // Placed object id (MissingDestination/Extra).
83};
84
86 std::vector<DungeonConnectedRoomLink> links;
87 std::vector<DungeonStaircaseIssue> staircase_issues;
88};
89
98
99inline constexpr std::array<const char*, 6> kDungeonIssueCategoryLabels = {{
100 "Palette mismatch",
101 "Object draw mismatch",
102 "Door render mismatch",
103 "Entity mismatch",
104 "Overlay or collision mismatch",
105 "General room render mismatch",
106}};
107
109 return static_cast<int>(category);
110}
111
112inline const char* GetIssueCategoryLabel(int index) {
113 if (index < 0 ||
114 index >= static_cast<int>(kDungeonIssueCategoryLabels.size())) {
115 return kDungeonIssueCategoryLabels.back();
116 }
117 return kDungeonIssueCategoryLabels[static_cast<size_t>(index)];
118}
119
121 const DungeonObjectInteraction& object_interaction) {
122 if (!object_interaction.GetSelectedObjectIndices().empty()) {
124 }
125 if (object_interaction.HasEntitySelection()) {
126 switch (object_interaction.GetSelectedEntity().type) {
127 case EntityType::Door:
130 case EntityType::Item:
132 default:
133 break;
134 }
135 }
137}
138
139std::vector<DungeonConnectedRoomLink> CollectDungeonConnectedRoomLinks(
140 int room_id, const zelda3::Room& room,
141 const std::function<bool(int, zelda3::DoorDirection)>& has_reciprocal_door);
142
143// Returns the same set of links as CollectDungeonConnectedRoomLinks plus any
144// stale staircase header slots — i.e. header destinations with no placed
145// interroom-stair object consuming them. Provenance fields on each returned
146// link are populated for tooltip/issue-report surfacing.
147DungeonConnectedRoomLinkDiagnostics CollectDungeonConnectedRoomLinkDiagnostics(
148 int room_id, const zelda3::Room& room,
149 const std::function<bool(int, zelda3::DoorDirection)>& has_reciprocal_door);
150
151// Renders a single connectivity link as a one-line human-readable description
152// for tooltips and issue reports. Caller decides separator/heading.
154 const DungeonConnectedRoomLink& link);
155
156// Renders a staircase issue entry as a one-line warning for tooltips and
157// issue reports.
159 const DungeonStaircaseIssue& issue);
160
161// Returns the room id adjacent to `room_id` in the given direction, or -1 if
162// no such neighbor exists (out of the 16x16 room matrix).
163inline int NeighborRoomId(int room_id, zelda3::DoorDirection dir) {
164 if (room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
165 return -1;
166 }
167
168 constexpr int kCols = 16;
169 const int row = room_id / kCols;
170 const int col = room_id % kCols;
171
172 int candidate = -1;
173 switch (dir) {
175 candidate = row > 0 ? room_id - kCols : -1;
176 break;
178 candidate = room_id + kCols;
179 break;
181 candidate = col > 0 ? room_id - 1 : -1;
182 break;
184 candidate = col < kCols - 1 ? room_id + 1 : -1;
185 break;
186 }
187
188 return (candidate >= 0 && candidate < zelda3::kNumberOfRooms) ? candidate
189 : -1;
190}
191
192// Returns the opposite cardinal direction (N<->S, E<->W).
207
212 Manual, // Use ObjectDrawer routines
213 Emulator, // Use SNES emulator
214 Hybrid // Emulator with manual fallback
215};
216
218 public:
219 explicit DungeonCanvasViewer(Rom* rom = nullptr)
223 [this](int target_room, std::optional<size_t> target_door_index,
224 int target_tile_x, int target_tile_y) {
225 NavigateToRoom(target_room);
226 bool focused_target_door = false;
227 if (target_door_index.has_value() && rooms_ != nullptr) {
228 auto* target = rooms_->TryEnsureRoom(target_room);
229 if (target != nullptr &&
230 *target_door_index < target->GetDoors().size()) {
231 const auto [x, y, width, height] =
232 target->GetDoors()[*target_door_index].GetEditorBounds();
233 ScrollToTile((x + width / 2) / 8, (y + height / 2) / 8);
234 TriggerCanvasPingRect(x, y, width, height);
236 *target_door_index);
237 focused_target_door = true;
238 }
239 }
240 if (!focused_target_door && target_tile_x >= 0 &&
241 target_tile_y >= 0) {
242 ScrollToTile(target_tile_x, target_tile_y);
243 TriggerCanvasPingRect(target_tile_x * 8, target_tile_y * 8, 24, 24);
244 } else if (!focused_target_door) {
246 }
247 });
249 [this](int room_id, const zelda3::RoomObject& object) {
250 const auto semantics =
252 auto is_visible = [this, room_id](zelda3::LayerType layer) {
253 return IsLayerVisible(room_id, layer) &&
254 GetLayerBlendMode(room_id, layer) !=
256 };
257 switch (semantics.effective_bg_layer) {
259 return is_visible(zelda3::LayerType::BG1_Objects);
261 return is_visible(zelda3::LayerType::BG2_Objects);
263 return is_visible(zelda3::LayerType::BG1_Objects) ||
265 }
266 return true;
267 });
268 }
270
271 void DrawDungeonCanvas(int room_id);
272 std::optional<int> DrawConnectedRoomMatrix(int center_room_id);
273 void DrawConnectedToolbarControls(int center_room_id);
274 void Draw(int room_id);
275 void TriggerChangePing();
276 void TriggerCanvasPingRect(int pixel_x, int pixel_y, int pixel_w,
277 int pixel_h);
279 const std::vector<zelda3::RoomObject>& previous_objects,
280 const std::vector<zelda3::RoomObject>& next_objects);
281
282 // Unscaled content size of the connected-room graph centered on
283 // `center_room_id`. Used by the workbench to reserve scroll space for the
284 // body child before DrawConnectedRoomMatrix runs, so the matrix doesn't need
285 // to open its own BeginChild scroll wrapper.
286 ImVec2 GetConnectedContentSize(int center_room_id);
287 // Clamped connected-canvas zoom scale that matches the one used by
288 // DrawConnectedRoomMatrix when it next runs.
289 float ConnectedCanvasScale() const;
290 void SetConnectedControlsInline(bool inline_controls) {
291 connected_controls_inline_ = inline_controls;
292 }
293
304 EditorContext context() const { return {rom_, game_data_}; }
314 Rom* rom() const { return rom_; }
322 void SetRenderer(gfx::IRenderer* renderer) { renderer_ = renderer; }
323
324 // Room data access
333 // Rebind ROM-backed context without replacing this viewer. This preserves
334 // panel aliases into viewer state while discarding selections and caches
335 // that may refer to data decoded from the previous ROM bytes.
337 DungeonRoomStore* rooms, int room_id);
338 DungeonRoomStore* rooms() const { return rooms_; }
339 bool HasRooms() const { return rooms_ != nullptr; }
340
341 // Best-effort "current room" context for auxiliary panels that are driven by
342 // whichever room the viewer is currently drawing.
343 int current_room_id() const { return current_room_id_; }
344
345 // Workbench/Inspector integration.
346 void SetCompactHeaderMode(bool compact) { compact_header_mode_ = compact; }
348 void SetRoomDetailsExpanded(bool expanded) { show_room_details_ = expanded; }
349
350 // Used by overworld editor when double-clicking entrances
351 void set_active_rooms(const ImVector<int>& rooms) { active_rooms_ = rooms; }
353
354 // Palette access
357 }
358 void SetCurrentPaletteId(uint64_t id) { current_palette_id_ = id; }
360 bool force_refresh = false) {
361 bool palette_changed = current_palette_group_.name() != group.name() ||
362 current_palette_group_.size() != group.size();
363 for (int i = 0; !palette_changed && i < static_cast<int>(group.size());
364 ++i) {
365 palette_changed =
367 }
368 if (!palette_changed && !force_refresh) {
369 return;
370 }
371
373 object_interaction_.SetCurrentPaletteGroup(group, force_refresh);
374 }
375 void SetEntranceRenderContext(int entrance_id, uint8_t entrance_blockset) {
376 current_entrance_id_ = entrance_id;
377 current_entrance_blockset_ = entrance_blockset;
378 }
386 }
387 void SetRoomNavigationCallback(std::function<void(int)> callback) {
388 room_navigation_callback_ = std::move(callback);
389 }
390 // Callback to swap the current room in-place (for arrow navigation)
391 void SetRoomSwapCallback(std::function<void(int, int)> callback) {
392 room_swap_callback_ = std::move(callback);
393 }
394
395 bool CanNavigateRooms() const {
396 return room_swap_callback_ != nullptr ||
397 room_navigation_callback_ != nullptr;
398 }
399
400 // Navigate to another dungeon room using the same semantics as the header
401 // arrow buttons:
402 // - If a swap callback exists (workbench mode), swap in-place.
403 // - Otherwise, use the navigation callback (opens/switches room view).
404 void NavigateToRoom(int target_room) {
405 if (target_room < 0 || target_room >= zelda3::kNumberOfRooms) {
406 return;
407 }
410 } else if (room_navigation_callback_) {
411 room_navigation_callback_(target_room);
412 }
413 }
414 void SetShowObjectPanelCallback(std::function<void()> callback) {
415 show_object_panel_callback_ = std::move(callback);
416 }
417 void SetShowSpritePanelCallback(std::function<void()> callback) {
418 show_sprite_panel_callback_ = std::move(callback);
419 }
420 void SetShowItemPanelCallback(std::function<void()> callback) {
421 show_item_panel_callback_ = std::move(callback);
422 }
423 void SetShowRoomListCallback(std::function<void()> callback) {
424 show_room_list_callback_ = std::move(callback);
425 }
426 void SetShowRoomMatrixCallback(std::function<void()> callback) {
427 show_room_matrix_callback_ = std::move(callback);
428 }
429 void SetShowEntranceListCallback(std::function<void()> callback) {
430 show_entrance_list_callback_ = std::move(callback);
431 }
432
439
442 }
444
445 // Overlay toggles (used by settings panels / workbench UI).
469 }
476
477 bool show_grid() const { return show_grid_; }
478 void set_show_grid(bool show) { show_grid_ = show; }
479 int custom_grid_size() const { return custom_grid_size_; }
480 void set_custom_grid_size(int size) {
481 custom_grid_size_ = size > 0 ? size : 1;
482 }
484 void set_show_object_bounds(bool show) { show_object_bounds_ = show; }
488 }
492 void set_show_texture_debug(bool show) { show_texture_debug_ = show; }
493 bool show_layer_info() const { return show_layer_info_; }
494 void set_show_layer_info(bool show) { show_layer_info_ = show; }
503
504 // Mutable pointer accessors for OverlayManagerPanel binding.
505 bool* mutable_show_grid() { return &show_grid_; }
527 void SetShowRoomGraphicsCallback(std::function<void()> callback) {
528 show_room_graphics_callback_ = std::move(callback);
529 }
530 void SetShowDoorEditorCallback(std::function<void()> callback) {
531 show_door_editor_callback_ = std::move(callback);
532 }
533 void SetShowDungeonSettingsCallback(std::function<void()> callback) {
534 show_dungeon_settings_callback_ = std::move(callback);
535 }
536 void SetSaveRoomCallback(std::function<void(int)> callback) {
537 save_room_callback_ = std::move(callback);
538 }
540 std::function<void(int, const zelda3::RoomObject&)> callback) {
541 edit_graphics_callback_ = std::move(callback);
542 }
544 std::function<void(int, const zelda3::RoomObject&)> callback) {
545 edit_object_tiles_callback_ = std::move(callback);
546 }
550 void SetPinned(bool pinned) { is_pinned_ = pinned; }
551 void SetPinCallback(std::function<void(bool)> callback) {
552 pin_callback_ = std::move(callback);
553 }
555 // Refresh external previews without replacing room data or editing state.
557 const project::YazeProject* project() const { return project_; }
558
559 // Canvas access
561 const gui::Canvas& canvas() const { return canvas_; }
562
563 // Object interaction access
565
569
570 // Enable/disable object interaction mode
571 void SetObjectInteractionEnabled(bool enabled) {
573 }
577
578 // Make the room header controls non-interactive while still allowing canvas
579 // pan/zoom (useful for split/compare views).
580 void SetHeaderReadOnly(bool read_only) { header_read_only_ = read_only; }
581 bool header_read_only() const { return header_read_only_; }
582
583 // Hide the header chrome entirely (useful for stitched/split layouts where a
584 // parent container provides its own metadata/controls).
585 void SetHeaderVisible(bool visible) { header_visible_ = visible; }
586 bool header_visible() const { return header_visible_; }
593
594 // Set and get the object render mode
599
600 // Layer visibility controls (per-room) using RoomLayerManager
601 void SetLayerVisible(int room_id, zelda3::LayerType layer, bool visible) {
602 GetRoomLayerManager(room_id).SetLayerVisible(layer, visible);
603 }
604 bool IsLayerVisible(int room_id, zelda3::LayerType layer) const {
605 auto it = room_layer_managers_.find(room_id);
606 return it != room_layer_managers_.end()
607 ? it->second.manager.IsLayerVisible(layer)
608 : true;
609 }
610
611 // Legacy compatibility - BG1 visibility (combines layout + objects)
612 void SetBG1Visible(int room_id, bool visible) {
613 auto& mgr = GetRoomLayerManager(room_id);
614 mgr.SetLayerVisible(zelda3::LayerType::BG1_Layout, visible);
615 mgr.SetLayerVisible(zelda3::LayerType::BG1_Objects, visible);
616 }
617 void SetBG2Visible(int room_id, bool visible) {
618 auto& mgr = GetRoomLayerManager(room_id);
619 mgr.SetLayerVisible(zelda3::LayerType::BG2_Layout, visible);
620 mgr.SetLayerVisible(zelda3::LayerType::BG2_Objects, visible);
621 }
622 bool IsBG1Visible(int room_id) const {
623 auto it = room_layer_managers_.find(room_id);
624 if (it == room_layer_managers_.end())
625 return true;
626 return it->second.manager.IsLayerVisible(zelda3::LayerType::BG1_Layout) ||
627 it->second.manager.IsLayerVisible(zelda3::LayerType::BG1_Objects);
628 }
629 bool IsBG2Visible(int room_id) const {
630 auto it = room_layer_managers_.find(room_id);
631 if (it == room_layer_managers_.end())
632 return true;
633 return it->second.manager.IsLayerVisible(zelda3::LayerType::BG2_Layout) ||
634 it->second.manager.IsLayerVisible(zelda3::LayerType::BG2_Objects);
635 }
636
637 // Layer blend mode controls
638 void SetLayerBlendMode(int room_id, zelda3::LayerType layer,
640 GetRoomLayerManager(room_id).SetLayerBlendMode(layer, mode);
641 }
643 zelda3::LayerType layer) const {
644 auto it = room_layer_managers_.find(room_id);
645 return it != room_layer_managers_.end()
646 ? it->second.manager.GetLayerBlendMode(layer)
648 }
649
650 // Per-object translucency
651 void SetObjectTranslucent(int room_id, size_t object_index, bool translucent,
652 uint8_t alpha = 128) {
653 GetRoomLayerManager(room_id).SetObjectTranslucency(object_index,
654 translucent, alpha);
655 }
656
657 // Layer manager access
660 static zelda3::RoomLayerManager default_manager;
661 auto it = room_layer_managers_.find(room_id);
662 return it != room_layer_managers_.end() ? it->second.manager
663 : default_manager;
664 }
665
666 // Legacy BG2 layer type (mapped to blend mode)
667 void SetBG2LayerType(int room_id, int type) {
668 auto& mgr = GetRoomLayerManager(room_id);
670 switch (type) {
671 case 0:
673 break;
674 case 1:
676 break;
677 case 2:
679 break;
680 case 3:
682 break;
683 case 4:
685 break;
686 default:
688 break;
689 }
690 mgr.SetLayerBlendMode(zelda3::LayerType::BG2_Layout, mode);
691 mgr.SetLayerBlendMode(zelda3::LayerType::BG2_Objects, mode);
692 }
693 int GetBG2LayerType(int room_id) const {
695 switch (mode) {
697 return 0;
699 return 1;
701 return 2;
703 return 3;
705 return 4;
706 }
707 return 0;
708 }
709
710 // Set the object to be placed
712 // Pass palette group first so ghost preview can render correctly
715 }
718 false);
719 }
720
721 // Scroll the canvas to center on the given tile coordinates.
722 // The pending target is consumed by Draw() on the next frame.
723 void ScrollToTile(int tile_x, int tile_y) {
724 pending_scroll_target_ = {tile_x, tile_y};
725 }
727 return pending_scroll_target_.has_value();
728 }
729 std::optional<std::pair<int, int>> GetPendingScrollTarget() const {
731 }
732
733 // Object manipulation
736 return ImGui::GetCurrentContext() != nullptr &&
737 HasRoomCanvasShortcutFocusForFrame(ImGui::GetFrameCount());
738 }
739
740 // Entity visibility controls
741 void SetSpritesVisible(bool visible) {
743 }
745 void SetPotItemsVisible(bool visible) {
747 }
749
752 int col = 0;
753 int row = 0;
754 bool placed = false;
755 bool connected_to_start = false;
756 };
757
758 std::array<bool, zelda3::kNumberOfRooms> room_mask{};
759 std::array<RoomPlacement, zelda3::kNumberOfRooms> room_positions{};
760 std::array<std::string, zelda3::kNumberOfRooms> room_floor_labels{};
761 std::vector<std::string> floor_order;
762 std::vector<DungeonConnectedRoomLink> links;
763 // Staircase configuration anomalies aggregated across the whole
764 // connected component. Order is "first-encountered during BFS".
765 std::vector<DungeonStaircaseIssue> staircase_issues;
766 // Resolved links whose destination room falls outside the project's
767 // current dungeon registry scope (only populated when scoping is active).
768 // The runtime would still traverse these; the matrix omits the target
769 // rooms by default but surfaces the link as a diagnostic so the user can
770 // see "current dungeon connects out to room X via staircase".
771 std::vector<DungeonConnectedRoomLink> out_of_scope_links;
772 // True when BFS is restricted to a project-registry dungeon group.
774 int room_count = 0;
776 int min_col = 0;
777 int max_col = 0;
778 int min_row = 0;
779 int max_row = 0;
780 };
781
782 private:
785 friend class
787 friend class
789 friend class
791 friend class
793
795 int x = 0;
796 int y = 0;
797 int w = 0;
798 int h = 0;
799 };
800
802 const zelda3::RoomObject& object, int canvas_x,
803 int canvas_y);
804 void DrawRoomHeader(zelda3::Room& room, int room_id);
805 void DrawRoomNavigation(int room_id);
806 void DrawRoomPropertyTable(zelda3::Room& room, int room_id);
807 void DrawRecentRoomBreadcrumbs(int room_id);
808 void DrawLayerControls(zelda3::Room& room, int room_id);
809 void DrawCompactLayerToggles(int room_id);
810 void PopulateCanvasContextMenu(int room_id);
811 void AddInteractionContextMenuItems(int room_id);
812 void AddLoadedRoomContextMenuItems(int room_id);
815 std::vector<gui::CanvasMenuItem> BuildSelectionContextMenuItems(int room_id);
816 std::vector<gui::CanvasMenuItem> BuildSelectionContextMenuItems(
817 int room_id, std::optional<zelda3::RoomObject> context_object);
818 std::optional<zelda3::RoomObject> GetObjectUnderContextCursor(int room_id);
821 std::optional<gui::CanvasMenuItem> BuildOpenContextMenu();
826 std::string BuildRoomMetadataSummary(const zelda3::Room& room,
827 int room_id) const;
828 std::string BuildDrawIssueReport(const zelda3::Room& room, int room_id);
829 std::string BuildSelectionIssueReport(const zelda3::Room& room,
830 int room_id) const;
831 void OpenIssueReportPopup(const std::string& title,
832 const std::string& summary,
833 const std::string& kind_label,
834 const std::string& diagnostics, int room_id,
835 int default_category_index);
836 absl::Status PrepareIssueReportPopup(const std::string& title,
837 const std::string& summary,
838 const std::string& kind_label,
839 const std::string& diagnostics,
840 int room_id, int default_category_index);
841 std::string BuildIssueReportClipboardText() const;
842 absl::Status CaptureIssueReportScreenshot();
843 absl::Status AppendIssueReportToLog();
844 absl::Status EnsureIssueReportPersisted();
847 void SetIssueReportPopupStatus(std::string message, bool is_error);
849 void DrawIssueReportStatusMessage() const;
850 void RenderSprites(const gui::CanvasRuntime& rt, const zelda3::Room& room);
851 void RenderPotItems(const gui::CanvasRuntime& rt, const zelda3::Room& room);
853 const zelda3::Room& room);
854
855 // Touch interaction: long-press context menu for entities
857 const zelda3::Room& room);
858
859 // Visualization
861 const zelda3::Room& room);
864 GetCollisionOverlayCache(int room_id);
865
866 // Draw semi-transparent overlay on stored value 1 / BG2 overlay objects when
867 // mask mode is active.
869 const zelda3::Room& room);
870 void DrawPersistentDebugWindows(int room_id);
871 void DrawRoomDebugWindow(zelda3::Room& room, int room_id);
872 void DrawTextureDebugWindow(zelda3::Room& room, int room_id);
873 void DrawLayerInfoWindow(int room_id);
875 int room_id);
876 void DrawCoordinateOverlayHud(int room_id);
877 bool ValidateRoomCanvasRequest(int room_id);
881 void RefreshActiveRoomCanvasState(zelda3::Room& room, int room_id);
883 void SyncCanvasCaptureRegion(const gui::CanvasRuntime& canvas_rt);
884 void ConsumePendingCanvasScroll(const gui::CanvasRuntime& canvas_rt);
885 void DrawHeaderHiddenMetadataHud(int room_id);
886 void PrepareRoomStateForCanvas(zelda3::Room& room, int room_id);
887 void DrawRoomCanvasContent(const gui::CanvasRuntime& canvas_rt,
888 zelda3::Room& room, int room_id);
889 void HandleRoomCanvasDropTargets(zelda3::Room& room, int room_id);
890 void DrawChangePingOverlay(const gui::CanvasRuntime& canvas_rt,
891 const zelda3::Room& room);
892 void RecordVisitedRoom(int room_id);
894 RoomCompositeOutput& output);
897 void UpdateRoomCanvasShortcutFocus(bool hovered, bool pointer_pressed,
898 int frame_index);
899 bool HasRoomCanvasShortcutFocusForFrame(int frame_index) const;
900
901 // Room graphics management
902 // Load: Read from ROM, Render: Process pixels, Draw: Display on canvas
903 absl::Status LoadAndRenderRoomGraphics(int room_id);
909 int ApplyConnectedStaircaseIssueAutoFixes(int center_room_id);
910 bool DrawRoomBackgroundLayers(int room_id); // Draw room buffers to canvas
911
912 Rom* rom_ = nullptr;
914 gui::Canvas canvas_{"##DungeonCanvas", ImVec2(0x200, 0x200)};
915 gui::Canvas connected_canvas_{"##DungeonConnectedCanvas",
916 ImVec2(0x200, 0x200)};
917 // ObjectRenderer removed - use ObjectDrawer for rendering (production system)
919
920 // Touch gesture handler for long-press context menus on touch devices
922
923 // Scroll target
924 std::optional<std::pair<int, int>> pending_scroll_target_;
925
926 // Room data
929 std::vector<int> recently_visited_rooms_;
932 // Used by overworld editor for double-click entrance → open dungeon room
933 ImVector<int> active_rooms_;
942 // Last-frame visibility of the connected-mode side panel (overview /
943 // preview). Used to apply hysteresis when the viewport width crosses the
944 // panel's min-size threshold, so dragging the pane splitter past the
945 // threshold doesn't cause a sudden ~220 px horizontal snap.
948 ImVec2 connected_controls_custom_position_value_ = ImVec2(0.0f, 0.0f);
953
954 // Object interaction state
958
959 // Per-room layer managers (4-way visibility, blend modes, per-object translucency)
962 std::optional<std::pair<zelda3::LayerMergeType, zelda3::EffectKey>>
964 };
965 std::map<int, RoomLayerState> room_layer_managers_;
966
967 // The primary output is pinned because Canvas retains its Bitmap pointer.
968 // Connected previews keep stable outputs for rooms visible this frame.
971 std::unique_ptr<RoomCompositeOutput> output;
973 };
974 std::map<int, ConnectedRoomCompositeEntry> connected_composite_outputs_;
976
977 // Palette data
981 std::function<void(int)> room_navigation_callback_;
982 std::function<void(int, int)>
983 room_swap_callback_; // (old_room_id, new_room_id)
984 std::function<void()> show_object_panel_callback_;
985 std::function<void()> show_sprite_panel_callback_;
986 std::function<void()> show_item_panel_callback_;
987 std::function<void()> show_room_list_callback_;
988 std::function<void()> show_room_matrix_callback_;
989 std::function<void()> show_entrance_list_callback_;
990 std::function<void()> show_room_graphics_callback_;
991 std::function<void()> show_door_editor_callback_;
992 std::function<void()> show_dungeon_settings_callback_;
993 std::function<void(int)> save_room_callback_;
994 std::function<void(int, const zelda3::RoomObject&)> edit_graphics_callback_;
997 bool is_pinned_ = false;
998 std::function<void(bool)> pin_callback_;
1001 // Room graphics revisions are globally unique. Retain indexed pixels across
1002 // connected-room draws; presentation (palette, position, zoom) stays live.
1003 using SpritePreviewKey = std::tuple<uint64_t, uint8_t, int, bool>;
1005 std::vector<uint8_t> pixels;
1006 SDL_Rect bounds{};
1007 };
1010
1022 bool header_read_only_ = false;
1023 bool header_visible_ = true;
1025
1027 std::vector<uint16_t> track_tile_order_;
1028 std::vector<uint16_t> switch_tile_order_;
1030 std::unordered_map<int, DungeonRenderingHelpers::CollisionOverlayCache>
1032 std::bitset<256> minecart_sprite_ids_{};
1033
1034 // Object rendering cache
1042 std::vector<ObjectRenderCache> object_render_cache_;
1044
1045 // Debug state flags
1049 bool show_layer_info_ = false;
1050 bool show_grid_ = false; // Grid off by default for dungeon editor
1052 false; // Show camera coordinates on hover (toggle via Debug menu)
1053 int layout_override_ = -1; // -1 for no override
1056 ObjectRenderMode::Emulator; // Default to emulator rendering
1057
1058 // Object outline filtering toggles
1060 bool show_type1_objects = true; // Standard objects (0x00-0xFF)
1061 bool show_type2_objects = true; // Extended objects (0x100-0x1FF)
1062 bool show_type3_objects = true; // Special objects (0xF00-0xFFF)
1063 bool show_layer0_objects = true; // Primary / upper layer (BG1)
1064 bool show_layer1_objects = true; // BG2 overlay / lower layer (BG2)
1065 bool show_layer2_objects = true; // BG1 overlay room stream
1066 };
1068
1069 // Entity overlay visibility toggles
1071 bool show_sprites = true; // Show sprite entities
1072 bool show_pot_items = true; // Show pot item entities
1073 bool show_chests = true; // Show chest entities (future)
1074 };
1076
1078
1079 // Previous room state for change detection (per-viewer)
1084
1094 "Report Dungeon Issue##DungeonIssueReportPopup";
1106 std::vector<ChangePingRect> change_ping_rects_;
1108 std::function<void(int, const zelda3::RoomObject&)>
1110};
1111
1112} // namespace editor
1113} // namespace yaze
1114
1115#endif
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
std::optional< zelda3::RoomObject > GetObjectUnderContextCursor(int room_id)
gui::CanvasMenuItem BuildLayerVisibilityContextMenu(int room_id)
void HandleRoomCanvasDropTargets(zelda3::Room &room, int room_id)
bool RoomHasNonExitDoorInDirection(int room_id, zelda3::DoorDirection dir)
const zelda3::RoomLayerManager & GetRoomLayerManager(int room_id) const
void DrawChangePingOverlay(const gui::CanvasRuntime &canvas_rt, const zelda3::Room &room)
void SetShowEntranceListCallback(std::function< void()> callback)
void RefreshActiveRoomCanvasState(zelda3::Room &room, int room_id)
gfx::Bitmap * PrepareConnectedRoomCompositeBitmap(int room_id)
gui::CanvasMenuItem BuildRoomContextMenu(int room_id)
gui::CanvasMenuItem BuildDebugContextMenu(int room_id)
friend class DungeonEditorPaletteRefreshTest_CachedRoomRefreshesThroughViewerCompositePreparation_Test
std::string BuildDrawIssueReport(const zelda3::Room &room, int room_id)
std::optional< std::pair< int, int > > pending_scroll_target_
void SetLayerBlendMode(int room_id, zelda3::LayerType layer, zelda3::LayerBlendMode mode)
void SyncCanvasCaptureRegion(const gui::CanvasRuntime &canvas_rt)
std::vector< gui::CanvasMenuItem > BuildSelectionContextMenuItems(int room_id)
void SetEditorSystem(zelda3::DungeonEditorSystem *system)
int ApplyConnectedStaircaseIssueAutoFixes(int center_room_id)
void RenderPotItems(const gui::CanvasRuntime &rt, const zelda3::Room &room)
std::optional< int > DrawConnectedRoomMatrix(int center_room_id)
absl::Status LoadAndRenderRoomGraphics(int room_id)
gfx::Bitmap * PrepareRoomCompositeWithOutput(int room_id, RoomCompositeOutput &output)
void UpdateRoomCanvasShortcutFocus(bool hovered, bool pointer_pressed, int frame_index)
friend class DungeonEditorPaletteRefreshTest_DungeonMainNotificationSkipsUnrelatedCachedViewerWork_Test
void SetMinecartTrackPanel(MinecartTrackEditorPanel *panel)
void SetObjectTranslucent(int room_id, size_t object_index, bool translucent, uint8_t alpha=128)
void SetShowItemPanelCallback(std::function< void()> callback)
std::function< void()> show_door_editor_callback_
void SetShowRoomGraphicsCallback(std::function< void()> callback)
void SetConnectedControlsInline(bool inline_controls)
std::function< void(int, const zelda3::RoomObject &) edit_object_tiles_callback_)
zelda3::SpritePreviewResourceCache sprite_preview_resources_
void SetProject(const project::YazeProject *project)
std::function< void(int, const zelda3::RoomObject &) edit_graphics_callback_)
const DungeonRenderingHelpers::CollisionOverlayCache & GetCollisionOverlayCache(int room_id)
std::map< int, ConnectedRoomCompositeEntry > connected_composite_outputs_
void SetIssueReportPopupStatus(std::string message, bool is_error)
void set_active_rooms(const ImVector< int > &rooms)
void SetRenderer(gfx::IRenderer *renderer)
void SetEditGraphicsCallback(std::function< void(int, const zelda3::RoomObject &)> callback)
void SetBG2LayerType(int room_id, int type)
friend class DungeonEditorPaletteRefreshTest_CachedPanelPaletteRefreshSurvivesWorkbenchModeAndPreservesRoomPalette_Test
std::function< void()> show_entrance_list_callback_
void DrawTextureDebugWindow(zelda3::Room &room, int room_id)
zelda3::Room * EnsureRoomLoadedForConnectedView(int room_id)
void SetShowRoomListCallback(std::function< void()> callback)
ConnectedRoomGraphData connected_graph_cache_
zelda3::Room * PrepareActiveRoomForCanvasFrame(int room_id)
void RefreshRomBackedState(Rom *rom, zelda3::GameData *game_data, DungeonRoomStore *rooms, int room_id)
DungeonObjectInteraction object_interaction_
void SetObjectRenderMode(ObjectRenderMode mode)
void PrepareRoomStateForCanvas(zelda3::Room &room, int room_id)
void OpenIssueReportPopup(const std::string &title, const std::string &summary, const std::string &kind_label, const std::string &diagnostics, int room_id, int default_category_index)
gui::CanvasMenuItem BuildSelectionContextMenu(int room_id)
void DrawRoomCanvasContent(const gui::CanvasRuntime &canvas_rt, zelda3::Room &room, int room_id)
DungeonObjectInteraction & object_interaction()
void DrawRoomDebugWindow(zelda3::Room &room, int room_id)
ConnectedRoomGraphData BuildConnectedRoomGraph(int start_room_id)
friend class DungeonEditorPaletteRefreshTest_CachedCompositePreparationPreservesTargetEntranceContext_Test
std::string BuildRoomMetadataSummary(const zelda3::Room &room, int room_id) const
std::function< void()> show_room_list_callback_
void TriggerCanvasPingRect(int pixel_x, int pixel_y, int pixel_w, int pixel_h)
void SetShowRoomMatrixCallback(std::function< void()> callback)
const project::YazeProject * project_
void SetRoomNavigationCallback(std::function< void(int)> callback)
gui::CanvasMenuItem BuildReportContextMenu(int room_id)
void SetShowSpritePanelCallback(std::function< void()> callback)
void SetPreviewObject(const zelda3::RoomObject &object)
std::function< void()> show_object_panel_callback_
std::function< void(bool)> pin_callback_
std::optional< std::pair< int, int > > GetPendingScrollTarget() const
void SetBG1Visible(int room_id, bool visible)
void SetBG2Visible(int room_id, bool visible)
bool IsLayerVisible(int room_id, zelda3::LayerType layer) const
std::unordered_map< int, DungeonRenderingHelpers::CollisionOverlayCache > collision_overlay_cache_
void DrawLayerControls(zelda3::Room &room, int room_id)
void SetShowObjectPanelCallback(std::function< void()> callback)
void RenderSprites(const gui::CanvasRuntime &rt, const zelda3::Room &room)
gfx::Bitmap * PrepareRoomCompositeBitmap(int room_id)
zelda3::RoomLayerManager & GetRoomLayerManager(int room_id)
ObjectRenderMode GetObjectRenderMode() const
void DrawMaskHighlights(const gui::CanvasRuntime &rt, const zelda3::Room &room)
void TriggerObjectChangePing(const std::vector< zelda3::RoomObject > &previous_objects, const std::vector< zelda3::RoomObject > &next_objects)
gui::CanvasFrameOptions BuildRoomCanvasFrameOptions() const
void SetEntranceRenderContext(int entrance_id, uint8_t entrance_blockset)
absl::Status PrepareIssueReportPopup(const std::string &title, const std::string &summary, const std::string &kind_label, const std::string &diagnostics, int room_id, int default_category_index)
util::LruCache< SpritePreviewKey, CachedSpritePreview > sprite_preview_cache_
bool HasRoomCanvasShortcutFocusForFrame(int frame_index) const
std::function< void()> show_dungeon_settings_callback_
void SetRoomSwapCallback(std::function< void(int, int)> callback)
const project::YazeProject * project() const
void SetEditObjectTilesCallback(std::function< void(int, const zelda3::RoomObject &)> callback)
std::function< void()> show_room_matrix_callback_
void ScrollToTile(int tile_x, int tile_y)
zelda3::LayerBlendMode GetLayerBlendMode(int room_id, zelda3::LayerType layer) const
std::function< void()> show_room_graphics_callback_
void ConsumePendingCanvasScroll(const gui::CanvasRuntime &canvas_rt)
void SetSaveRoomCallback(std::function< void(int)> callback)
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group, bool force_refresh=false)
void DrawRoomHeader(zelda3::Room &room, int room_id)
std::function< void(int)> room_navigation_callback_
void RenderEntityOverlay(const gui::CanvasRuntime &rt, const zelda3::Room &room)
std::function< void()> show_item_panel_callback_
std::vector< ObjectRenderCache > object_render_cache_
void DisplayObjectInfo(const gui::CanvasRuntime &rt, const zelda3::RoomObject &object, int canvas_x, int canvas_y)
std::tuple< uint64_t, uint8_t, int, bool > SpritePreviewKey
std::function< void()> show_sprite_panel_callback_
std::function< void(int, int)> room_swap_callback_
DungeonRenderingHelpers::TrackCollisionConfig track_collision_config_
void SetLayerVisible(int room_id, zelda3::LayerType layer, bool visible)
void DrawObjectPositionOutlines(const gui::CanvasRuntime &rt, const zelda3::Room &room)
std::optional< gui::CanvasMenuItem > BuildOpenContextMenu()
void DrawRoomPropertyTable(zelda3::Room &room, int room_id)
void HandleTouchLongPressContextMenu(const gui::CanvasRuntime &rt, const zelda3::Room &room)
void SetShowDungeonSettingsCallback(std::function< void()> callback)
void SetRooms(DungeonRoomStore *rooms)
MinecartTrackEditorPanel * minecart_track_panel_
std::vector< ChangePingRect > change_ping_rects_
std::map< int, RoomLayerState > room_layer_managers_
void DrawRoomCanvasOverlays(const gui::CanvasRuntime &rt, zelda3::Room &room, int room_id)
std::string BuildSelectionIssueReport(const zelda3::Room &room, int room_id) const
gui::CanvasMenuItem BuildCopyExportContextMenu(int room_id)
void SetGameData(zelda3::GameData *game_data)
void SetShowDoorEditorCallback(std::function< void()> callback)
void SetPinCallback(std::function< void(bool)> callback)
std::function< void(int)> save_room_callback_
Handles object selection, placement, and interaction within the dungeon canvas.
void SetObjectSelectionVisibilityPredicate(std::function< bool(int, const zelda3::RoomObject &)> predicate)
void SetDoorPairNavigationCallback(std::function< void(int, std::optional< size_t >, int, int)> callback)
void SetEditorSystem(zelda3::DungeonEditorSystem *system)
std::vector< size_t > GetSelectedObjectIndices() const
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group, bool force_refresh=false)
void SelectEntity(EntityType type, size_t index)
void SetPreviewObject(const zelda3::RoomObject &object, bool loaded)
zelda3::Room * TryEnsureRoom(int room_id)
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:69
Defines an abstract interface for all rendering operations.
Definition irenderer.h:60
Handles touch input integration for Canvas.
Modern, robust canvas for drawing and manipulating graphics.
Definition canvas.h:64
RoomLayerManager - Manages layer visibility and compositing.
void SetLayerBlendMode(LayerType layer, LayerBlendMode mode)
void SetLayerVisible(LayerType layer, bool visible)
void SetObjectTranslucency(size_t object_index, bool translucent, uint8_t alpha=128)
const std::vector< Door > & GetDoors() const
Definition room.h:373
std::string FormatDungeonConnectedLinkDescription(const DungeonConnectedRoomLink &link)
int NeighborRoomId(int room_id, zelda3::DoorDirection dir)
DungeonConnectedRoomLinkDiagnostics CollectDungeonConnectedRoomLinkDiagnostics(int room_id, const zelda3::Room &room, const std::function< bool(int, zelda3::DoorDirection)> &has_reciprocal_door)
const char * GetIssueCategoryLabel(int index)
constexpr std::array< const char *, 6 > kDungeonIssueCategoryLabels
zelda3::DoorDirection OppositeDir(zelda3::DoorDirection dir)
DungeonIssueCategory GetDefaultSelectionIssueCategory(const DungeonObjectInteraction &object_interaction)
ObjectRenderMode
Handles the main dungeon canvas rendering and interaction.
constexpr int ToIssueCategoryIndex(DungeonIssueCategory category)
std::vector< DungeonConnectedRoomLink > CollectDungeonConnectedRoomLinks(int room_id, const zelda3::Room &room, const std::function< bool(int, zelda3::DoorDirection)> &has_reciprocal_door)
std::string FormatDungeonStaircaseIssueDescription(const DungeonStaircaseIssue &issue)
DoorType
Door types from ALTTP.
Definition door_types.h:33
@ NormalDoor
Normal door (upper layer)
LayerBlendMode
Layer blend modes for compositing.
ObjectLayerSemantics GetEffectiveObjectLayerSemantics(const RoomObject &object)
LayerType
Layer types for the 4-way visibility system.
constexpr int kNumberOfRooms
DoorDirection
Door direction on room walls.
Definition door_types.h:18
@ South
Bottom wall (horizontal door, 4x3 tiles)
@ North
Top wall (horizontal door, 4x3 tiles)
@ East
Right wall (vertical door, 3x4 tiles)
@ West
Left wall (vertical door, 3x4 tiles)
std::array< RoomPlacement, zelda3::kNumberOfRooms > room_positions
std::array< std::string, zelda3::kNumberOfRooms > room_floor_labels
std::optional< std::pair< zelda3::LayerMergeType, zelda3::EffectKey > > room_settings
std::vector< DungeonConnectedRoomLink > links
std::vector< DungeonStaircaseIssue > staircase_issues
Lightweight view into the essential runtime context (Rom + GameData)
Definition editor.h:72
zelda3::GameData * game_data
Definition editor.h:74
Represents a group of palettes.
const SnesPalette & palette_ref(int i) const
Declarative menu item definition.
Definition canvas_menu.h:64
Modern project structure with comprehensive settings consolidation.
Definition project.h:172