yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_canvas_overlays.cc
Go to the documentation of this file.
1#include <algorithm>
2#include <array>
3#include <cstdint>
4#include <span>
5#include <string>
6#include <string_view>
7#include <vector>
8#include "util/i18n/tr.h"
9
10#include "absl/strings/str_format.h"
17#include "core/project.h"
19#include "imgui/imgui.h"
28
29namespace yaze::editor {
30
31namespace {
32
33bool ShowEntityLabel(const gui::CanvasRuntime& rt, int x, int y, int width,
34 int height, bool selected) {
35 if (selected)
36 return true;
37 if (!rt.hovered || !ImGui::IsWindowHovered())
38 return false;
39 const ImVec2 min(rt.canvas_p0.x + rt.scrolling.x + x * rt.scale,
40 rt.canvas_p0.y + rt.scrolling.y + y * rt.scale);
41 return ImGui::IsMouseHoveringRect(
42 min, ImVec2(min.x + width * rt.scale, min.y + height * rt.scale));
43}
44
46 const std::vector<uint8_t>& preview,
47 const SDL_Rect& bounds, int room_x, int room_y,
48 const std::array<SDL_Color, 256>& color_table) {
49 if (!rt.draw_list || bounds.w <= 0 || bounds.h <= 0) {
50 return false;
51 }
52 const size_t row_stride = static_cast<size_t>(bounds.w);
53 if (static_cast<size_t>(bounds.h) > preview.size() / row_stride) {
54 return false;
55 }
56
57 const float scale = std::max(rt.scale, 0.01f);
58 const ImVec2 origin(rt.canvas_p0.x + rt.scrolling.x,
59 rt.canvas_p0.y + rt.scrolling.y);
60 const ImVec2 canvas_max(rt.canvas_p0.x + rt.canvas_sz.x,
61 rt.canvas_p0.y + rt.canvas_sz.y);
62 bool drew_any_pixel = false;
63
64 rt.draw_list->PushClipRect(rt.canvas_p0, canvas_max, true);
65 for (int y = 0; y < bounds.h; ++y) {
66 int x = 0;
67 while (x < bounds.w) {
68 const uint8_t palette_index =
69 preview[static_cast<size_t>(y) * row_stride + x];
70 const SDL_Color& palette_color = color_table[palette_index];
71 // RenderPreviewGraphics reserves index 0 for transparency. Unlike the
72 // former 0xFF sentinel, index 0 cannot collide with a visible dungeon
73 // sprite pixel: the preview encoder emits CGRAM indices 0x71..0xFF.
74 if (palette_index == 0 || palette_color.a == 0) {
75 ++x;
76 continue;
77 }
78 const ImU32 color = IM_COL32(palette_color.r, palette_color.g,
79 palette_color.b, palette_color.a);
80
81 const int start_x = x;
82 while (x < bounds.w && preview[static_cast<size_t>(y) * row_stride + x] ==
83 palette_index) {
84 ++x;
85 }
86
87 const ImVec2 min(origin.x + (room_x + start_x) * scale,
88 origin.y + (room_y + y) * scale);
89 const ImVec2 max(origin.x + (room_x + x) * scale,
90 origin.y + (room_y + y + 1) * scale);
91 rt.draw_list->AddRectFilled(min, max, color);
92 drew_any_pixel = true;
93 }
94 }
95 rt.draw_list->PopClipRect();
96
97 return drew_any_pixel;
98}
99
100const char* GetStoredPlacementLabel(const zelda3::RoomObject& object) {
101 const int layer_value = object.GetLayerValue();
102 if (!zelda3::UsesRoomObjectStream(object)) {
103 if (layer_value == 0) {
104 return "Upper BG1";
105 }
106 return layer_value == 1 ? "Lower BG2" : "Invalid selector";
107 }
108 switch (layer_value) {
109 case 0:
110 return "Primary";
111 case 1:
112 return "BG2 overlay";
113 case 2:
114 return "BG1 overlay";
115 default:
116 return "Unknown";
117 }
118}
119
120} // namespace
121
123 const zelda3::Room& room) {
125 return;
126 }
127
128 const auto& theme = AgentUI::GetTheme();
129 const bool is_touch = gui::LayoutHelpers::IsTouchDevice();
130 const int entity_size = is_touch ? 24 : 16;
131 const auto sprite_colors =
133 const std::string_view hack_name =
134 project_ != nullptr && project_->hack_manifest.loaded()
135 ? std::string_view(project_->hack_manifest.hack_name())
136 : std::string_view{};
138 project_ ? project_->filepath : std::string{},
140 : std::string{},
141 hack_name)) {
142 sprite_preview_cache_.Clear();
143 }
144 const auto& room_gfx = room.get_gfx_buffer();
145 const std::span<const uint8_t> room_gfx_span(room_gfx.data(),
146 room_gfx.size());
147
148 const auto& selected =
150 const auto& sprites = room.GetSprites();
151 for (size_t index = 0; index < sprites.size(); ++index) {
152 const auto& sprite = sprites[index];
153 const int canvas_x = sprite.x() * 16;
154 const int canvas_y = sprite.y() * 16;
155
156 if (canvas_x >= -entity_size && canvas_y >= -entity_size &&
157 canvas_x < 512 + entity_size && canvas_y < 512 + entity_size) {
158 ImVec4 sprite_color = sprite.layer() == 0 ? theme.dungeon_sprite_layer0
159 : theme.dungeon_sprite_layer1;
160
161 const auto* preview_layout =
162 zelda3::SpriteOamRegistry::GetPreviewOverride(sprite.id(), hack_name);
163 const SpritePreviewKey preview_key{room.graphics_revision(), sprite.id(),
164 sprite.subtype(), sprite.IsOverlord()};
165 // Revision zero means the room has not assembled its graphics yet and
166 // is not a reusable graphics identity.
167 auto* preview = room.graphics_revision() != 0
168 ? sprite_preview_cache_.Get(preview_key)
169 : nullptr;
170 CachedSpritePreview uncached_preview;
171 if (preview == nullptr) {
172 zelda3::Sprite preview_sprite = sprite;
173 preview_sprite.RenderPreviewGraphics(
174 room_gfx_span, preview_layout,
175 sprite_preview_resources_.GetGraphics(preview_layout));
176 CachedSpritePreview rendered{*preview_sprite.preview_graphics(),
177 preview_sprite.preview_bounds()};
178 if (room.graphics_revision() != 0) {
179 preview =
180 sprite_preview_cache_.Insert(preview_key, std::move(rendered));
181 } else {
182 uncached_preview = std::move(rendered);
183 preview = &uncached_preview;
184 }
185 }
186 const SDL_Rect preview_bounds = preview->bounds;
187 const bool drew_preview = DrawSpritePreviewPixels(
188 rt, preview->pixels, preview_bounds, canvas_x + preview_bounds.x,
189 canvas_y + preview_bounds.y, sprite_colors);
190
191 const bool show_label = ShowEntityLabel(
192 rt, canvas_x, canvas_y, entity_size, entity_size,
193 std::any_of(selected.begin(), selected.end(),
194 [index](const auto& entity) {
195 return entity.type == EntityType::Sprite &&
196 entity.index == index;
197 }));
198 if (drew_preview && show_label) {
199 gui::DrawOutline(rt, canvas_x, canvas_y, entity_size, entity_size,
200 ImGui::GetColorU32(sprite_color));
201 } else if (!drew_preview) {
202 gui::DrawRect(rt, canvas_x, canvas_y, entity_size, entity_size,
203 sprite_color);
204 }
205
206 std::string full_name = preview_layout != nullptr
207 ? preview_layout->name
208 : zelda3::GetSpriteLabel(sprite.id());
209 std::string sprite_text;
210 if (full_name.length() > 12) {
211 sprite_text = absl::StrFormat("%02X %s..", sprite.id(),
212 full_name.substr(0, 8).c_str());
213 } else {
214 sprite_text =
215 absl::StrFormat("%02X %s", sprite.id(), full_name.c_str());
216 }
217
218 if (show_label)
219 gui::DrawText(rt, sprite_text, canvas_x, canvas_y);
220 }
221 }
222}
223
225 const zelda3::Room& room) {
227 return;
228 }
229
230 const auto& pot_items = room.GetPotItems();
231 if (pot_items.empty()) {
232 return;
233 }
234
235 static const char* kPotItemNames[] = {
236 "Nothing", "Green Rupee", "Rock", "Bee", "Health",
237 "Bomb", "Heart", "Blue Rupee", "Key", "Arrow",
238 "Bomb", "Heart", "Magic", "Full Magic", "Cucco",
239 "Green Soldier", "Bush Stal", "Blue Soldier", "Landmine", "Heart",
240 "Fairy", "Heart", "Nothing", "Hole", "Warp",
241 "Staircase", "Bombable", "Switch"};
242 constexpr size_t kPotItemNameCount =
243 sizeof(kPotItemNames) / sizeof(kPotItemNames[0]);
244
245 const auto& selected =
247 for (size_t index = 0; index < pot_items.size(); ++index) {
248 const auto& pot_item = pot_items[index];
249 const int pixel_x = pot_item.GetPixelX();
250 const int pixel_y = pot_item.GetPixelY();
251 auto [canvas_x, canvas_y] =
253 pixel_y / 8);
254
255 const bool is_touch = gui::LayoutHelpers::IsTouchDevice();
256 const int entity_size = is_touch ? 24 : 16;
257
258 if (canvas_x >= -entity_size && canvas_y >= -entity_size &&
259 canvas_x < 512 + entity_size && canvas_y < 512 + entity_size) {
260 const auto& theme = AgentUI::GetTheme();
261 ImVec4 pot_item_color;
262 if (pot_item.item == 0) {
263 pot_item_color = theme.status_inactive;
264 pot_item_color.w = 0.4f;
265 } else {
266 pot_item_color = theme.item_color;
267 pot_item_color.w = 0.75f;
268 }
269
270 gui::DrawRect(rt, canvas_x, canvas_y, entity_size, entity_size,
271 pot_item_color);
272
273 std::string item_name;
274 if (pot_item.item < kPotItemNameCount) {
275 item_name = kPotItemNames[pot_item.item];
276 } else {
277 item_name = absl::StrFormat("Unk%02X", pot_item.item);
278 }
279
280 std::string item_text =
281 absl::StrFormat("%02X %s", pot_item.item, item_name.c_str());
282 if (ShowEntityLabel(rt, canvas_x, canvas_y, entity_size, entity_size,
283 std::any_of(selected.begin(), selected.end(),
284 [index](const auto& entity) {
285 return entity.type ==
286 EntityType::Item &&
287 entity.index == index;
288 }))) {
289 gui::DrawText(rt, item_text, canvas_x, canvas_y - 10);
290 }
291 }
292 }
293}
294
296 const zelda3::Room& room) {
298 !room.AreChestsLoaded() || rt.canvas_sz.x <= 16.0f) {
299 return;
300 }
301 const auto& objects = room.GetTileObjects();
302 const auto& chests = room.GetChests();
303 // Residual records and mismatched small/big objects must not be presented as
304 // a confident reward association. The chest inspector explains the mismatch.
305 if (chests.empty() ||
306 !zelda3::ValidateChestObjectMapping(objects, chests).ok()) {
307 return;
308 }
309
310 const DungeonCanvasTransform transform(rt.canvas_p0, rt.scrolling, rt.scale);
311 const ImVec2 canvas_max(rt.canvas_p0.x + rt.canvas_sz.x,
312 rt.canvas_p0.y + rt.canvas_sz.y);
313 const auto& theme = AgentUI::GetTheme();
314 const float padding = 4.0f;
315 const float wrap_width = std::min(180.0f, rt.canvas_sz.x - padding * 4);
316 rt.draw_list->PushClipRect(rt.canvas_p0, canvas_max, true);
317 for (size_t i = 0; i < objects.size(); ++i) {
318 const auto chest_index = zelda3::ChestIndexForObject(objects, i);
319 if (!chest_index) {
320 continue;
321 }
322 const auto& object = objects[i];
323 const float chest_width = object.id_ == 0xFB1 ? 32.0f : 16.0f;
324 const ImVec2 chest_min = transform.RoomPixelsToScreen(
325 ImVec2(object.x_ * 8.0f, object.y_ * 8.0f));
326 const ImVec2 chest_max = transform.RoomPixelsToScreen(
327 ImVec2(object.x_ * 8.0f + chest_width,
328 object.y_ * 8.0f + (object.id_ == 0xFB1 ? 24.0f : 16.0f)));
329 if (chest_max.x < rt.canvas_p0.x || chest_min.x > canvas_max.x ||
330 chest_max.y < rt.canvas_p0.y || chest_min.y > canvas_max.y) {
331 continue;
332 }
333 const auto item_id = chests[*chest_index].id;
334 const std::string text = absl::StrFormat(
335 "$%02X %s", item_id,
337 item_id, project_ ? &project_->resource_labels : nullptr));
338 const ImVec2 text_size =
339 ImGui::CalcTextSize(text.c_str(), nullptr, false, wrap_width);
340 const float width = text_size.x + padding * 2;
341 const float height = text_size.y + padding * 2;
342 const ImVec2 min(
343 std::clamp((chest_min.x + chest_max.x - width) * 0.5f, rt.canvas_p0.x,
344 std::max(rt.canvas_p0.x, canvas_max.x - width)),
345 std::max(rt.canvas_p0.y, chest_min.y - height - 3.0f));
346 const ImVec2 max(min.x + width, min.y + height);
347 rt.draw_list->AddRectFilled(min, max, IM_COL32(0, 0, 0, 200), 3.0f);
348 rt.draw_list->AddRect(min, max, ImGui::GetColorU32(theme.item_color), 3.0f);
349 rt.draw_list->AddText(ImGui::GetFont(), ImGui::GetFontSize(),
350 ImVec2(min.x + padding, min.y + padding),
351 ImGui::GetColorU32(ImGuiCol_Text), text.c_str(),
352 nullptr, wrap_width);
353 // Draw-list annotations have no ImGui item and cannot take a placement,
354 // selection, drag, or pan gesture away from the room canvas.
355 }
356 rt.draw_list->PopClipRect();
357}
358
360 const zelda3::Room& room) {
361 RenderSprites(rt, room);
362 RenderPotItems(rt, room);
363 RenderChestContents(rt, room);
364}
365
367 const gui::CanvasRuntime& rt, const zelda3::Room& room) {
368 constexpr const char* kPopupId = "##TouchEntityContextMenu";
369 const ImGuiIO& io = ImGui::GetIO();
370 const bool touch_context_click =
371 rt.hovered && io.MouseSource == ImGuiMouseSource_TouchScreen &&
372 ImGui::IsMouseClicked(ImGuiMouseButton_Right);
373 const bool gesture_long_press = touch_handler_.WasLongPressed();
374 const bool should_open_context = gesture_long_press || touch_context_click;
375
376 if (should_open_context) {
377 const ImVec2 gesture_pos = gesture_long_press
379 : ImGui::GetMousePos();
380 const DungeonCanvasTransform transform(rt.canvas_p0, rt.scrolling,
381 rt.scale);
382 const ImVec2 room_pos = transform.ScreenToRoomPixels(gesture_pos);
383 const auto [room_x, room_y] =
384 transform.ScreenToRoomPixelCoordinates(gesture_pos);
385 if (!dungeon_coords::IsWithinBounds(room_x, room_y)) {
386 return;
387 }
388 const float rel_x = room_pos.x;
389 const float rel_y = room_pos.y;
390 const bool is_touch = gui::LayoutHelpers::IsTouchDevice();
391 const int hit_size = is_touch ? 24 : 16;
392
393 const auto& sprites = room.GetSprites();
394 for (size_t idx = 0; idx < sprites.size(); ++idx) {
395 const int sprite_px = sprites[idx].x() * 16;
396 const int sprite_py = sprites[idx].y() * 16;
397 if (rel_x >= sprite_px && rel_x < sprite_px + hit_size &&
398 rel_y >= sprite_py && rel_y < sprite_py + hit_size) {
399 object_interaction_.SelectEntity(EntityType::Sprite, idx);
400 ImGui::OpenPopup(kPopupId);
401 break;
402 }
403 }
404
405 if (!ImGui::IsPopupOpen(kPopupId)) {
406 const auto& pot_items = room.GetPotItems();
407 for (size_t idx = 0; idx < pot_items.size(); ++idx) {
408 const int item_px = pot_items[idx].GetPixelX();
409 const int item_py = pot_items[idx].GetPixelY();
410 if (rel_x >= item_px && rel_x < item_px + hit_size &&
411 rel_y >= item_py && rel_y < item_py + hit_size) {
412 object_interaction_.SelectEntity(EntityType::Item, idx);
413 ImGui::OpenPopup(kPopupId);
414 break;
415 }
416 }
417 }
418
419 if (!ImGui::IsPopupOpen(kPopupId)) {
420 const auto object_index =
422 .tile_handler()
423 .GetEntityAtPosition(static_cast<int>(rel_x),
424 static_cast<int>(rel_y));
425 if (object_index.has_value()) {
427 ImGui::OpenPopup(kPopupId);
428 }
429 }
430 }
431
432 if (ImGui::BeginPopup(kPopupId)) {
435 if (sel.type == EntityType::Sprite) {
436 const auto& sprites = room.GetSprites();
437 if (sel.index < sprites.size()) {
438 std::string label = zelda3::GetSpriteLabel(sprites[sel.index].id());
439 ImGui::TextDisabled(tr("Sprite: %02X %s"), sprites[sel.index].id(),
440 label.c_str());
441 ImGui::Separator();
442 }
443 if (ImGui::MenuItem(tr("Delete Sprite"))) {
445 }
446 } else if (sel.type == EntityType::Item) {
447 ImGui::TextDisabled(tr("Pot Item"));
448 ImGui::Separator();
449 if (ImGui::MenuItem(tr("Delete Item"))) {
451 }
452 }
453 } else if (object_interaction_.GetSelectionCount() > 0) {
454 const auto indices = object_interaction_.GetSelectedObjectIndices();
455 if (indices.size() == 1) {
456 const auto& objects = room.GetTileObjects();
457 if (indices[0] < objects.size()) {
458 std::string name = zelda3::GetObjectName(objects[indices[0]].id_);
459 ImGui::TextDisabled(tr("Object: %03X %s"), objects[indices[0]].id_,
460 name.c_str());
461 ImGui::Separator();
462 }
463 } else {
464 ImGui::TextDisabled(tr("%zu objects selected"),
466 ImGui::Separator();
467 }
468 if (ImGui::MenuItem(tr("Delete"))) {
470 }
471 if (ImGui::MenuItem(tr("Copy"))) {
473 }
474 ImGui::Separator();
475 if (ImGui::MenuItem(tr("Send to Front"))) {
477 }
478 if (ImGui::MenuItem(tr("Send to Back"))) {
480 }
481 }
482 ImGui::EndPopup();
483 }
484}
485
487 const gui::CanvasRuntime& rt, const zelda3::Room& room) {
488 const auto& theme = AgentUI::GetTheme();
489 const auto& objects = room.GetTileObjects();
490
491 for (const auto& obj : objects) {
492 bool show_this_type = true;
493 if (obj.id_ < 0x100) {
495 } else if (obj.id_ >= 0x100 && obj.id_ < 0x200) {
497 } else if (obj.id_ >= 0xF00) {
499 }
500
501 bool show_this_layer = true;
502 if (obj.GetLayerValue() == 0) {
504 } else if (obj.GetLayerValue() == 1) {
506 } else if (obj.GetLayerValue() == 2) {
508 }
509
510 if (!show_this_type || !show_this_layer) {
511 continue;
512 }
513
514 auto [canvas_x, canvas_y, width, height] =
516 width = std::min(width, 512);
517 height = std::min(height, 512);
518
519 ImVec4 outline_color;
520 if (obj.GetLayerValue() == 0) {
521 outline_color = theme.dungeon_outline_layer0;
522 } else if (obj.GetLayerValue() == 1) {
523 outline_color = theme.dungeon_outline_layer1;
524 } else {
525 outline_color = theme.dungeon_outline_layer2;
526 }
527
528 gui::DrawRect(rt, canvas_x, canvas_y, width, height, outline_color);
529
530 std::string name = zelda3::GetObjectName(obj.id_);
531 if (name.length() > 12) {
532 name = name.substr(0, 10) + "..";
533 }
534 std::string label;
535 if (obj.id_ >= 0x100) {
536 label = absl::StrFormat("0x%03X\n%s\n%s [%dx%d]", obj.id_, name.c_str(),
537 GetStoredPlacementLabel(obj), width, height);
538 } else {
539 label = absl::StrFormat("0x%02X\n%s\n%s [%dx%d]", obj.id_, name.c_str(),
540 GetStoredPlacementLabel(obj), width, height);
541 }
542 gui::DrawText(rt, label, canvas_x + 1, canvas_y + 1);
543 }
544}
545
548 const auto* working = rooms_ ? rooms_->GetIfLoaded(room_id) : nullptr;
549 auto it = collision_overlay_cache_.find(room_id);
550 if (!working && it != collision_overlay_cache_.end()) {
551 return it->second;
552 }
553
555 cache.entries.clear();
556
557 if (!rom_ || !rom_->is_loaded()) {
558 collision_overlay_cache_.emplace(room_id, cache);
559 return collision_overlay_cache_.at(room_id);
560 }
561
562 // A loaded room is the authoring source of truth. Rebuild its small overlay
563 // from the model so edits, Undo and save-dirty clearing cannot leave ROM-era
564 // pixels cached. Unloaded rooms still use the ROM-backed cache.
565 auto map_or = working ? absl::StatusOr<zelda3::CustomCollisionMap>(
566 working->custom_collision())
568 if (!map_or.ok()) {
569 collision_overlay_cache_.emplace(room_id, cache);
570 return collision_overlay_cache_.at(room_id);
571 }
572
573 const auto& map = map_or.value();
574 cache.has_data = map.has_data;
575 if (cache.has_data && !track_collision_config_.IsEmpty()) {
576 for (int y = 0; y < 64; ++y) {
577 for (int x = 0; x < 64; ++x) {
578 const uint8_t tile = map.tiles[static_cast<size_t>(y * 64 + x)];
579 if (map.HasOverride(static_cast<size_t>(y * 64 + x)) &&
583 cache.entries.push_back(
585 static_cast<uint8_t>(x), static_cast<uint8_t>(y), tile});
586 }
587 }
588 }
589 }
590
591 collision_overlay_cache_.insert_or_assign(room_id, std::move(cache));
592 return collision_overlay_cache_.at(room_id);
593}
594
596 gfx::Bitmap* composite =
598 zelda3::Room* room = rooms_ ? rooms_->GetIfMaterialized(room_id) : nullptr;
599 if (composite != nullptr && room != nullptr) {
601 composite, room->rendered_dungeon_palette(), room->rendered_palette());
602 }
603 return composite;
604}
605
607 int room_id) {
608 // Resolve Auto for each neighbor without changing the primary room's context.
609 // Manual mode returns the same pinned entrance for every room.
610 auto* room = EnsureRoomLoadedForConnectedView(room_id);
611 if (!room) {
612 return nullptr;
613 }
614 room->SetRenderEntranceBlockset(entrance_blockset_resolver_
617 auto& entry = connected_composite_outputs_[room_id];
618 if (!entry.output) {
619 entry.output = std::make_unique<RoomCompositeOutput>();
620 }
621 entry.last_used_frame =
622 ImGui::GetCurrentContext() != nullptr ? ImGui::GetFrameCount() : 0;
623 return PrepareRoomCompositeWithOutput(room_id, *entry.output);
624}
625
627 int room_id, RoomCompositeOutput& output) {
628 if (room_id < 0 || room_id >= zelda3::kNumberOfRooms || !rooms_ || !rom_ ||
629 !rom_->is_loaded()) {
630 return nullptr;
631 }
632
633 auto* room_ptr = rooms_->TryEnsureRoom(room_id);
634 if (!room_ptr) {
635 return nullptr;
636 }
637 auto& room = *room_ptr;
638 auto& layer_mgr = GetRoomLayerManager(room_id);
639 auto& composite = output.Prepare(room, layer_mgr);
640 if (!composite.is_active() || composite.width() <= 0) {
641 return nullptr;
642 }
643
644 // Closes the A3 first-frame race for the dungeon room composite, mirroring
645 // the title-screen path (A4). The helper queues CREATE on first sight,
646 // queues UPDATE when RoomLayerManager::CompositeToOutput marked the bitmap
647 // modified, and stamps metadata().purpose so canvas_rendering's diagnostic
648 // log can identify the bitmap.
650 return &composite;
651}
652
654 if (room_id < 0 || room_id >= zelda3::kNumberOfRooms || !rooms_) {
655 return false;
656 }
657
658 const float scale = canvas_.global_scale();
659 gfx::Bitmap* composite = PrepareRoomCompositeBitmap(room_id);
660 if (!composite) {
661 return false;
662 }
663
664 // Single-room canvas rendering prepares the composite after the normal room
665 // state upload pass. Flush here too so workbench mode can draw the freshly
666 // queued room texture in the same frame.
668
669 if (composite->texture()) {
670 canvas_.DrawBitmap(*composite, 0, 0, scale, 255);
671 return true;
672 }
673
674 return false;
675}
676
678 const zelda3::Room& room) {
679 const auto& objects = room.GetTileObjects();
680 const ImVec4 mask_color(0.2f, 0.6f, 1.0f, 0.4f);
681
682 for (const auto& obj : objects) {
683 if (obj.GetLayerValue() != 1) {
684 continue;
685 }
686
687 auto [canvas_x, canvas_y] =
689 auto [width, height] =
691 width = std::min(width, 512);
692 height = std::min(height, 512);
693 gui::DrawRect(rt, canvas_x, canvas_y, width, height, mask_color);
694 }
695}
696
697} // namespace yaze::editor
bool is_loaded() const
Definition rom.h:187
const std::string & hack_name() const
bool loaded() const
Check if the manifest has been loaded.
std::pair< int, int > ScreenToRoomPixelCoordinates(ImVec2 screen) const
ImVec2 ScreenToRoomPixels(ImVec2 screen) const
gfx::Bitmap * PrepareConnectedRoomCompositeBitmap(int room_id)
void RenderPotItems(const gui::CanvasRuntime &rt, const zelda3::Room &room)
gfx::Bitmap * PrepareRoomCompositeWithOutput(int room_id, RoomCompositeOutput &output)
zelda3::SpritePreviewResourceCache sprite_preview_resources_
const DungeonRenderingHelpers::CollisionOverlayCache & GetCollisionOverlayCache(int room_id)
std::map< int, ConnectedRoomCompositeEntry > connected_composite_outputs_
zelda3::Room * EnsureRoomLoadedForConnectedView(int room_id)
DungeonObjectInteraction object_interaction_
const project::YazeProject * project_
std::function< uint8_t(int)> entrance_blockset_resolver_
std::unordered_map< int, DungeonRenderingHelpers::CollisionOverlayCache > collision_overlay_cache_
void RenderSprites(const gui::CanvasRuntime &rt, const zelda3::Room &room)
gfx::Bitmap * PrepareRoomCompositeBitmap(int room_id)
zelda3::RoomLayerManager & GetRoomLayerManager(int room_id)
void DrawMaskHighlights(const gui::CanvasRuntime &rt, const zelda3::Room &room)
util::LruCache< SpritePreviewKey, CachedSpritePreview > sprite_preview_cache_
void RenderChestContents(const gui::CanvasRuntime &rt, const zelda3::Room &room)
void RenderEntityOverlay(const gui::CanvasRuntime &rt, const zelda3::Room &room)
std::tuple< uint64_t, uint8_t, int, bool > SpritePreviewKey
DungeonRenderingHelpers::TrackCollisionConfig track_collision_config_
void DrawObjectPositionOutlines(const gui::CanvasRuntime &rt, const zelda3::Room &room)
void HandleTouchLongPressContextMenu(const gui::CanvasRuntime &rt, const zelda3::Room &room)
void SetSelectedObjects(const std::vector< size_t > &indices)
std::vector< size_t > GetSelectedObjectIndices() const
InteractionCoordinator & entity_coordinator()
Get the interaction coordinator for entity handling.
void SelectEntity(EntityType type, size_t index)
static std::pair< int, int > RoomToCanvasCoordinates(int room_x, int room_y)
zelda3::Room * GetIfMaterialized(int room_id)
zelda3::Room * GetIfLoaded(int room_id)
zelda3::Room * TryEnsureRoom(int room_id)
const std::vector< SelectedEntity > & GetSelectedEntities() const
gfx::Bitmap & Prepare(zelda3::Room &room, const zelda3::RoomLayerManager &layer_manager)
std::optional< size_t > GetEntityAtPosition(int canvas_x, int canvas_y) const override
Get entity at canvas position.
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:211
static Arena & Get()
Definition arena.cc:24
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:70
TextureHandle texture() const
Definition bitmap.h:410
bool WasLongPressed() const
Check if a long-press gesture occurred this frame.
ImVec2 GetGesturePosition() const
Get the position of the last tap/gesture.
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1293
auto global_scale() const
Definition canvas.h:413
static DimensionService & Get()
std::tuple< int, int, int, int > GetSelectionBoundsPixels(const RoomObject &obj) const
std::pair< int, int > GetPixelDimensions(const RoomObject &obj) const
static PaletteDebugger & Get()
void SetCurrentPresentation(gfx::Bitmap *bitmap, const gfx::SnesPalette &palette, const std::vector< SDL_Color > &render_palette)
const std::array< uint8_t, 0x10000 > & get_gfx_buffer() const
Definition room.h:1120
const std::vector< chest_data > & GetChests() const
Definition room.h:323
const std::vector< SDL_Color > & rendered_palette() const
Definition room.h:1138
const gfx::SnesPalette & rendered_dungeon_palette() const
Definition room.h:1132
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:316
uint64_t graphics_revision() const
Definition room.h:1125
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:445
const std::vector< PotItem > & GetPotItems() const
Definition room.h:429
bool AreChestsLoaded() const
Definition room.h:974
static const SpriteOamLayout * GetPreviewOverride(uint8_t sprite_id, std::string_view hack_name)
std::span< const uint8_t > GetGraphics(const SpriteOamLayout *layout)
bool SetContext(std::string_view project_path, std::string_view assets_path, std::string_view hack_name)
A class for managing sprites in the overworld and underworld.
Definition sprite.h:39
void RenderPreviewGraphics(std::span< const uint8_t > graphics, const SpriteOamLayout *layout_override=nullptr, std::span< const uint8_t > graphics_resource={})
Definition sprite.cc:1194
auto preview_graphics() const
Definition sprite.h:103
SDL_Rect preview_bounds() const
Definition sprite.h:106
const AgentUITheme & GetTheme()
bool DrawSpritePreviewPixels(const gui::CanvasRuntime &rt, const std::vector< uint8_t > &preview, const SDL_Rect &bounds, int room_x, int room_y, const std::array< SDL_Color, 256 > &color_table)
bool ShowEntityLabel(const gui::CanvasRuntime &rt, int x, int y, int width, int height, bool selected)
bool IsWithinBounds(int canvas_x, int canvas_y, int margin=0)
Check if coordinates are within room bounds.
Editors are the view controllers for the application.
void EnsureCompositeBitmapTextureQueued(Bitmap &composite)
void DrawRect(const CanvasRuntime &rt, int x, int y, int w, int h, ImVec4 color)
void DrawOutline(const CanvasRuntime &rt, int x, int y, int w, int h, ImU32 color=IM_COL32(255, 255, 255, 200))
void DrawText(const CanvasRuntime &rt, const std::string &text, int x, int y)
absl::Status ValidateChestObjectMapping(const std::vector< RoomObject > &objects, const std::vector< chest_data > &chests)
Definition chest_edit.cc:74
std::string GetSpriteLabel(int id)
Convenience function to get a sprite label.
std::optional< size_t > ChestIndexForObject(const std::vector< RoomObject > &objects, size_t object_index)
Definition chest_edit.cc:63
std::string GetItemReceiptLabel(uint8_t id, const ResourceLabelProvider::ProjectLabels *project_labels)
absl::StatusOr< CustomCollisionMap > LoadCustomCollisionMap(Rom *rom, int room_id)
bool UsesRoomObjectStream(const RoomObject &object)
std::string GetObjectName(int object_id)
constexpr int kNumberOfRooms
std::array< SDL_Color, 256 > BuildDungeonSpriteRenderPalette(const Room &room, const GameData *game_data)
Definition room.cc:200
core::HackManifest hack_manifest
Definition project.h:360
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
Definition project.h:353
std::string assets_folder
Definition project.h:312
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:2028