yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_editor_v2.h
Go to the documentation of this file.
1#ifndef YAZE_APP_EDITOR_DUNGEON_EDITOR_V2_H
2#define YAZE_APP_EDITOR_DUNGEON_EDITOR_V2_H
3
4#include <array>
5#include <cstdint>
6#include <deque>
7#include <functional>
8#include <memory>
9#include <optional>
10#include <string>
11#include <unordered_map>
12#include <utility>
13#include <vector>
14
15#include "absl/status/status.h"
16#include "absl/strings/str_format.h"
17#include "app/editor/editor.h"
25#include "dungeon_room_loader.h"
27#include "dungeon_room_store.h"
29#include "imgui/imgui.h"
32#include "rom/rom.h"
34#include "util/lru_cache.h"
37#include "zelda3/dungeon/room.h"
40#include "zelda3/game_data.h"
41
42namespace yaze {
43namespace editor {
44
45class CustomCollisionPanel;
46class DungeonEditorPaletteRefreshTestPeer;
47class DungeonEditorV2RegularEntranceTestPeer;
48class DungeonEditorV2ReloadTestPeer;
49class DungeonEditorV2SpawnPointTestPeer;
50class DungeonEditorV2SpawnRejectionTestPeer;
51class MinecartTrackEditorPanel;
52class ObjectTileEditorPanel;
53class OverlayManagerPanel;
54class PaletteEditorContent;
55class RoomTagEditorPanel;
56class WaterFillPanel;
57
98class DungeonEditorV2 : public Editor {
99 public:
100 explicit DungeonEditorV2(Rom* rom = nullptr);
101
102 ~DungeonEditorV2() override;
103
106 dependencies_.game_data = game_data; // Also set base class dependency
110 }
112 dungeon_editor_system_->SetGameData(game_data);
113 }
115 // Note: Canvas viewer game data is set lazily in GetViewerForRoom
116 // but we should update existing viewers
117 room_viewers_.ForEach(
118 [game_data](int, std::unique_ptr<DungeonCanvasViewer>& viewer) {
119 if (viewer)
120 viewer->SetGameData(game_data);
121 });
122 if (workbench_viewer_) {
123 workbench_viewer_->SetGameData(game_data);
124 }
127 }
128 }
129
130 // Editor interface
131 void Initialize() override;
132 absl::Status Load() override;
133 absl::Status Update() override;
134 absl::Status Undo() override;
135 absl::Status Redo() override;
136 absl::Status Cut() override;
137 absl::Status Copy() override;
138 absl::Status Paste() override;
139 absl::Status Find() override { return absl::UnimplementedError("Find"); }
140 absl::Status Save() override;
141 absl::Status BeginSaveTransaction() override;
142 void RollbackSaveTransaction() override;
143 void CommitSaveTransaction() override;
144 void ContributeStatus(StatusBar* status_bar) override;
145 absl::Status SaveRoom(int room_id);
146 int LoadedRoomCount() const;
147 // Room-specific pending state used by room counts and room-level UI.
148 int PendingRoomCount() const;
149 bool HasPendingRoomChanges() const;
150 // All dungeon save domains, including global entrance/spawn/pit metadata.
151 bool HasPendingDungeonChanges() const;
152 bool CurrentRoomHasPendingChanges() const;
153 int TotalRoomCount() const { return static_cast<int>(rooms_.size()); }
154
155 // Collect PC write ranges for all dirty/loaded rooms (for write conflict
156 // analysis). Returns pairs of (start_pc, end_pc) covering header and object
157 // regions that would be written during save.
158 std::vector<std::pair<uint32_t, uint32_t>> CollectWriteRanges() const;
159
160 // ROM management
161 void SetRom(Rom* rom) {
162 const bool rom_changed = rom_ != rom;
163 rom_ = rom;
167
168 // Propagate ROM to all rooms
170 // Reset viewers on ROM change
171 if (rom_changed) {
172 rooms_.Clear();
173 room_viewers_.Clear();
174 workbench_viewer_.reset();
176 }
177 }
178 // Reloads same-address ROM state in place so rooms, viewers, and workspace
179 // panel bindings retain stable addresses.
180 absl::Status RefreshRomBackedState();
181 Rom* rom() const { return rom_; }
182
183 // Room management
184 void add_room(int room_id);
185 void FocusRoom(int room_id);
186
187 // Agent/Automation controls
188 void SelectObject(int obj_id);
189 void SetAgentMode(bool enabled);
190
191 // ROM state
192 bool IsRomLoaded() const override { return rom_ && rom_->is_loaded(); }
193 std::string GetRomStatus() const override {
194 if (!rom_)
195 return "No ROM loaded";
196 if (!rom_->is_loaded())
197 return "ROM failed to load";
198 return absl::StrFormat("ROM loaded: %s", rom_->title());
199 }
200
201 // Open a workspace window by its id using WorkspaceWindowManager.
202 void OpenWindow(const std::string& window_id) {
205 }
206 }
207
208 // Explicit workflow toggle between integrated Workbench and standalone panels.
209 void SetWorkbenchWorkflowMode(bool enabled, bool show_toast = true);
210 // Queue a workflow mode change to run at a safe point in the next update.
211 void QueueWorkbenchWorkflowMode(bool enabled, bool show_toast = true);
212 // Queue a mode flip (Workbench <-> Standalone) for next update.
213 void ToggleWorkbenchWorkflowMode(bool show_toast = true);
214 bool IsWorkbenchWorkflowEnabled() const;
215
216 // Panel card IDs for programmatic access
217 static constexpr const char* kRoomSelectorId = "dungeon.room_selector";
218 static constexpr const char* kEntranceListId = "dungeon.entrance_list";
219 static constexpr const char* kRoomMatrixId = "dungeon.room_matrix";
220 static constexpr const char* kRoomGraphicsId = "dungeon.room_graphics";
221 static constexpr const char* kObjectSelectorId = "dungeon.object_selector";
222 static constexpr const char* kObjectToolsId = kObjectSelectorId;
223 static constexpr const char* kDoorEditorId = "dungeon.door_editor";
224 static constexpr const char* kPaletteEditorId = "dungeon.palette_editor";
225
226 // Public accessors for WASM API and automation
229 const ImVector<int>& active_rooms() const {
231 }
233 const DungeonRoomStore& rooms() const { return rooms_; }
234 gfx::IRenderer* renderer() const { return renderer_; }
245
250 const std::deque<int>& GetRecentRooms() const { return recent_rooms_; }
251
252 private:
259 friend class
262 friend class
264 friend class
266 friend class
268 friend class
270 friend class
272 friend class
274 friend class
276 friend class
278
280
281 // Draw the Room Panels
282 void DrawRoomPanels();
283 void DrawRoomTab(int room_id);
284
285 // Texture processing (critical for rendering)
288
289 // Room selection callback
290 void OnRoomSelected(int room_id, bool request_focus = true);
291 void OnRoomSelected(int room_id, RoomSelectionIntent intent);
292 void OnEntranceSelected(int entrance_id);
293
294 // Sync all sub-panels to the current room configuration
295 void SyncPanelsToRoom(int room_id);
297 void HandleArenaPaletteChanged(const std::string& group_name,
298 int palette_index);
301 uint8_t ResolveSelectedEntranceBlocksetForRoom(int room_id) const;
302 void ApplyEntranceRenderContext(int room_id);
303 void ConfigureViewerRenderContext(DungeonCanvasViewer* viewer, int room_id);
305
306 // Show or create a standalone room panel
307 void ShowRoomPanel(int room_id);
308
309 // Convenience action for Settings panel.
310 void SaveAllRooms();
311
312 // Object placement callback
313 void HandleObjectPlaced(const zelda3::RoomObject& obj);
314 void OpenGraphicsEditorForObject(int room_id,
315 const zelda3::RoomObject& object);
316
317 // Helper to get or create a viewer for a specific room
322 int room_id);
323 void TouchViewerLru(int room_id);
324 void RemoveViewerFromLru(int room_id);
325
326 absl::Status SaveRoomData(int room_id);
327 absl::Status RunWithSaveTransaction(
328 const std::function<absl::Status()>& operation);
329
330 // Data
334 std::array<zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots> entrances_;
335 std::array<zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints>
337
339 std::vector<std::pair<int, zelda3::Room::SaveDirtySnapshot>> room_states;
340 std::array<bool, zelda3::kNumDungeonEntranceSlots> entrance_dirty_states{};
341 std::array<bool, zelda3::kNumDungeonSpawnPoints> spawn_dirty_states{};
343 bool pit_damage_dirty = false;
345 };
346 std::optional<SaveTransactionSnapshot> save_transaction_snapshot_;
347
348 // Current selection state
350
351 // Active room tabs and card tracking for jump-to
352 ImVector<int> active_rooms_;
354
355 // Recent rooms history for quick navigation (most recent first, max 10)
356 static constexpr size_t kMaxRecentRooms = 10;
357 std::deque<int> recent_rooms_;
358 std::vector<int> pinned_rooms_;
359
360 // Workbench panel pointer (owned by WorkspaceWindowManager, stored for notifications).
362
363 // Palette management
369
370 // Components - these do all the work
373 static constexpr int kMaxCachedViewers = 20;
376 std::unique_ptr<DungeonCanvasViewer> workbench_viewer_;
377 std::unique_ptr<DungeonCanvasViewer> workbench_compare_viewer_;
378
380 // Panel pointers. WorkspaceWindowManager owns these when available; fallback
381 // unique_ptrs keep non-workspace tests and direct embedding paths alive.
382 // Workbench mode embeds room-local utilities in the inspector drawer and
383 // closes/hides their standalone window entries.
397
398 // Object editor is retained as the non-window backend for inspector actions.
399 // Other owned_* fields are fallbacks for tests without WorkspaceWindowManager.
400 std::unique_ptr<ObjectSelectorContent> owned_object_selector_panel_;
401 std::unique_ptr<ObjectEditorContent> owned_object_editor_content_;
402 std::unique_ptr<DoorEditorContent> owned_door_editor_panel_;
403 std::unique_ptr<RoomTagEditorPanel> owned_room_tag_editor_panel_;
404 std::unique_ptr<CustomCollisionPanel> owned_custom_collision_panel_;
405 std::unique_ptr<WaterFillPanel> owned_water_fill_panel_;
406 std::unique_ptr<MinecartTrackEditorPanel> owned_minecart_track_editor_panel_;
407 std::unique_ptr<zelda3::DungeonEditorSystem> dungeon_editor_system_;
408 std::unique_ptr<emu::render::EmulatorRenderService> render_service_;
409
410 bool is_loaded_ = false;
411
412 // Docking class for room windows to dock together
413 ImGuiWindowClass room_window_class_;
414
415 // Shared dock ID for all room panels to auto-dock together
416 ImGuiID room_dock_id_ = 0;
417
418 // Dynamic room cards - created per open room
419 std::unordered_map<int, std::shared_ptr<gui::PanelWindow>> room_cards_;
420
421 // Stable window slot mapping: room_id -> slot_id.
422 // Slot IDs are used in the "###" part of the window title so ImGui treats the
423 // window as the same entity even when its displayed room changes.
425 std::unordered_map<int, int> room_panel_slot_ids_;
426
427 // Pending undo snapshot: captured on mutation callback (before edit),
428 // finalized on cache invalidation callback (after edit) by pushing an undo
429 // action to the inherited undo_manager_.
430 struct PendingUndo {
431 int room_id = -1;
432 std::vector<zelda3::RoomObject> before_objects;
433 std::vector<size_t> before_selection;
434 };
436 bool has_pending_undo_ = false;
438
444
450
451 // Pending room swap (deferred until after draw phase completes)
452 struct PendingSwap {
453 int old_room_id = -1;
454 int new_room_id = -1;
455 bool pending = false;
456 };
458
460 bool enabled = false;
461 bool show_toast = true;
462 bool pending = false;
463 };
465
466 // Two-phase undo capture: BeginUndoSnapshot saves state before mutation,
467 // FinalizeUndoAction captures state after mutation and pushes the action.
468 void BeginUndoSnapshot(int room_id);
469 void FinalizeUndoAction(int room_id);
470 void RestoreRoomObjects(int room_id,
471 const std::vector<zelda3::RoomObject>& objects,
472 const std::vector<size_t>& selected_indices);
473
474 void BeginCollisionUndoSnapshot(int room_id);
475 void FinalizeCollisionUndoAction(int room_id);
476 void RestoreRoomCustomCollision(int room_id,
477 const zelda3::CustomCollisionMap& map);
478
479 void BeginWaterFillUndoSnapshot(int room_id);
480 void FinalizeWaterFillUndoAction(int room_id);
481 void RestoreRoomWaterFill(int room_id, const WaterFillSnapshot& snap);
482 void SwapRoomInPanel(int old_room_id, int new_room_id);
483 void ProcessPendingSwap(); // Process deferred swap after draw
485
486 // Room panel slot IDs provide stable ImGui window IDs across "swap room in
487 // panel" navigation. This keeps the window position/dock state when the room
488 // changes via the directional arrows.
489 int GetOrCreateRoomPanelSlotId(int room_id);
490 void ReleaseRoomPanelSlotId(int room_id);
491
492 // Defensive guard: returns true iff room_id is within the valid range
493 // [0, kNumberOfRooms). Use this instead of open-coded range checks.
494 static bool IsValidRoomId(int room_id) {
495 return room_id >= 0 && room_id < zelda3::kNumberOfRooms;
496 }
497};
498
499} // namespace editor
500} // namespace yaze
501
502#endif // YAZE_APP_EDITOR_DUNGEON_EDITOR_V2_H
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
bool is_loaded() const
Definition rom.h:144
auto title() const
Definition rom.h:149
DungeonEditorV2 - Simplified dungeon editor using component delegation.
class MinecartTrackEditorPanel * minecart_track_editor_panel_
friend class DungeonEditorV2SpawnRejectionTestPeer
std::unique_ptr< CustomCollisionPanel > owned_custom_collision_panel_
class CustomCollisionPanel * custom_collision_panel_
friend class DungeonEditorV2RegularEntranceTestPeer
friend class DungeonEditorV2RomSafetyTest_SaveAllRoomsRollsBackEarlierWritesOnLateFailure_Test
friend class DungeonEditorPaletteRefreshTest_CachedRoomRefreshesThroughViewerCompositePreparation_Test
void ToggleWorkbenchWorkflowMode(bool show_toast=true)
friend class DungeonEditorPaletteRefreshTest_SharedHudEditRefreshesRoomUsingDifferentDungeonPalette_Test
void RestoreRoomWaterFill(int room_id, const WaterFillSnapshot &snap)
void ContributeStatus(StatusBar *status_bar) override
void OpenGraphicsEditorForObject(int room_id, const zelda3::RoomObject &object)
class ItemEditorPanel * item_editor_panel_
std::unique_ptr< RoomTagEditorPanel > owned_room_tag_editor_panel_
ObjectSelectorContent * object_selector_panel() const
util::LruCache< int, std::unique_ptr< DungeonCanvasViewer > > room_viewers_
gfx::PaletteGroup current_palette_group_
void OnEntranceSelected(int entrance_id)
static constexpr const char * kEntranceListId
class SpriteEditorPanel * sprite_editor_panel_
void HandleObjectPlaced(const zelda3::RoomObject &obj)
std::string GetRomStatus() const override
DoorEditorContent * door_editor_panel() const
std::unique_ptr< zelda3::DungeonEditorSystem > dungeon_editor_system_
std::array< zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots > entrances_
void OpenWindow(const std::string &window_id)
DoorEditorContent * door_editor_panel_
std::unordered_map< int, int > room_panel_slot_ids_
friend class DungeonEditorV2RomSafetyTest_UndoSnapshotLeakDetection_Test
void RestoreRoomObjects(int room_id, const std::vector< zelda3::RoomObject > &objects, const std::vector< size_t > &selected_indices)
class DungeonWorkbenchContent * workbench_panel_
friend class DungeonEditorV2RomSafetyTest_ViewerCacheLRUEviction_Test
void OnRoomSelected(int room_id, bool request_focus=true)
friend class DungeonEditorV2RomSafetyTest_ViewerCacheNeverEvictsActiveRooms_Test
ObjectEditorContent * object_editor_content() const
PendingCollisionUndo pending_collision_undo_
ObjectTileEditorPanel * object_tile_editor_panel_
ObjectSelectorContent * object_selector_panel_
OverlayManagerPanel * overlay_manager_panel_
uint8_t ResolveSelectedEntranceBlocksetForRoom(int room_id) const
DungeonCanvasViewer * GetWorkbenchCompareViewer(int room_id)
void WireViewerPanelCallbacks(DungeonCanvasViewer *viewer)
gfx::IRenderer * renderer() const
std::unique_ptr< DungeonCanvasViewer > workbench_compare_viewer_
PendingWaterFillUndo pending_water_fill_undo_
std::unique_ptr< MinecartTrackEditorPanel > owned_minecart_track_editor_panel_
bool IsRomLoaded() const override
gui::PaletteEditorWidget palette_editor_
std::unique_ptr< ObjectEditorContent > owned_object_editor_content_
std::unique_ptr< DoorEditorContent > owned_door_editor_panel_
const std::deque< int > & GetRecentRooms() const
Get the list of recently visited room IDs.
static bool IsValidRoomId(int room_id)
void SwapRoomInPanel(int old_room_id, int new_room_id)
RoomGraphicsContent * room_graphics_panel_
absl::Status RunWithSaveTransaction(const std::function< absl::Status()> &operation)
const DungeonRoomStore & rooms() const
static constexpr int kMaxCachedViewers
static constexpr size_t kMaxRecentRooms
std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > spawn_points_
void SetWorkbenchWorkflowMode(bool enabled, bool show_toast=true)
class RoomTagEditorPanel * room_tag_editor_panel_
std::optional< SaveTransactionSnapshot > save_transaction_snapshot_
std::unique_ptr< ObjectSelectorContent > owned_object_selector_panel_
friend class DungeonEditorV2RomSafetyTest_ViewerCacheLRUAccessOrderUpdate_Test
friend class DungeonEditorPaletteRefreshTest_CompareViewerScopesEntranceContextToRequestedRoom_Test
static constexpr const char * kDoorEditorId
friend class DungeonEditorV2RomSafetyTest_LateCoordinatorRollbackRestoresEntranceDirtyState_Test
static constexpr const char * kObjectToolsId
DungeonCanvasViewer * GetViewerForRoom(int room_id)
class WaterFillPanel * water_fill_panel_
void RefreshWorkbenchViewerRuntimeContext(DungeonCanvasViewer *viewer, int room_id)
absl::Status Find() override
friend class DungeonEditorPaletteRefreshTest_DungeonMainEditRefreshesResolvedAliasesOnly_Test
ObjectSelectorContent * object_editor_panel() const
std::unique_ptr< emu::render::EmulatorRenderService > render_service_
DungeonCanvasViewer * GetWorkbenchViewer()
std::unordered_map< int, std::shared_ptr< gui::PanelWindow > > room_cards_
void ApplyEntranceRenderContext(int room_id)
void SetGameData(zelda3::GameData *game_data) override
friend class DungeonEditorV2RomSafetyTest_ObjectStreamUndoRedoRestoresSelectionIdentity_Test
static constexpr const char * kObjectSelectorId
void ConfigureViewerRenderContext(DungeonCanvasViewer *viewer, int room_id)
static constexpr const char * kRoomGraphicsId
void QueueWorkbenchWorkflowMode(bool enabled, bool show_toast=true)
ObjectEditorContent * object_editor_content_
static constexpr const char * kRoomMatrixId
void HandleDungeonPaletteChanged(gui::DungeonPaletteChange change)
static constexpr const char * kRoomSelectorId
const ImVector< int > & active_rooms() const
PaletteEditorContent * palette_editor_panel_
std::unique_ptr< DungeonCanvasViewer > workbench_viewer_
void RestoreRoomCustomCollision(int room_id, const zelda3::CustomCollisionMap &map)
std::vector< std::pair< uint32_t, uint32_t > > CollectWriteRanges() const
std::unique_ptr< WaterFillPanel > owned_water_fill_panel_
static constexpr const char * kPaletteEditorId
PendingWorkflowMode pending_workflow_mode_
void InvalidateDungeonPaletteUsers(gui::DungeonPaletteChange change)
void HandleArenaPaletteChanged(const std::string &group_name, int palette_index)
Manages loading and saving of dungeon room data.
void SetGameData(zelda3::GameData *game_data)
Handles room and entrance selection UI.
const ImVector< int > & active_rooms() const
void SetGameData(zelda3::GameData *game_data)
Interface for editor classes.
Definition editor.h:245
zelda3::GameData * game_data() const
Definition editor.h:320
EditorDependencies dependencies_
Definition editor.h:333
WindowContent for placing and managing dungeon pot items.
Browse and place dungeon objects.
void SetGameData(zelda3::GameData *game_data)
Panel for editing the tile8 composition of dungeon objects.
WindowContent wrapper for PaletteEditorWidget in dungeon context.
WindowContent for displaying room graphics blocks.
WindowContent showing all room tag slots and their usage across rooms.
WindowContent for placing and managing dungeon sprites.
A session-aware status bar displayed at the bottom of the application.
Definition status_bar.h:54
bool OpenWindow(size_t session_id, const std::string &base_window_id)
Defines an abstract interface for all rendering operations.
Definition irenderer.h:60
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
RoomSelectionIntent
Intent for room selection in the dungeon editor.
constexpr int kNumberOfRooms
std::vector< zelda3::RoomObject > before_objects
std::vector< std::pair< int, zelda3::Room::SaveDirtySnapshot > > room_states
std::array< bool, zelda3::kNumDungeonSpawnPoints > spawn_dirty_states
std::array< bool, zelda3::kNumDungeonEntranceSlots > entrance_dirty_states
zelda3::GameData * game_data
Definition editor.h:172
WorkspaceWindowManager * window_manager
Definition editor.h:181
Represents a group of palettes.