9#include "absl/strings/str_format.h"
17#include "imgui/imgui.h"
34 std::array<ImU32, 256> colors{};
35 bool has_sprite_colors =
false;
40 return IM_COL32(
static_cast<int>(rgb.red),
static_cast<int>(rgb.green),
41 static_cast<int>(rgb.blue), 255);
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);
51 if (index < 0 || index >=
static_cast<int>(colors.size())) {
54 colors[
static_cast<size_t>(index)] =
ToImU32(palette[i]);
68 if (groups.sprites_aux1.size() > 1) {
71 groups.sprites_aux1.palette_ref(1), 7);
73 if (!groups.sprites_aux3.empty()) {
76 groups.sprites_aux3.palette_ref(0), 7);
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) {
84 if (source_index >= global.size()) {
88 ToImU32(global[source_index++]);
94 if (!groups.sprites_aux1.empty()) {
96 groups.sprites_aux1.palette_ref(0), 7);
98 if (!groups.sprites_aux2.empty()) {
100 groups.sprites_aux2.palette_ref(0), 7);
102 if (!groups.armors.empty()) {
104 groups.armors.palette_ref(0), 15);
111 const std::vector<uint8_t>& preview,
int room_x,
120 const float scale = std::max(rt.
scale, 0.01f);
125 bool drew_any_pixel =
false;
131 const uint8_t palette_index =
133 const ImU32 color = color_table.
colors[palette_index];
134 if (palette_index == 0xFF || color == 0) {
139 const int start_x = x;
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;
156 return drew_any_pixel;
160 const int layer_value =
object.GetLayerValue();
162 if (layer_value == 0) {
165 return layer_value == 1 ?
"Lower BG2" :
"Invalid selector";
167 switch (layer_value) {
171 return "BG2 overlay";
173 return "BG1 overlay";
189 const int entity_size = is_touch ? 24 : 16;
190 const DungeonSpriteColorTable sprite_colors =
193 const std::span<const uint8_t> room_gfx_span(room_gfx.data(),
196 for (
const auto& sprite : room.
GetSprites()) {
197 const int canvas_x = sprite.x() * 16;
198 const int canvas_y = sprite.y() * 16;
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;
208 const bool drew_preview =
209 preview && DrawSpritePreviewPixels(
210 rt, *preview, canvas_x - kSpritePreviewAnchor,
211 canvas_y - kSpritePreviewAnchor, sprite_colors);
215 ImGui::GetColorU32(sprite_color));
217 gui::DrawRect(rt, canvas_x, canvas_y, entity_size, entity_size,
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());
228 absl::StrFormat(
"%02X %s", sprite.id(), full_name.c_str());
243 if (pot_items.empty()) {
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]);
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] =
265 const int entity_size = is_touch ? 24 : 16;
267 if (canvas_x >= -entity_size && canvas_y >= -entity_size &&
268 canvas_x < 512 + entity_size && canvas_y < 512 + entity_size) {
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;
275 pot_item_color = theme.item_color;
276 pot_item_color.w = 0.75f;
279 gui::DrawRect(rt, canvas_x, canvas_y, entity_size, entity_size,
282 std::string item_name;
283 if (pot_item.item < kPotItemNameCount) {
284 item_name = kPotItemNames[pot_item.item];
286 item_name = absl::StrFormat(
"Unk%02X", pot_item.item);
289 std::string item_text =
290 absl::StrFormat(
"%02X %s", pot_item.item, item_name.c_str());
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);
310 const bool should_open_context = gesture_long_press || touch_context_click;
312 if (should_open_context) {
313 const ImVec2 gesture_pos = gesture_long_press
315 : ImGui::GetMousePos();
319 const auto [room_x, room_y] =
324 const float rel_x = room_pos.x;
325 const float rel_y = room_pos.y;
327 const int hit_size = is_touch ? 24 : 16;
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);
341 if (!ImGui::IsPopupOpen(kPopupId)) {
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);
355 if (!ImGui::IsPopupOpen(kPopupId)) {
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);
375 if (ImGui::BeginPopup(kPopupId)) {
380 if (sel.index < sprites.size()) {
382 ImGui::TextDisabled(tr(
"Sprite: %02X %s"), sprites[sel.index].id(),
386 if (ImGui::MenuItem(tr(
"Delete Sprite"))) {
390 ImGui::TextDisabled(tr(
"Pot Item"));
392 if (ImGui::MenuItem(tr(
"Delete Item"))) {
398 if (indices.size() == 1) {
400 if (indices[0] < objects.size()) {
402 ImGui::TextDisabled(tr(
"Object: %03X %s"), objects[indices[0]].id_,
407 ImGui::TextDisabled(tr(
"%zu objects selected"),
411 if (ImGui::MenuItem(tr(
"Delete"))) {
414 if (ImGui::MenuItem(tr(
"Copy"))) {
418 if (ImGui::MenuItem(tr(
"Send to Front"))) {
421 if (ImGui::MenuItem(tr(
"Send to Back"))) {
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) {
444 bool show_this_layer =
true;
445 if (obj.GetLayerValue() == 0) {
447 }
else if (obj.GetLayerValue() == 1) {
449 }
else if (obj.GetLayerValue() == 2) {
453 if (!show_this_type || !show_this_layer) {
457 auto [canvas_x, canvas_y, width, height] =
459 width = std::min(width, 512);
460 height = std::min(height, 512);
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;
468 outline_color = theme.dungeon_outline_layer2;
471 gui::DrawRect(rt, canvas_x, canvas_y, width, height, outline_color);
474 if (name.length() > 12) {
475 name = name.substr(0, 10) +
"..";
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);
482 label = absl::StrFormat(
"0x%02X\n%s\n%s [%dx%d]", obj.id_, name.c_str(),
483 GetStoredPlacementLabel(obj), width, height);
510 const auto& map = map_or.value();
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)];
521 static_cast<uint8_t
>(x),
static_cast<uint8_t
>(y), tile});
541 auto& room = *room_ptr;
549 layer_mgr.ApplyLayerMerging(room.layer_merging());
550 layer_mgr.ApplyRoomEffect(room.effect());
552 auto& composite = room.GetCompositeBitmap(layer_mgr);
553 if (!composite.is_active() || composite.width() <= 0) {
593 const ImVec4 mask_color(0.2f, 0.6f, 1.0f, 0.4f);
595 for (
const auto& obj : objects) {
596 if (obj.GetLayerValue() != 1) {
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);
bool DrawRoomBackgroundLayers(int room_id)
zelda3::GameData * game_data_
ObjectOutlineToggles object_outline_toggles_
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)
DungeonRoomStore * rooms_
gfx::IRenderer * renderer_
gui::CanvasTouchHandler touch_handler_
EntityVisibility entity_visibility_
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 HandleDeleteSelected()
void SetSelectedObjects(const std::vector< size_t > &indices)
SelectedEntity GetSelectedEntity() const
void SendSelectedToBack()
std::vector< size_t > GetSelectedObjectIndices() const
InteractionCoordinator & entity_coordinator()
Get the interaction coordinator for entity handling.
size_t GetSelectionCount() const
bool HasEntitySelection() const
void SelectEntity(EntityType type, size_t index)
void HandleCopySelected()
void SendSelectedToFront()
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)
Represents a bitmap image optimized for SNES ROM hacking.
TextureHandle texture() const
constexpr snes_color rom_color() const
Get snes_color struct (0-255 RGB)
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)
auto global_scale() const
static bool IsTouchDevice()
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
const std::vector< zelda3::Sprite > & GetSprites() const
void PrepareForRender(std::optional< uint8_t > entrance_blockset=std::nullopt)
const std::vector< RoomObject > & GetTileObjects() const
const std::vector< PotItem > & GetPotItems() const
A class for managing sprites in the overworld and underworld.
void RenderPreviewGraphics(std::span< const uint8_t > graphics)
auto preview_graphics() const
const AgentUITheme & GetTheme()
constexpr int kSpritePaletteRowsStart
constexpr int kSpritePreviewSize
ImU32 ToImU32(const gfx::SnesColor &color)
constexpr int kSpritePreviewAnchor
DungeonSpriteColorTable BuildDungeonSpriteColorTable(const zelda3::GameData *game_data)
constexpr int kPaletteRowSize
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
std::vector< CollisionOverlayEntry > entries
std::array< bool, 256 > stop_tiles
std::array< bool, 256 > switch_tiles
std::array< bool, 256 > track_tiles
std::array< ImU32, 256 > colors
gfx::PaletteGroupMap palette_groups