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
29absl::StatusOr<std::string> DungeonCanvasViewer::ExportRoomDocument(
30 int room_id) {
31 if (!room_document_export_callback_ || !rooms_ || room_id < 0 ||
32 room_id >= static_cast<int>(rooms_->size())) {
33 return absl::FailedPreconditionError("Room export is unavailable");
34 }
35 return room_document_export_callback_(room_id);
36}
37
38absl::StatusOr<DungeonRoomTransferPlan>
40 int source_room_id, const std::string& json,
41 const DungeonRoomTransferOptions& options) {
42 const auto* context =
45 context->current_room_id != current_room_id_) {
46 return absl::FailedPreconditionError(
47 "Room replacement is not editable in this view");
48 }
49 return room_transfer_preview_callback_(current_room_id_, source_room_id, json,
50 options);
51}
52
54 const DungeonRoomTransferPlan& plan) {
55 const auto* context =
58 context->current_room_id != current_room_id_ ||
60 return absl::FailedPreconditionError(
61 "Room replacement is not editable in this view");
62 }
64}
65
66absl::StatusOr<DungeonConnectionPlan>
68 const DungeonConnectionRequest& request) {
69 const auto* context =
73 context->current_room_id != current_room_id_) {
74 return absl::FailedPreconditionError(
75 "Open this room in the canvas to preview its connection");
76 }
78}
79
81 const DungeonConnectionPlan& plan) {
82 const auto* context =
86 context->current_room_id != current_room_id_) {
87 return absl::FailedPreconditionError(
88 "Door connections are not editable in this view");
89 }
91}
92
94 int room_id, const RoomMetadataEdit& edit) {
96 !rooms_->GetIfLoaded(room_id)) {
97 return absl::FailedPreconditionError("Room properties are not editable");
98 }
99 return metadata_edit_callback_(room_id, edit);
100}
101
103 const std::vector<RoomMetadataRequest>& requests) {
105 return absl::FailedPreconditionError("Room properties are not editable");
106 }
107 for (const auto& request : requests) {
108 if (!rooms_->GetIfLoaded(request.room_id)) {
109 return absl::FailedPreconditionError("Room properties are not loaded");
110 }
111 }
112 return metadata_batch_edit_callback_(requests);
113}
114
115absl::Status DungeonCanvasViewer::EditChest(int room_id, size_t index,
116 uint8_t item_id, bool big_chest) {
118 !rooms_->GetIfLoaded(room_id)) {
119 return absl::FailedPreconditionError("Chest contents are not editable");
120 }
121 return chest_edit_callback_(room_id, index, item_id, big_chest);
122}
123
124absl::Status DungeonCanvasViewer::DeleteChest(int room_id, size_t index) {
126 !rooms_->GetIfLoaded(room_id)) {
127 return absl::FailedPreconditionError("Chests are not editable");
128 }
129 return chest_delete_callback_(room_id, index);
130}
131
133 if (room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
134 return;
135 }
136
138 std::remove(recently_visited_rooms_.begin(),
139 recently_visited_rooms_.end(), room_id),
141 recently_visited_rooms_.insert(recently_visited_rooms_.begin(), room_id);
142 if (recently_visited_rooms_.size() > kRecentRoomLimit) {
143 recently_visited_rooms_.resize(kRecentRoomLimit);
144 }
145}
146
155
160
162 int room_id) {
163 auto& state = room_layer_managers_[room_id];
164 const auto* room = rooms_ ? rooms_->GetIfMaterialized(room_id) : nullptr;
165 // Initialize before controls can edit the manager. Reapply room defaults only
166 // when the header changes, not on every draw, so manual blend choices persist.
167 if (room && (!state.room_settings ||
168 state.room_settings->first != room->layer_merging() ||
169 state.room_settings->second != room->effect())) {
170 state.manager.ApplyLayerMerging(room->layer_merging());
171 state.manager.ApplyRoomEffect(room->effect());
172 state.room_settings = std::make_pair(room->layer_merging(), room->effect());
173 }
174 if (room) {
175 // Objects and tag2 can change the game's layer registers without changing
176 // the merge mode or effect. Refresh only this derived state so manual
177 // visibility and blend settings survive object edits.
178 state.manager.ApplyGameLayerRegisters(room->GameLayerRegisters());
179 }
180 return state.manager;
181}
182
184 change_ping_rects_.clear();
185 change_ping_start_time_ = ImGui::GetCurrentContext() ? ImGui::GetTime() : 0.0;
186}
187
188void DungeonCanvasViewer::TriggerCanvasPingRect(int pixel_x, int pixel_y,
189 int pixel_w, int pixel_h) {
190 change_ping_rects_.clear();
191 const int x = std::clamp(pixel_x, 0, kDungeonCanvasPixelSize - 1);
192 const int y = std::clamp(pixel_y, 0, kDungeonCanvasPixelSize - 1);
193 const int w =
194 std::clamp(pixel_w, 1, std::max(1, kDungeonCanvasPixelSize - x));
195 const int h =
196 std::clamp(pixel_h, 1, std::max(1, kDungeonCanvasPixelSize - y));
197 change_ping_rects_.push_back(ChangePingRect{x, y, w, h});
198 change_ping_start_time_ = ImGui::GetCurrentContext() ? ImGui::GetTime() : 0.0;
199}
200
202 const std::vector<zelda3::RoomObject>& previous_objects,
203 const std::vector<zelda3::RoomObject>& next_objects) {
204 change_ping_rects_.clear();
205
206 auto append_object_rect = [this](const zelda3::RoomObject& object) {
207 const auto [x, y, w, h] =
209 change_ping_rects_.push_back(
210 ChangePingRect{x, y, std::max(w, 8), std::max(h, 8)});
211 };
212
213 const size_t count = std::max(previous_objects.size(), next_objects.size());
214 for (size_t i = 0; i < count; ++i) {
215 const bool has_previous = i < previous_objects.size();
216 const bool has_next = i < next_objects.size();
217 if (has_previous && has_next &&
218 HasSameObjectIdentity(previous_objects[i], next_objects[i])) {
219 continue;
220 }
221 if (has_previous) {
222 append_object_rect(previous_objects[i]);
223 }
224 if (has_next) {
225 append_object_rect(next_objects[i]);
226 }
227 }
228
229 if (change_ping_rects_.empty()) {
231 return;
232 }
233
234 change_ping_start_time_ = ImGui::GetCurrentContext() ? ImGui::GetTime() : 0.0;
235}
236
238 auto apply_list = [](std::array<bool, 256>& dest,
239 const std::vector<uint16_t>& values) {
240 dest.fill(false);
241 for (uint16_t value : values) {
242 if (value < 256) {
243 dest[value] = true;
244 }
245 }
246 };
247 auto ids_valid = [](const std::vector<uint16_t>& values) {
248 std::array<bool, 256> seen{};
249 for (uint16_t value : values) {
250 if (value >= seen.size() || seen[value]) {
251 return false;
252 }
253 seen[value] = true;
254 }
255 return true;
256 };
257
258 std::vector<uint16_t> default_track_tile_list;
259 for (uint16_t tile = 0xB0; tile <= 0xBE; ++tile) {
260 default_track_tile_list.push_back(tile);
261 }
262 const std::vector<uint16_t> default_stop_tile_list = {0xB7, 0xB8, 0xB9, 0xBA};
263 const std::vector<uint16_t> default_switch_tile_list = {0xD0, 0xD1, 0xD2,
264 0xD3};
265
266 const auto& track_tiles =
269 : default_track_tile_list;
270 apply_list(track_collision_config_.track_tiles, track_tiles);
271 track_tile_order_ = track_tiles;
272
273 const auto& stop_tiles =
276 : default_stop_tile_list;
277 apply_list(track_collision_config_.stop_tiles, stop_tiles);
278
279 const auto& switch_tiles =
282 : default_switch_tile_list;
283 apply_list(track_collision_config_.switch_tiles, switch_tiles);
284 switch_tile_order_ = switch_tiles;
285
287 (track_tile_order_.size() == default_track_tile_list.size()) &&
288 (switch_tile_order_.size() == default_switch_tile_list.size()) &&
289 ids_valid(track_tile_order_) && ids_valid(switch_tile_order_);
290
291 minecart_sprite_ids_.reset();
292 std::vector<uint16_t> minecart_ids = {0xA3};
295 }
296 for (uint16_t id : minecart_ids) {
297 if (id < minecart_sprite_ids_.size()) {
298 minecart_sprite_ids_[id] = true;
299 }
300 }
301
303}
304
305void DungeonCanvasViewer::Draw(int room_id) {
306 DrawDungeonCanvas(room_id);
307}
308
310 zelda3::GameData* game_data,
311 DungeonRoomStore* rooms,
312 int room_id) {
313 // Refresh can replace Room values in place while all backing pointers stay
314 // identical. Discard presentation stamps before those replacements occur.
325
326 SetRom(rom);
329 current_room_id_ = room_id;
331
333 object_render_cache_.clear();
334 room_layer_managers_.clear();
336 prev_blockset_ = -1;
337 prev_palette_ = -1;
338 prev_layout_ = -1;
339 prev_spriteset_ = -1;
341 change_ping_rects_.clear();
343}
344
350
352 if (ImGui::GetCurrentContext() == nullptr) {
353 return;
354 }
355 const int frame = ImGui::GetFrameCount();
357 return;
358 }
360
361 // Keep one completed-frame grace period. This bounds the cache to the union
362 // of recently visible rooms without retiring a texture still referenced by
363 // another deferred ImGui draw in this frame.
364 std::erase_if(connected_composite_outputs_, [frame](const auto& item) {
365 return item.second.last_used_frame >= 0 &&
366 item.second.last_used_frame < frame - 1;
367 });
368}
369
371 current_room_id_ = room_id;
372 if (!ValidateRoomCanvasRequest(room_id)) {
373 return;
374 }
375 RecordVisitedRoom(room_id);
377
378 ImGui::BeginGroup();
380 zelda3::Room* active_room = PrepareActiveRoomForCanvasFrame(room_id);
381 if (header_visible_)
384 bool tracks = minecart_track_panel_->canvas_editing();
385 if (ImGui::Checkbox("Tracks##canvas_tracks", &tracks)) {
390 if (tracks)
393 }
394 }
395 ImGui::EndGroup();
396
397 // BeginCanvas renders context-menu callbacks before the room draw pass. Bind
398 // this viewer's bitmap and palette first so issue capture cannot sample a
399 // compare viewer or auxiliary preview left active by the previous frame.
400 if (active_room != nullptr) {
402 }
403
404 if (show_crumble_floor_ && active_room != nullptr) {
405 crumble_floor_view_.Sync(rom_, project_, *active_room);
406 const bool was_rehearsing = crumble_floor_view_.rehearsing();
408 if (!was_rehearsing && crumble_floor_view_.rehearsing()) {
418 }
419 }
420 }
421
423
424 // Workbench, Standalone, and Compare share one fitted, centered viewport.
425 // Free navigation belongs to Overview, not an ImGui bitmap scrollbar.
426 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
427 const bool viewport_visible = ImGui::BeginChild(
428 "##DungeonRoomViewport", ImVec2(0, 0), false,
429 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
430 ImGui::PopStyleVar();
431 if (!viewport_visible) {
432 ImGui::EndChild();
433 return;
434 }
435 const auto frame_opts = BuildRoomCanvasFrameOptions();
436 UpdateRoomCanvasViewport(*frame_opts.viewport_size);
437 auto canvas_rt = gui::BeginCanvas(canvas_, frame_opts);
438 const bool pointer_pressed = ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
439 ImGui::IsMouseClicked(ImGuiMouseButton_Right) ||
440 ImGui::IsMouseClicked(ImGuiMouseButton_Middle);
441 UpdateRoomCanvasShortcutFocus(canvas_rt.hovered, pointer_pressed,
442 ImGui::GetFrameCount());
443 SyncCanvasCaptureRegion(canvas_rt);
447
448 touch_handler_.ProcessForCanvas(canvas_rt.canvas_p0, canvas_rt.canvas_sz,
449 canvas_rt.hovered);
451
454
455 if (active_room != nullptr) {
456 DrawRoomCanvasContent(canvas_rt, *active_room, room_id);
457 DrawRoomCanvasOverlays(canvas_rt, *active_room, room_id);
458 DrawChangePingOverlay(canvas_rt, *active_room);
459 }
460
462
463 gui::EndCanvas(canvas_, canvas_rt, frame_opts);
464 ImGui::EndChild();
467}
468
470 bool pointer_pressed,
471 int frame_index) {
472 room_canvas_last_draw_frame_ = frame_index;
473 if (pointer_pressed) {
475 }
476}
477
484
486 const gui::CanvasRuntime& canvas_rt, const zelda3::Room& room) {
487 if (!canvas_rt.draw_list || change_ping_start_time_ < 0.0 ||
488 !ImGui::GetCurrentContext()) {
489 return;
490 }
491
492 const double elapsed = ImGui::GetTime() - change_ping_start_time_;
493 if (elapsed >= kChangePingDurationSeconds) {
495 change_ping_rects_.clear();
496 return;
497 }
498
499 std::vector<ChangePingRect> rects = change_ping_rects_;
500 if (rects.empty()) {
501 const auto& objects = room.GetTileObjects();
502 for (size_t index : object_interaction_.GetSelectedObjectIndices()) {
503 if (index >= objects.size()) {
504 continue;
505 }
506 const auto [x, y, w, h] =
508 objects[index]);
509 rects.push_back(ChangePingRect{x, y, std::max(w, 8), std::max(h, 8)});
510 }
511
512 auto selected_entities =
514 if (selected_entities.empty() && object_interaction_.HasEntitySelection()) {
515 selected_entities.push_back(object_interaction_.GetSelectedEntity());
516 }
517 for (const SelectedEntity entity : selected_entities) {
518 switch (entity.type) {
519 case EntityType::Door: {
520 const auto& doors = room.GetDoors();
521 if (entity.index < doors.size()) {
522 const auto [x, y, w, h] = doors[entity.index].GetEditorBounds();
523 rects.push_back(
524 ChangePingRect{x, y, std::max(w, 8), std::max(h, 8)});
525 }
526 break;
527 }
528 case EntityType::Sprite: {
529 const auto& sprites = room.GetSprites();
530 if (entity.index < sprites.size()) {
531 rects.push_back(ChangePingRect{sprites[entity.index].x() * 16,
532 sprites[entity.index].y() * 16, 16,
533 16});
534 }
535 break;
536 }
537 case EntityType::Item: {
538 const auto& pot_items = room.GetPotItems();
539 if (entity.index < pot_items.size()) {
540 rects.push_back(ChangePingRect{pot_items[entity.index].GetPixelX(),
541 pot_items[entity.index].GetPixelY(),
542 16, 16});
543 }
544 break;
545 }
546 case EntityType::Object:
547 case EntityType::None:
548 break;
549 }
550 }
551 }
552
553 if (rects.empty()) {
554 rects.push_back(
555 ChangePingRect{0, 0, kDungeonCanvasPixelSize, kDungeonCanvasPixelSize});
556 }
557
558 const float progress =
559 static_cast<float>(elapsed / kChangePingDurationSeconds);
560 const float remaining = std::clamp(1.0f - progress, 0.0f, 1.0f);
561 const float expand = 4.0f + 12.0f * progress;
562 const auto& theme = AgentUI::GetTheme();
563 ImVec4 fill = theme.dungeon_selection_pulsing;
564 fill.w = 0.18f * remaining;
565 ImVec4 border = theme.dungeon_selection_primary;
566 border.w = 0.95f * remaining;
567 const ImU32 fill_color = ImGui::GetColorU32(fill);
568 const ImU32 border_color = ImGui::GetColorU32(border);
569
570 const ImVec2 viewport_min = canvas_rt.canvas_p0;
571 const ImVec2 viewport_max(canvas_rt.canvas_p0.x + canvas_rt.canvas_sz.x,
572 canvas_rt.canvas_p0.y + canvas_rt.canvas_sz.y);
573
574 for (const ChangePingRect& rect : rects) {
575 const ImVec2 min(canvas_rt.canvas_p0.x + canvas_rt.scrolling.x +
576 static_cast<float>(rect.x) * canvas_rt.scale - expand,
577 canvas_rt.canvas_p0.y + canvas_rt.scrolling.y +
578 static_cast<float>(rect.y) * canvas_rt.scale - expand);
579 const ImVec2 max(
580 min.x + static_cast<float>(rect.w) * canvas_rt.scale + expand * 2.0f,
581 min.y + static_cast<float>(rect.h) * canvas_rt.scale + expand * 2.0f);
582
583 const bool visible = max.x >= viewport_min.x && min.x <= viewport_max.x &&
584 max.y >= viewport_min.y && min.y <= viewport_max.y;
585 if (visible) {
586 canvas_rt.draw_list->AddRectFilled(min, max, fill_color, 2.0f);
587 canvas_rt.draw_list->AddRect(min, max, border_color, 2.0f, 0,
588 2.0f + 2.0f * remaining);
589 continue;
590 }
591
592 const ImVec2 center(
593 canvas_rt.canvas_p0.x + canvas_rt.scrolling.x +
594 (static_cast<float>(rect.x) + static_cast<float>(rect.w) * 0.5f) *
595 canvas_rt.scale,
596 canvas_rt.canvas_p0.y + canvas_rt.scrolling.y +
597 (static_cast<float>(rect.y) + static_cast<float>(rect.h) * 0.5f) *
598 canvas_rt.scale);
599 const float marker_min_x = viewport_min.x + 8.0f;
600 const float marker_min_y = viewport_min.y + 8.0f;
601 const float marker_max_x = std::max(marker_min_x, viewport_max.x - 8.0f);
602 const float marker_max_y = std::max(marker_min_y, viewport_max.y - 8.0f);
603 const ImVec2 clamped(std::clamp(center.x, marker_min_x, marker_max_x),
604 std::clamp(center.y, marker_min_y, marker_max_y));
605 canvas_rt.draw_list->AddCircleFilled(clamped, 5.0f + 4.0f * remaining,
606 fill_color);
607 canvas_rt.draw_list->AddCircle(clamped, 7.0f + 6.0f * remaining,
608 border_color, 18, 2.0f);
609 }
610}
611
612} // 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:49
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_
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)
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 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:102
std::vector< uint16_t > track_stop_tiles
Definition project.h:97
std::vector< uint16_t > track_tiles
Definition project.h:96
std::vector< uint16_t > track_switch_tiles
Definition project.h:98
Modern project structure with comprehensive settings consolidation.
Definition project.h:287
DungeonOverlaySettings dungeon_overlay
Definition project.h:347