yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_canvas_viewer.cc
Go to the documentation of this file.
3
4#include <algorithm>
5#include <vector>
6
10
11namespace yaze::editor {
12
13namespace {
14
15constexpr double kChangePingDurationSeconds = 0.85;
16constexpr int kDungeonCanvasPixelSize = 512;
17constexpr size_t kRecentRoomLimit = 5;
18
20 const zelda3::RoomObject& rhs) {
21 return lhs.id_ == rhs.id_ && lhs.x() == rhs.x() && lhs.y() == rhs.y() &&
22 lhs.size() == rhs.size() && lhs.GetLayerValue() == rhs.GetLayerValue();
23}
24
25} // namespace
26
28
33
34absl::StatusOr<std::string> DungeonCanvasViewer::ExportRoomDocument(
35 int room_id) {
36 if (!room_document_export_callback_ || !rooms_ || room_id < 0 ||
37 room_id >= static_cast<int>(rooms_->size())) {
38 return absl::FailedPreconditionError("Room export is unavailable");
39 }
40 return room_document_export_callback_(room_id);
41}
42
43absl::StatusOr<DungeonRoomTransferPlan>
45 int source_room_id, const std::string& json,
46 const DungeonRoomTransferOptions& options) {
47 const auto* context =
50 context->current_room_id != current_room_id_) {
51 return absl::FailedPreconditionError(
52 "Room replacement is not editable in this view");
53 }
54 return room_transfer_preview_callback_(current_room_id_, source_room_id, json,
55 options);
56}
57
59 const DungeonRoomTransferPlan& plan) {
60 const auto* context =
63 context->current_room_id != current_room_id_ ||
65 return absl::FailedPreconditionError(
66 "Room replacement is not editable in this view");
67 }
69}
70
71absl::StatusOr<DungeonConnectionPlan>
73 const DungeonConnectionRequest& request) {
74 const auto* context =
78 context->current_room_id != current_room_id_) {
79 return absl::FailedPreconditionError(
80 "Open this room in the canvas to preview its connection");
81 }
83}
84
86 const DungeonConnectionPlan& plan) {
87 const auto* context =
91 context->current_room_id != current_room_id_) {
92 return absl::FailedPreconditionError(
93 "Door connections are not editable in this view");
94 }
96}
97
99 int room_id, const RoomMetadataEdit& edit) {
101 !rooms_->GetIfLoaded(room_id)) {
102 return absl::FailedPreconditionError("Room properties are not editable");
103 }
104 return metadata_edit_callback_(room_id, edit);
105}
106
108 const std::vector<RoomMetadataRequest>& requests) {
110 return absl::FailedPreconditionError("Room properties are not editable");
111 }
112 for (const auto& request : requests) {
113 if (!rooms_->GetIfLoaded(request.room_id)) {
114 return absl::FailedPreconditionError("Room properties are not loaded");
115 }
116 }
117 return metadata_batch_edit_callback_(requests);
118}
119
120absl::Status DungeonCanvasViewer::EditChest(int room_id, size_t index,
121 uint8_t item_id, bool big_chest) {
123 !rooms_->GetIfLoaded(room_id)) {
124 return absl::FailedPreconditionError("Chest contents are not editable");
125 }
126 return chest_edit_callback_(room_id, index, item_id, big_chest);
127}
128
129absl::Status DungeonCanvasViewer::DeleteChest(int room_id, size_t index) {
131 !rooms_->GetIfLoaded(room_id)) {
132 return absl::FailedPreconditionError("Chests are not editable");
133 }
134 return chest_delete_callback_(room_id, index);
135}
136
138 if (room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
139 return;
140 }
141
143 std::remove(recently_visited_rooms_.begin(),
144 recently_visited_rooms_.end(), room_id),
146 recently_visited_rooms_.insert(recently_visited_rooms_.begin(), room_id);
147 if (recently_visited_rooms_.size() > kRecentRoomLimit) {
148 recently_visited_rooms_.resize(kRecentRoomLimit);
149 }
150}
151
160
166
168 int room_id) {
169 auto& state = room_layer_managers_[room_id];
170 const auto* room = rooms_ ? rooms_->GetIfMaterialized(room_id) : nullptr;
171 // Initialize before controls can edit the manager. Reapply room defaults only
172 // when the header changes, not on every draw, so manual blend choices persist.
173 if (room && (!state.room_settings ||
174 state.room_settings->first != room->layer_merging() ||
175 state.room_settings->second != room->effect())) {
176 state.manager.ApplyLayerMerging(room->layer_merging());
177 state.manager.ApplyRoomEffect(room->effect());
178 state.room_settings = std::make_pair(room->layer_merging(), room->effect());
179 }
180 if (room) {
181 // Objects and tag2 can change the game's layer registers without changing
182 // the merge mode or effect. Refresh only this derived state so manual
183 // visibility and blend settings survive object edits.
184 state.manager.ApplyGameLayerRegisters(room->GameLayerRegisters());
185 }
186 return state.manager;
187}
188
190 change_ping_rects_.clear();
191 change_ping_start_time_ = ImGui::GetCurrentContext() ? ImGui::GetTime() : 0.0;
192}
193
194void DungeonCanvasViewer::TriggerCanvasPingRect(int pixel_x, int pixel_y,
195 int pixel_w, int pixel_h) {
196 change_ping_rects_.clear();
197 const int x = std::clamp(pixel_x, 0, kDungeonCanvasPixelSize - 1);
198 const int y = std::clamp(pixel_y, 0, kDungeonCanvasPixelSize - 1);
199 const int w =
200 std::clamp(pixel_w, 1, std::max(1, kDungeonCanvasPixelSize - x));
201 const int h =
202 std::clamp(pixel_h, 1, std::max(1, kDungeonCanvasPixelSize - y));
203 change_ping_rects_.push_back(ChangePingRect{x, y, w, h});
204 change_ping_start_time_ = ImGui::GetCurrentContext() ? ImGui::GetTime() : 0.0;
205}
206
208 const std::vector<zelda3::RoomObject>& previous_objects,
209 const std::vector<zelda3::RoomObject>& next_objects) {
210 change_ping_rects_.clear();
211
212 auto append_object_rect = [this](const zelda3::RoomObject& object) {
213 const auto [x, y, w, h] =
215 change_ping_rects_.push_back(
216 ChangePingRect{x, y, std::max(w, 8), std::max(h, 8)});
217 };
218
219 const size_t count = std::max(previous_objects.size(), next_objects.size());
220 for (size_t i = 0; i < count; ++i) {
221 const bool has_previous = i < previous_objects.size();
222 const bool has_next = i < next_objects.size();
223 if (has_previous && has_next &&
224 HasSameObjectIdentity(previous_objects[i], next_objects[i])) {
225 continue;
226 }
227 if (has_previous) {
228 append_object_rect(previous_objects[i]);
229 }
230 if (has_next) {
231 append_object_rect(next_objects[i]);
232 }
233 }
234
235 if (change_ping_rects_.empty()) {
237 return;
238 }
239
240 change_ping_start_time_ = ImGui::GetCurrentContext() ? ImGui::GetTime() : 0.0;
241}
242
244 auto apply_list = [](std::array<bool, 256>& dest,
245 const std::vector<uint16_t>& values) {
246 dest.fill(false);
247 for (uint16_t value : values) {
248 if (value < 256) {
249 dest[value] = true;
250 }
251 }
252 };
253 auto ids_valid = [](const std::vector<uint16_t>& values) {
254 std::array<bool, 256> seen{};
255 for (uint16_t value : values) {
256 if (value >= seen.size() || seen[value]) {
257 return false;
258 }
259 seen[value] = true;
260 }
261 return true;
262 };
263
264 std::vector<uint16_t> default_track_tile_list;
265 for (uint16_t tile = 0xB0; tile <= 0xBE; ++tile) {
266 default_track_tile_list.push_back(tile);
267 }
268 const std::vector<uint16_t> default_stop_tile_list = {0xB7, 0xB8, 0xB9, 0xBA};
269 const std::vector<uint16_t> default_switch_tile_list = {0xD0, 0xD1, 0xD2,
270 0xD3};
271
272 const auto& track_tiles =
275 : default_track_tile_list;
276 apply_list(track_collision_config_.track_tiles, track_tiles);
277 track_tile_order_ = track_tiles;
278
279 const auto& stop_tiles =
282 : default_stop_tile_list;
283 apply_list(track_collision_config_.stop_tiles, stop_tiles);
284
285 const auto& switch_tiles =
288 : default_switch_tile_list;
289 apply_list(track_collision_config_.switch_tiles, switch_tiles);
290 switch_tile_order_ = switch_tiles;
291
293 (track_tile_order_.size() == default_track_tile_list.size()) &&
294 (switch_tile_order_.size() == default_switch_tile_list.size()) &&
295 ids_valid(track_tile_order_) && ids_valid(switch_tile_order_);
296
297 minecart_sprite_ids_.reset();
298 std::vector<uint16_t> minecart_ids = {0xA3};
301 }
302 for (uint16_t id : minecart_ids) {
303 if (id < minecart_sprite_ids_.size()) {
304 minecart_sprite_ids_[id] = true;
305 }
306 }
307
309}
310
311void DungeonCanvasViewer::Draw(int room_id) {
312 DrawDungeonCanvas(room_id);
313}
314
316 zelda3::GameData* game_data,
317 DungeonRoomStore* rooms,
318 int room_id) {
321 // Refresh can replace Room values in place while all backing pointers stay
322 // identical. Discard presentation stamps before those replacements occur.
335
336 SetRom(rom);
339 current_room_id_ = room_id;
341
343 object_render_cache_.clear();
344 room_layer_managers_.clear();
346 prev_blockset_ = -1;
347 prev_palette_ = -1;
348 prev_layout_ = -1;
349 prev_spriteset_ = -1;
351 change_ping_rects_.clear();
353}
354
360
362 if (ImGui::GetCurrentContext() == nullptr) {
363 return;
364 }
365 const int frame = ImGui::GetFrameCount();
367 return;
368 }
370
371 // Keep one completed-frame grace period. This bounds the cache to the union
372 // of recently visible rooms without retiring a texture still referenced by
373 // another deferred ImGui draw in this frame.
374 std::erase_if(connected_composite_outputs_, [frame](const auto& item) {
375 return item.second.last_used_frame >= 0 &&
376 item.second.last_used_frame < frame - 1;
377 });
378}
379
381 current_room_id_ = room_id;
382 if (!ValidateRoomCanvasRequest(room_id)) {
383 return;
384 }
385 RecordVisitedRoom(room_id);
387
388 ImGui::BeginGroup();
390 zelda3::Room* active_room = PrepareActiveRoomForCanvasFrame(room_id);
391 if (header_visible_)
394 bool tracks = minecart_track_panel_->canvas_editing();
395 if (ImGui::Checkbox("Tracks##canvas_tracks", &tracks)) {
400 if (tracks)
403 }
404 }
405 ImGui::EndGroup();
406
407 // BeginCanvas renders context-menu callbacks before the room draw pass. Bind
408 // this viewer's bitmap and palette first so issue capture cannot sample a
409 // compare viewer or auxiliary preview left active by the previous frame.
410 if (active_room != nullptr) {
412 }
413
414 if (show_crumble_floor_ && active_room != nullptr) {
415 crumble_floor_view_.Sync(rom_, project_, *active_room);
416 const bool was_rehearsing = crumble_floor_view_.rehearsing();
418 if (!was_rehearsing && crumble_floor_view_.rehearsing()) {
429 }
430 }
431 }
432
434
435 // Workbench, Standalone, and Compare share one fitted, centered viewport.
436 // Free navigation belongs to Overview, not an ImGui bitmap scrollbar.
437 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
438 const bool viewport_visible = ImGui::BeginChild(
439 "##DungeonRoomViewport", ImVec2(0, 0), false,
440 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
441 ImGui::PopStyleVar();
442 if (!viewport_visible) {
443 ImGui::EndChild();
444 return;
445 }
446 const auto frame_opts = BuildRoomCanvasFrameOptions();
447 UpdateRoomCanvasViewport(*frame_opts.viewport_size);
448 auto canvas_rt = gui::BeginCanvas(canvas_, frame_opts);
449 const bool pointer_pressed = ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
450 ImGui::IsMouseClicked(ImGuiMouseButton_Right) ||
451 ImGui::IsMouseClicked(ImGuiMouseButton_Middle);
452 UpdateRoomCanvasShortcutFocus(canvas_rt.hovered, pointer_pressed,
453 ImGui::GetFrameCount());
454 SyncCanvasCaptureRegion(canvas_rt);
458
459 touch_handler_.ProcessForCanvas(canvas_rt.canvas_p0, canvas_rt.canvas_sz,
460 canvas_rt.hovered);
462
465
466 if (active_room != nullptr) {
467 DrawRoomCanvasContent(canvas_rt, *active_room, room_id);
468 DrawRoomCanvasOverlays(canvas_rt, *active_room, room_id);
469 DrawChangePingOverlay(canvas_rt, *active_room);
470 }
471
473
474 gui::EndCanvas(canvas_, canvas_rt, frame_opts);
475 ImGui::EndChild();
479}
480
482 bool pointer_pressed,
483 int frame_index) {
484 room_canvas_last_draw_frame_ = frame_index;
485 if (pointer_pressed) {
487 }
488}
489
496
498 const gui::CanvasRuntime& canvas_rt, const zelda3::Room& room) {
499 if (!canvas_rt.draw_list || change_ping_start_time_ < 0.0 ||
500 !ImGui::GetCurrentContext()) {
501 return;
502 }
503
504 const double elapsed = ImGui::GetTime() - change_ping_start_time_;
505 if (elapsed >= kChangePingDurationSeconds) {
507 change_ping_rects_.clear();
508 return;
509 }
510
511 std::vector<ChangePingRect> rects = change_ping_rects_;
512 if (rects.empty()) {
513 const auto& objects = room.GetTileObjects();
514 for (size_t index : object_interaction_.GetSelectedObjectIndices()) {
515 if (index >= objects.size()) {
516 continue;
517 }
518 const auto [x, y, w, h] =
520 objects[index]);
521 rects.push_back(ChangePingRect{x, y, std::max(w, 8), std::max(h, 8)});
522 }
523
524 auto selected_entities =
526 if (selected_entities.empty() && object_interaction_.HasEntitySelection()) {
527 selected_entities.push_back(object_interaction_.GetSelectedEntity());
528 }
529 for (const SelectedEntity entity : selected_entities) {
530 switch (entity.type) {
531 case EntityType::Door: {
532 const auto& doors = room.GetDoors();
533 if (entity.index < doors.size()) {
534 const auto [x, y, w, h] = doors[entity.index].GetEditorBounds();
535 rects.push_back(
536 ChangePingRect{x, y, std::max(w, 8), std::max(h, 8)});
537 }
538 break;
539 }
540 case EntityType::Sprite: {
541 const auto& sprites = room.GetSprites();
542 if (entity.index < sprites.size()) {
543 rects.push_back(ChangePingRect{sprites[entity.index].x() * 16,
544 sprites[entity.index].y() * 16, 16,
545 16});
546 }
547 break;
548 }
549 case EntityType::Item: {
550 const auto& pot_items = room.GetPotItems();
551 if (entity.index < pot_items.size()) {
552 rects.push_back(ChangePingRect{pot_items[entity.index].GetPixelX(),
553 pot_items[entity.index].GetPixelY(),
554 16, 16});
555 }
556 break;
557 }
558 case EntityType::Object:
559 case EntityType::None:
560 break;
561 }
562 }
563 }
564
565 if (rects.empty()) {
566 rects.push_back(
567 ChangePingRect{0, 0, kDungeonCanvasPixelSize, kDungeonCanvasPixelSize});
568 }
569
570 const float progress =
571 static_cast<float>(elapsed / kChangePingDurationSeconds);
572 const float remaining = std::clamp(1.0f - progress, 0.0f, 1.0f);
573 const float expand = 4.0f + 12.0f * progress;
574 const auto& theme = AgentUI::GetTheme();
575 ImVec4 fill = theme.dungeon_selection_pulsing;
576 fill.w = 0.18f * remaining;
577 ImVec4 border = theme.dungeon_selection_primary;
578 border.w = 0.95f * remaining;
579 const ImU32 fill_color = ImGui::GetColorU32(fill);
580 const ImU32 border_color = ImGui::GetColorU32(border);
581
582 const ImVec2 viewport_min = canvas_rt.canvas_p0;
583 const ImVec2 viewport_max(canvas_rt.canvas_p0.x + canvas_rt.canvas_sz.x,
584 canvas_rt.canvas_p0.y + canvas_rt.canvas_sz.y);
585
586 for (const ChangePingRect& rect : rects) {
587 const ImVec2 min(canvas_rt.canvas_p0.x + canvas_rt.scrolling.x +
588 static_cast<float>(rect.x) * canvas_rt.scale - expand,
589 canvas_rt.canvas_p0.y + canvas_rt.scrolling.y +
590 static_cast<float>(rect.y) * canvas_rt.scale - expand);
591 const ImVec2 max(
592 min.x + static_cast<float>(rect.w) * canvas_rt.scale + expand * 2.0f,
593 min.y + static_cast<float>(rect.h) * canvas_rt.scale + expand * 2.0f);
594
595 const bool visible = max.x >= viewport_min.x && min.x <= viewport_max.x &&
596 max.y >= viewport_min.y && min.y <= viewport_max.y;
597 if (visible) {
598 canvas_rt.draw_list->AddRectFilled(min, max, fill_color, 2.0f);
599 canvas_rt.draw_list->AddRect(min, max, border_color, 2.0f,
600 2.0f + 2.0f * remaining);
601 continue;
602 }
603
604 const ImVec2 center(
605 canvas_rt.canvas_p0.x + canvas_rt.scrolling.x +
606 (static_cast<float>(rect.x) + static_cast<float>(rect.w) * 0.5f) *
607 canvas_rt.scale,
608 canvas_rt.canvas_p0.y + canvas_rt.scrolling.y +
609 (static_cast<float>(rect.y) + static_cast<float>(rect.h) * 0.5f) *
610 canvas_rt.scale);
611 const float marker_min_x = viewport_min.x + 8.0f;
612 const float marker_min_y = viewport_min.y + 8.0f;
613 const float marker_max_x = std::max(marker_min_x, viewport_max.x - 8.0f);
614 const float marker_max_y = std::max(marker_min_y, viewport_max.y - 8.0f);
615 const ImVec2 clamped(std::clamp(center.x, marker_min_x, marker_max_x),
616 std::clamp(center.y, marker_min_y, marker_max_y));
617 canvas_rt.draw_list->AddCircleFilled(clamped, 5.0f + 4.0f * remaining,
618 fill_color);
619 canvas_rt.draw_list->AddCircle(clamped, 7.0f + 6.0f * remaining,
620 border_color, 18, 2.0f);
621 }
622}
623
624} // namespace yaze::editor
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:52
InteractionContext * context() const
Get the interaction context.
void Sync(const Rom *rom, const project::YazeProject *project, const zelda3::Room &room)
void DrawControls(bool interactive)
RoomTransferPreviewCallback room_transfer_preview_callback_
void DrawChangePingOverlay(const gui::CanvasRuntime &canvas_rt, const zelda3::Room &room)
DungeonRoomTransferEditorState room_transfer_state_
DungeonChestEditDialogState chest_edit_dialog_state_
void SyncCanvasCaptureRegion(const gui::CanvasRuntime &canvas_rt)
absl::Status ApplyRoomTransfer(const DungeonRoomTransferPlan &plan)
absl::StatusOr< std::string > ExportRoomDocument(int room_id)
RoomTransferApplyCallback room_transfer_apply_callback_
DoorConnectionPreviewCallback door_connection_preview_callback_
void UpdateRoomCanvasShortcutFocus(bool hovered, bool pointer_pressed, int frame_index)
absl::Status ApplyDoorConnection(const DungeonConnectionPlan &plan)
RoomDocumentExportCallback room_document_export_callback_
absl::Status EditRoomMetadataBatch(const std::vector< RoomMetadataRequest > &requests)
zelda3::SpritePreviewResourceCache sprite_preview_resources_
void SetProject(const project::YazeProject *project)
std::map< int, ConnectedRoomCompositeEntry > connected_composite_outputs_
absl::Status EditRoomMetadata(int room_id, const RoomMetadataEdit &edit)
absl::StatusOr< DungeonConnectionPlan > PreviewDoorConnection(const DungeonConnectionRequest &request)
zelda3::Room * PrepareActiveRoomForCanvasFrame(int room_id)
void RefreshRomBackedState(Rom *rom, zelda3::GameData *game_data, DungeonRoomStore *rooms, int room_id)
DungeonObjectInteraction object_interaction_
void DrawRoomCanvasContent(const gui::CanvasRuntime &canvas_rt, zelda3::Room &room, int room_id)
void TriggerCanvasPingRect(int pixel_x, int pixel_y, int pixel_w, int pixel_h)
absl::Status EditChest(int room_id, size_t index, uint8_t item_id, bool big_chest)
const project::YazeProject * project_
DoorConnectionApplyCallback door_connection_apply_callback_
absl::StatusOr< DungeonRoomTransferPlan > PreviewRoomTransfer(int source_room_id, const std::string &json, const DungeonRoomTransferOptions &options)
void UpdateRoomCanvasViewport(ImVec2 viewport_size)
std::unordered_map< int, DungeonRenderingHelpers::CollisionOverlayCache > collision_overlay_cache_
gfx::Bitmap * PrepareRoomCompositeBitmap(int room_id)
zelda3::RoomLayerManager & GetRoomLayerManager(int room_id)
void TriggerObjectChangePing(const std::vector< zelda3::RoomObject > &previous_objects, const std::vector< zelda3::RoomObject > &next_objects)
gui::CanvasFrameOptions BuildRoomCanvasFrameOptions() const
util::LruCache< SpritePreviewKey, CachedSpritePreview > sprite_preview_cache_
bool HasRoomCanvasShortcutFocusForFrame(int frame_index) const
absl::Status DeleteChest(int room_id, size_t index)
MetadataBatchEditCallback metadata_batch_edit_callback_
const project::YazeProject * project() const
DungeonConnectionEditorState connection_editor_state_
void ConsumePendingCanvasScroll(const gui::CanvasRuntime &canvas_rt)
util::LruCache< ItemPreviewKey, CachedSpritePreview > item_preview_cache_
std::vector< ObjectRenderCache > object_render_cache_
DungeonRenderingHelpers::TrackCollisionConfig track_collision_config_
void SetRooms(DungeonRoomStore *rooms)
MinecartTrackEditorPanel * minecart_track_panel_
void HandleRoomCanvasNavigation(gui::CanvasRuntime &canvas_rt)
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)
void SetGameData(zelda3::GameData *game_data)
void SetCurrentRoom(DungeonRoomStore *rooms, int room_id)
std::vector< size_t > GetSelectedObjectIndices() const
InteractionCoordinator & entity_coordinator()
Get the interaction coordinator for entity handling.
zelda3::Room * GetIfMaterialized(int room_id)
zelda3::Room * GetIfLoaded(int room_id)
const std::vector< SelectedEntity > & GetSelectedEntities() const
void ClearAllEntitySelections()
Clear all entity selections.
void CancelCurrentMode()
Cancel current mode and return to Select.
void CancelNodeDrag(const void *source_viewer=nullptr)
void Update()
Update touch state each frame.
void ProcessForCanvas(ImVec2 canvas_p0, ImVec2 canvas_sz, bool is_hovered)
Process touch gestures for the current canvas bounds.
static DimensionService & Get()
std::tuple< int, int, int, int > GetSelectionBoundsPixels(const RoomObject &obj) const
RoomLayerManager - Manages layer visibility and compositing.
uint8_t size() const
Definition room_object.h:89
uint8_t GetLayerValue() const
const std::vector< Door > & GetDoors() const
Definition room.h:413
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:316
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:445
const std::vector< PotItem > & GetPotItems() const
Definition room.h:429
bool SetContext(std::string_view project_path, std::string_view assets_path, std::string_view hack_name)
const AgentUITheme & GetTheme()
bool HasSameObjectIdentity(const zelda3::RoomObject &lhs, const zelda3::RoomObject &rhs)
Editors are the view controllers for the application.
void DrawDungeonRoomTransferPopup(DungeonCanvasViewer &viewer)
void EndCanvas(Canvas &canvas)
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
constexpr int kNumberOfRooms
Represents a selected entity in the dungeon editor.
std::vector< uint16_t > minecart_sprite_ids
Definition project.h:105
std::vector< uint16_t > track_stop_tiles
Definition project.h:100
std::vector< uint16_t > track_tiles
Definition project.h:99
std::vector< uint16_t > track_switch_tiles
Definition project.h:101
Modern project structure with comprehensive settings consolidation.
Definition project.h:290
DungeonOverlaySettings dungeon_overlay
Definition project.h:363