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 <vector>
7#include "util/i18n/tr.h"
8
9#include "absl/strings/str_format.h"
17#include "imgui/imgui.h"
23
24namespace yaze::editor {
25
26namespace {
27
28constexpr int kSpritePreviewSize = 64;
29constexpr int kSpritePreviewAnchor = 16;
30constexpr int kSpritePaletteRowsStart = 8;
31constexpr int kPaletteRowSize = 16;
32
34 std::array<ImU32, 256> colors{};
35 bool has_sprite_colors = false;
36};
37
38ImU32 ToImU32(const gfx::SnesColor& color) {
39 const auto rgb = color.rom_color();
40 return IM_COL32(static_cast<int>(rgb.red), static_cast<int>(rgb.green),
41 static_cast<int>(rgb.blue), 255);
42}
43
44void CopyPaletteRange(std::array<ImU32, 256>& colors, bool& has_colors, int row,
45 int start_column, const gfx::SnesPalette& palette,
46 size_t max_colors) {
47 const size_t count = std::min(max_colors, palette.size());
48 for (size_t i = 0; i < count; ++i) {
49 const int column = start_column + static_cast<int>(i);
50 const int index = row * kPaletteRowSize + column;
51 if (index < 0 || index >= static_cast<int>(colors.size())) {
52 continue;
53 }
54 colors[static_cast<size_t>(index)] = ToImU32(palette[i]);
55 has_colors = true;
56 }
57}
58
60 const zelda3::GameData* game_data) {
62 if (!game_data) {
63 return table;
64 }
65
66 const auto& groups = game_data->palette_groups;
67
68 if (groups.sprites_aux1.size() > 1) {
71 groups.sprites_aux1.palette_ref(1), 7);
72 }
73 if (!groups.sprites_aux3.empty()) {
76 groups.sprites_aux3.palette_ref(0), 7);
77 }
78
79 if (!groups.global_sprites.empty()) {
80 const auto& global = groups.global_sprites.palette_ref(0);
81 size_t source_index = 0;
82 for (int row = 9; row <= 12; ++row) {
83 for (int column = 1; column < kPaletteRowSize; ++column) {
84 if (source_index >= global.size()) {
85 break;
86 }
87 table.colors[static_cast<size_t>(row * kPaletteRowSize + column)] =
88 ToImU32(global[source_index++]);
89 table.has_sprite_colors = true;
90 }
91 }
92 }
93
94 if (!groups.sprites_aux1.empty()) {
95 CopyPaletteRange(table.colors, table.has_sprite_colors, 13, 1,
96 groups.sprites_aux1.palette_ref(0), 7);
97 }
98 if (!groups.sprites_aux2.empty()) {
99 CopyPaletteRange(table.colors, table.has_sprite_colors, 14, 1,
100 groups.sprites_aux2.palette_ref(0), 7);
101 }
102 if (!groups.armors.empty()) {
103 CopyPaletteRange(table.colors, table.has_sprite_colors, 15, 1,
104 groups.armors.palette_ref(0), 15);
105 }
106
107 return table;
108}
109
111 const std::vector<uint8_t>& preview, int room_x,
112 int room_y,
113 const DungeonSpriteColorTable& color_table) {
114 if (!rt.draw_list ||
115 preview.size() < kSpritePreviewSize * kSpritePreviewSize ||
116 !color_table.has_sprite_colors) {
117 return false;
118 }
119
120 const float scale = std::max(rt.scale, 0.01f);
121 const ImVec2 origin(rt.canvas_p0.x + rt.scrolling.x,
122 rt.canvas_p0.y + rt.scrolling.y);
123 const ImVec2 canvas_max(rt.canvas_p0.x + rt.canvas_sz.x,
124 rt.canvas_p0.y + rt.canvas_sz.y);
125 bool drew_any_pixel = false;
126
127 rt.draw_list->PushClipRect(rt.canvas_p0, canvas_max, true);
128 for (int y = 0; y < kSpritePreviewSize; ++y) {
129 int x = 0;
130 while (x < kSpritePreviewSize) {
131 const uint8_t palette_index =
132 preview[static_cast<size_t>(y * kSpritePreviewSize + x)];
133 const ImU32 color = color_table.colors[palette_index];
134 if (palette_index == 0xFF || color == 0) {
135 ++x;
136 continue;
137 }
138
139 const int start_x = x;
140 while (x < kSpritePreviewSize &&
141 preview[static_cast<size_t>(y * kSpritePreviewSize + x)] ==
142 palette_index) {
143 ++x;
144 }
145
146 const ImVec2 min(origin.x + (room_x + start_x) * scale,
147 origin.y + (room_y + y) * scale);
148 const ImVec2 max(origin.x + (room_x + x) * scale,
149 origin.y + (room_y + y + 1) * scale);
150 rt.draw_list->AddRectFilled(min, max, color);
151 drew_any_pixel = true;
152 }
153 }
154 rt.draw_list->PopClipRect();
155
156 return drew_any_pixel;
157}
158
159const char* GetStoredPlacementLabel(const zelda3::RoomObject& object) {
160 const int layer_value = object.GetLayerValue();
161 if (!zelda3::UsesRoomObjectStream(object)) {
162 if (layer_value == 0) {
163 return "Upper BG1";
164 }
165 return layer_value == 1 ? "Lower BG2" : "Invalid selector";
166 }
167 switch (layer_value) {
168 case 0:
169 return "Primary";
170 case 1:
171 return "BG2 overlay";
172 case 2:
173 return "BG1 overlay";
174 default:
175 return "Unknown";
176 }
177}
178
179} // namespace
180
182 const zelda3::Room& room) {
184 return;
185 }
186
187 const auto& theme = AgentUI::GetTheme();
188 const bool is_touch = gui::LayoutHelpers::IsTouchDevice();
189 const int entity_size = is_touch ? 24 : 16;
190 const DungeonSpriteColorTable sprite_colors =
191 BuildDungeonSpriteColorTable(game_data_);
192 const auto& room_gfx = room.get_gfx_buffer();
193 const std::span<const uint8_t> room_gfx_span(room_gfx.data(),
194 room_gfx.size());
195
196 for (const auto& sprite : room.GetSprites()) {
197 const int canvas_x = sprite.x() * 16;
198 const int canvas_y = sprite.y() * 16;
199
200 if (canvas_x >= -entity_size && canvas_y >= -entity_size &&
201 canvas_x < 512 + entity_size && canvas_y < 512 + entity_size) {
202 ImVec4 sprite_color = sprite.layer() == 0 ? theme.dungeon_sprite_layer0
203 : theme.dungeon_sprite_layer1;
204
205 zelda3::Sprite preview_sprite = sprite;
206 preview_sprite.RenderPreviewGraphics(room_gfx_span);
207 const auto* preview = preview_sprite.preview_graphics();
208 const bool drew_preview =
209 preview && DrawSpritePreviewPixels(
210 rt, *preview, canvas_x - kSpritePreviewAnchor,
211 canvas_y - kSpritePreviewAnchor, sprite_colors);
212
213 if (drew_preview) {
214 gui::DrawOutline(rt, canvas_x, canvas_y, entity_size, entity_size,
215 ImGui::GetColorU32(sprite_color));
216 } else {
217 gui::DrawRect(rt, canvas_x, canvas_y, entity_size, entity_size,
218 sprite_color);
219 }
220
221 std::string full_name = zelda3::GetSpriteLabel(sprite.id());
222 std::string sprite_text;
223 if (full_name.length() > 12) {
224 sprite_text = absl::StrFormat("%02X %s..", sprite.id(),
225 full_name.substr(0, 8).c_str());
226 } else {
227 sprite_text =
228 absl::StrFormat("%02X %s", sprite.id(), full_name.c_str());
229 }
230
231 gui::DrawText(rt, sprite_text, canvas_x, canvas_y);
232 }
233 }
234}
235
237 const zelda3::Room& room) {
239 return;
240 }
241
242 const auto& pot_items = room.GetPotItems();
243 if (pot_items.empty()) {
244 return;
245 }
246
247 static const char* kPotItemNames[] = {
248 "Nothing", "Green Rupee", "Rock", "Bee", "Health",
249 "Bomb", "Heart", "Blue Rupee", "Key", "Arrow",
250 "Bomb", "Heart", "Magic", "Full Magic", "Cucco",
251 "Green Soldier", "Bush Stal", "Blue Soldier", "Landmine", "Heart",
252 "Fairy", "Heart", "Nothing", "Hole", "Warp",
253 "Staircase", "Bombable", "Switch"};
254 constexpr size_t kPotItemNameCount =
255 sizeof(kPotItemNames) / sizeof(kPotItemNames[0]);
256
257 for (const auto& pot_item : pot_items) {
258 const int pixel_x = pot_item.GetPixelX();
259 const int pixel_y = pot_item.GetPixelY();
260 auto [canvas_x, canvas_y] =
262 pixel_y / 8);
263
264 const bool is_touch = gui::LayoutHelpers::IsTouchDevice();
265 const int entity_size = is_touch ? 24 : 16;
266
267 if (canvas_x >= -entity_size && canvas_y >= -entity_size &&
268 canvas_x < 512 + entity_size && canvas_y < 512 + entity_size) {
269 const auto& theme = AgentUI::GetTheme();
270 ImVec4 pot_item_color;
271 if (pot_item.item == 0) {
272 pot_item_color = theme.status_inactive;
273 pot_item_color.w = 0.4f;
274 } else {
275 pot_item_color = theme.item_color;
276 pot_item_color.w = 0.75f;
277 }
278
279 gui::DrawRect(rt, canvas_x, canvas_y, entity_size, entity_size,
280 pot_item_color);
281
282 std::string item_name;
283 if (pot_item.item < kPotItemNameCount) {
284 item_name = kPotItemNames[pot_item.item];
285 } else {
286 item_name = absl::StrFormat("Unk%02X", pot_item.item);
287 }
288
289 std::string item_text =
290 absl::StrFormat("%02X %s", pot_item.item, item_name.c_str());
291 gui::DrawText(rt, item_text, canvas_x, canvas_y - 10);
292 }
293 }
294}
295
297 const zelda3::Room& room) {
298 RenderSprites(rt, room);
299 RenderPotItems(rt, room);
300}
301
303 const gui::CanvasRuntime& rt, const zelda3::Room& room) {
304 constexpr const char* kPopupId = "##TouchEntityContextMenu";
305 const ImGuiIO& io = ImGui::GetIO();
306 const bool touch_context_click =
307 rt.hovered && io.MouseSource == ImGuiMouseSource_TouchScreen &&
308 ImGui::IsMouseClicked(ImGuiMouseButton_Right);
309 const bool gesture_long_press = touch_handler_.WasLongPressed();
310 const bool should_open_context = gesture_long_press || touch_context_click;
311
312 if (should_open_context) {
313 const ImVec2 gesture_pos = gesture_long_press
315 : ImGui::GetMousePos();
316 const DungeonCanvasTransform transform(rt.canvas_p0, rt.scrolling,
317 rt.scale);
318 const ImVec2 room_pos = transform.ScreenToRoomPixels(gesture_pos);
319 const auto [room_x, room_y] =
320 transform.ScreenToRoomPixelCoordinates(gesture_pos);
321 if (!dungeon_coords::IsWithinBounds(room_x, room_y)) {
322 return;
323 }
324 const float rel_x = room_pos.x;
325 const float rel_y = room_pos.y;
326 const bool is_touch = gui::LayoutHelpers::IsTouchDevice();
327 const int hit_size = is_touch ? 24 : 16;
328
329 const auto& sprites = room.GetSprites();
330 for (size_t idx = 0; idx < sprites.size(); ++idx) {
331 const int sprite_px = sprites[idx].x() * 16;
332 const int sprite_py = sprites[idx].y() * 16;
333 if (rel_x >= sprite_px && rel_x < sprite_px + hit_size &&
334 rel_y >= sprite_py && rel_y < sprite_py + hit_size) {
336 ImGui::OpenPopup(kPopupId);
337 break;
338 }
339 }
340
341 if (!ImGui::IsPopupOpen(kPopupId)) {
342 const auto& pot_items = room.GetPotItems();
343 for (size_t idx = 0; idx < pot_items.size(); ++idx) {
344 const int item_px = pot_items[idx].GetPixelX();
345 const int item_py = pot_items[idx].GetPixelY();
346 if (rel_x >= item_px && rel_x < item_px + hit_size &&
347 rel_y >= item_py && rel_y < item_py + hit_size) {
349 ImGui::OpenPopup(kPopupId);
350 break;
351 }
352 }
353 }
354
355 if (!ImGui::IsPopupOpen(kPopupId)) {
356 const auto& objects = room.GetTileObjects();
357 for (size_t idx = 0; idx < objects.size(); ++idx) {
358 const auto& obj = objects[idx];
359 const int obj_px = obj.x() * 8;
360 const int obj_py = obj.y() * 8;
361 auto [obj_w, obj_h] =
363 obj_w = std::max(obj_w, 8);
364 obj_h = std::max(obj_h, 8);
365 if (rel_x >= obj_px && rel_x < obj_px + obj_w && rel_y >= obj_py &&
366 rel_y < obj_py + obj_h) {
368 ImGui::OpenPopup(kPopupId);
369 break;
370 }
371 }
372 }
373 }
374
375 if (ImGui::BeginPopup(kPopupId)) {
378 if (sel.type == EntityType::Sprite) {
379 const auto& sprites = room.GetSprites();
380 if (sel.index < sprites.size()) {
381 std::string label = zelda3::GetSpriteLabel(sprites[sel.index].id());
382 ImGui::TextDisabled(tr("Sprite: %02X %s"), sprites[sel.index].id(),
383 label.c_str());
384 ImGui::Separator();
385 }
386 if (ImGui::MenuItem(tr("Delete Sprite"))) {
388 }
389 } else if (sel.type == EntityType::Item) {
390 ImGui::TextDisabled(tr("Pot Item"));
391 ImGui::Separator();
392 if (ImGui::MenuItem(tr("Delete Item"))) {
394 }
395 }
396 } else if (object_interaction_.GetSelectionCount() > 0) {
397 const auto indices = object_interaction_.GetSelectedObjectIndices();
398 if (indices.size() == 1) {
399 const auto& objects = room.GetTileObjects();
400 if (indices[0] < objects.size()) {
401 std::string name = zelda3::GetObjectName(objects[indices[0]].id_);
402 ImGui::TextDisabled(tr("Object: %03X %s"), objects[indices[0]].id_,
403 name.c_str());
404 ImGui::Separator();
405 }
406 } else {
407 ImGui::TextDisabled(tr("%zu objects selected"),
409 ImGui::Separator();
410 }
411 if (ImGui::MenuItem(tr("Delete"))) {
413 }
414 if (ImGui::MenuItem(tr("Copy"))) {
416 }
417 ImGui::Separator();
418 if (ImGui::MenuItem(tr("Send to Front"))) {
420 }
421 if (ImGui::MenuItem(tr("Send to Back"))) {
423 }
424 }
425 ImGui::EndPopup();
426 }
427}
428
430 const gui::CanvasRuntime& rt, const zelda3::Room& room) {
431 const auto& theme = AgentUI::GetTheme();
432 const auto& objects = room.GetTileObjects();
433
434 for (const auto& obj : objects) {
435 bool show_this_type = true;
436 if (obj.id_ < 0x100) {
438 } else if (obj.id_ >= 0x100 && obj.id_ < 0x200) {
440 } else if (obj.id_ >= 0xF00) {
442 }
443
444 bool show_this_layer = true;
445 if (obj.GetLayerValue() == 0) {
447 } else if (obj.GetLayerValue() == 1) {
449 } else if (obj.GetLayerValue() == 2) {
451 }
452
453 if (!show_this_type || !show_this_layer) {
454 continue;
455 }
456
457 auto [canvas_x, canvas_y, width, height] =
459 width = std::min(width, 512);
460 height = std::min(height, 512);
461
462 ImVec4 outline_color;
463 if (obj.GetLayerValue() == 0) {
464 outline_color = theme.dungeon_outline_layer0;
465 } else if (obj.GetLayerValue() == 1) {
466 outline_color = theme.dungeon_outline_layer1;
467 } else {
468 outline_color = theme.dungeon_outline_layer2;
469 }
470
471 gui::DrawRect(rt, canvas_x, canvas_y, width, height, outline_color);
472
473 std::string name = zelda3::GetObjectName(obj.id_);
474 if (name.length() > 12) {
475 name = name.substr(0, 10) + "..";
476 }
477 std::string label;
478 if (obj.id_ >= 0x100) {
479 label = absl::StrFormat("0x%03X\n%s\n%s [%dx%d]", obj.id_, name.c_str(),
480 GetStoredPlacementLabel(obj), width, height);
481 } else {
482 label = absl::StrFormat("0x%02X\n%s\n%s [%dx%d]", obj.id_, name.c_str(),
483 GetStoredPlacementLabel(obj), width, height);
484 }
485 gui::DrawText(rt, label, canvas_x + 1, canvas_y + 1);
486 }
487}
488
491 auto it = collision_overlay_cache_.find(room_id);
492 if (it != collision_overlay_cache_.end()) {
493 return it->second;
494 }
495
497 cache.entries.clear();
498
499 if (!rom_ || !rom_->is_loaded()) {
500 collision_overlay_cache_.emplace(room_id, cache);
501 return collision_overlay_cache_.at(room_id);
502 }
503
504 auto map_or = zelda3::LoadCustomCollisionMap(rom_, room_id);
505 if (!map_or.ok()) {
506 collision_overlay_cache_.emplace(room_id, cache);
507 return collision_overlay_cache_.at(room_id);
508 }
509
510 const auto& map = map_or.value();
511 cache.has_data = map.has_data;
512 if (cache.has_data && !track_collision_config_.IsEmpty()) {
513 for (int y = 0; y < 64; ++y) {
514 for (int x = 0; x < 64; ++x) {
515 const uint8_t tile = map.tiles[static_cast<size_t>(y * 64 + x)];
516 if (tile < 256 && (track_collision_config_.track_tiles[tile] ||
519 cache.entries.push_back(
521 static_cast<uint8_t>(x), static_cast<uint8_t>(y), tile});
522 }
523 }
524 }
525 }
526
527 collision_overlay_cache_.emplace(room_id, std::move(cache));
528 return collision_overlay_cache_.at(room_id);
529}
530
532 if (room_id < 0 || room_id >= zelda3::kNumberOfRooms || !rooms_ || !rom_ ||
533 !rom_->is_loaded()) {
534 return nullptr;
535 }
536
537 auto* room_ptr = rooms_->TryEnsureRoom(room_id);
538 if (!room_ptr) {
539 return nullptr;
540 }
541 auto& room = *room_ptr;
542 // Connected/compare views revisit already-materialized rooms. Honor Room's
543 // render dirtiness so those cached buffers refresh lazily before compositing.
544 // Do not apply this viewer's active-room entrance override to an arbitrary
545 // connected target; the room retains its own render context.
546 room.PrepareForRender();
547
548 auto& layer_mgr = GetRoomLayerManager(room_id);
549 layer_mgr.ApplyLayerMerging(room.layer_merging());
550 layer_mgr.ApplyRoomEffect(room.effect());
551
552 auto& composite = room.GetCompositeBitmap(layer_mgr);
553 if (!composite.is_active() || composite.width() <= 0) {
554 return nullptr;
555 }
556
557 // Closes the A3 first-frame race for the dungeon room composite, mirroring
558 // the title-screen path (A4). The helper queues CREATE on first sight,
559 // queues UPDATE when RoomLayerManager::CompositeToOutput marked the bitmap
560 // modified, and stamps metadata().purpose so canvas_rendering's diagnostic
561 // log can identify the bitmap.
563 return &composite;
564}
565
567 if (room_id < 0 || room_id >= zelda3::kNumberOfRooms || !rooms_) {
568 return false;
569 }
570
571 const float scale = canvas_.global_scale();
572 gfx::Bitmap* composite = PrepareRoomCompositeBitmap(room_id);
573 if (!composite) {
574 return false;
575 }
576
577 // Single-room canvas rendering prepares the composite after the normal room
578 // state upload pass. Flush here too so workbench mode can draw the freshly
579 // queued room texture in the same frame.
581
582 if (composite->texture()) {
583 canvas_.DrawBitmap(*composite, 0, 0, scale, 255);
584 return true;
585 }
586
587 return false;
588}
589
591 const zelda3::Room& room) {
592 const auto& objects = room.GetTileObjects();
593 const ImVec4 mask_color(0.2f, 0.6f, 1.0f, 0.4f);
594
595 for (const auto& obj : objects) {
596 if (obj.GetLayerValue() != 1) {
597 continue;
598 }
599
600 auto [canvas_x, canvas_y] =
602 auto [width, height] =
604 width = std::min(width, 512);
605 height = std::min(height, 512);
606 gui::DrawRect(rt, canvas_x, canvas_y, width, height, mask_color);
607 }
608}
609
610} // namespace yaze::editor
bool is_loaded() const
Definition rom.h:144
std::pair< int, int > ScreenToRoomPixelCoordinates(ImVec2 screen) const
ImVec2 ScreenToRoomPixels(ImVec2 screen) const
void RenderPotItems(const gui::CanvasRuntime &rt, const zelda3::Room &room)
const DungeonRenderingHelpers::CollisionOverlayCache & GetCollisionOverlayCache(int room_id)
DungeonObjectInteraction object_interaction_
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)
void RenderEntityOverlay(const gui::CanvasRuntime &rt, const zelda3::Room &room)
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 * TryEnsureRoom(int room_id)
void DeleteSelectedEntity()
Delete currently selected entity.
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:116
static Arena & Get()
Definition arena.cc:21
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
TextureHandle texture() const
Definition bitmap.h:401
SNES Color container.
Definition snes_color.h:110
constexpr snes_color rom_color() const
Get snes_color struct (0-255 RGB)
Definition snes_color.h:188
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
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:1162
auto global_scale() const
Definition canvas.h:397
static DimensionService & Get()
std::tuple< int, int, int, int > GetSelectionBoundsPixels(const RoomObject &obj) const
std::pair< int, int > GetPixelDimensions(const RoomObject &obj) const
const std::array< uint8_t, 0x10000 > & get_gfx_buffer() const
Definition room.h:970
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:253
void PrepareForRender(std::optional< uint8_t > entrance_blockset=std::nullopt)
Definition room.cc:850
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:382
const std::vector< PotItem > & GetPotItems() const
Definition room.h:366
A class for managing sprites in the overworld and underworld.
Definition sprite.h:37
void RenderPreviewGraphics(std::span< const uint8_t > graphics)
Definition sprite.cc:1185
auto preview_graphics() const
Definition sprite.h:98
const AgentUITheme & GetTheme()
DungeonSpriteColorTable BuildDungeonSpriteColorTable(const zelda3::GameData *game_data)
void CopyPaletteRange(std::array< ImU32, 256 > &colors, bool &has_colors, int row, int start_column, const gfx::SnesPalette &palette, size_t max_colors)
bool DrawSpritePreviewPixels(const gui::CanvasRuntime &rt, const std::vector< uint8_t > &preview, int room_x, int room_y, const DungeonSpriteColorTable &color_table)
bool IsWithinBounds(int canvas_x, int canvas_y, int margin=0)
Check if coordinates are within room bounds.
void EnsureCompositeBitmapTextureQueued(gfx::Bitmap &composite)
Editors are the view controllers for the application.
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)
std::string GetSpriteLabel(int id)
Convenience function to get a sprite label.
absl::StatusOr< CustomCollisionMap > LoadCustomCollisionMap(Rom *rom, int room_id)
bool UsesRoomObjectStream(const RoomObject &object)
std::string GetObjectName(int object_id)
constexpr int kNumberOfRooms
gfx::PaletteGroupMap palette_groups
Definition game_data.h:92