10#include "absl/strings/str_format.h"
18#include "imgui/imgui.h"
32 const std::vector<uint8_t>& preview,
33 const SDL_Rect& bounds,
int room_x,
int room_y,
34 const std::array<SDL_Color, 256>& color_table) {
35 if (!rt.
draw_list || bounds.w <= 0 || bounds.h <= 0) {
38 const size_t row_stride =
static_cast<size_t>(bounds.w);
39 if (
static_cast<size_t>(bounds.h) > preview.size() / row_stride) {
43 const float scale = std::max(rt.
scale, 0.01f);
48 bool drew_any_pixel =
false;
51 for (
int y = 0; y < bounds.h; ++y) {
53 while (x < bounds.w) {
54 const uint8_t palette_index =
55 preview[
static_cast<size_t>(y) * row_stride + x];
56 const SDL_Color& palette_color = color_table[palette_index];
60 if (palette_index == 0 || palette_color.a == 0) {
64 const ImU32 color = IM_COL32(palette_color.r, palette_color.g,
65 palette_color.b, palette_color.a);
67 const int start_x = x;
68 while (x < bounds.w && preview[
static_cast<size_t>(y) * row_stride + x] ==
73 const ImVec2 min(origin.x + (room_x + start_x) * scale,
74 origin.y + (room_y + y) * scale);
75 const ImVec2 max(origin.x + (room_x + x) * scale,
76 origin.y + (room_y + y + 1) * scale);
77 rt.
draw_list->AddRectFilled(min, max, color);
78 drew_any_pixel =
true;
83 return drew_any_pixel;
87 const int layer_value =
object.GetLayerValue();
89 if (layer_value == 0) {
92 return layer_value == 1 ?
"Lower BG2" :
"Invalid selector";
94 switch (layer_value) {
100 return "BG1 overlay";
116 const int entity_size = is_touch ? 24 : 16;
117 const auto sprite_colors =
119 const std::string_view hack_name =
122 : std::string_view{};
131 const std::span<const uint8_t> room_gfx_span(room_gfx.data(),
134 for (
const auto& sprite : room.
GetSprites()) {
135 const int canvas_x = sprite.x() * 16;
136 const int canvas_y = sprite.y() * 16;
138 if (canvas_x >= -entity_size && canvas_y >= -entity_size &&
139 canvas_x < 512 + entity_size && canvas_y < 512 + entity_size) {
140 ImVec4 sprite_color = sprite.layer() == 0 ? theme.dungeon_sprite_layer0
141 : theme.dungeon_sprite_layer1;
143 const auto* preview_layout =
146 sprite.subtype(), sprite.IsOverlord()};
153 if (preview ==
nullptr) {
156 room_gfx_span, preview_layout,
164 uncached_preview = std::move(rendered);
165 preview = &uncached_preview;
168 const SDL_Rect preview_bounds = preview->
bounds;
169 const bool drew_preview = DrawSpritePreviewPixels(
170 rt, preview->pixels, preview_bounds, canvas_x + preview_bounds.x,
171 canvas_y + preview_bounds.y, sprite_colors);
175 ImGui::GetColorU32(sprite_color));
177 gui::DrawRect(rt, canvas_x, canvas_y, entity_size, entity_size,
181 std::string full_name = preview_layout !=
nullptr
182 ? preview_layout->name
184 std::string sprite_text;
185 if (full_name.length() > 12) {
186 sprite_text = absl::StrFormat(
"%02X %s..", sprite.id(),
187 full_name.substr(0, 8).c_str());
190 absl::StrFormat(
"%02X %s", sprite.id(), full_name.c_str());
205 if (pot_items.empty()) {
209 static const char* kPotItemNames[] = {
210 "Nothing",
"Green Rupee",
"Rock",
"Bee",
"Health",
211 "Bomb",
"Heart",
"Blue Rupee",
"Key",
"Arrow",
212 "Bomb",
"Heart",
"Magic",
"Full Magic",
"Cucco",
213 "Green Soldier",
"Bush Stal",
"Blue Soldier",
"Landmine",
"Heart",
214 "Fairy",
"Heart",
"Nothing",
"Hole",
"Warp",
215 "Staircase",
"Bombable",
"Switch"};
216 constexpr size_t kPotItemNameCount =
217 sizeof(kPotItemNames) /
sizeof(kPotItemNames[0]);
219 for (
const auto& pot_item : pot_items) {
220 const int pixel_x = pot_item.GetPixelX();
221 const int pixel_y = pot_item.GetPixelY();
222 auto [canvas_x, canvas_y] =
227 const int entity_size = is_touch ? 24 : 16;
229 if (canvas_x >= -entity_size && canvas_y >= -entity_size &&
230 canvas_x < 512 + entity_size && canvas_y < 512 + entity_size) {
232 ImVec4 pot_item_color;
233 if (pot_item.item == 0) {
234 pot_item_color = theme.status_inactive;
235 pot_item_color.w = 0.4f;
237 pot_item_color = theme.item_color;
238 pot_item_color.w = 0.75f;
241 gui::DrawRect(rt, canvas_x, canvas_y, entity_size, entity_size,
244 std::string item_name;
245 if (pot_item.item < kPotItemNameCount) {
246 item_name = kPotItemNames[pot_item.item];
248 item_name = absl::StrFormat(
"Unk%02X", pot_item.item);
251 std::string item_text =
252 absl::StrFormat(
"%02X %s", pot_item.item, item_name.c_str());
266 constexpr const char* kPopupId =
"##TouchEntityContextMenu";
267 const ImGuiIO& io = ImGui::GetIO();
268 const bool touch_context_click =
269 rt.
hovered && io.MouseSource == ImGuiMouseSource_TouchScreen &&
270 ImGui::IsMouseClicked(ImGuiMouseButton_Right);
272 const bool should_open_context = gesture_long_press || touch_context_click;
274 if (should_open_context) {
275 const ImVec2 gesture_pos = gesture_long_press
277 : ImGui::GetMousePos();
281 const auto [room_x, room_y] =
286 const float rel_x = room_pos.x;
287 const float rel_y = room_pos.y;
289 const int hit_size = is_touch ? 24 : 16;
292 for (
size_t idx = 0; idx < sprites.size(); ++idx) {
293 const int sprite_px = sprites[idx].x() * 16;
294 const int sprite_py = sprites[idx].y() * 16;
295 if (rel_x >= sprite_px && rel_x < sprite_px + hit_size &&
296 rel_y >= sprite_py && rel_y < sprite_py + hit_size) {
298 ImGui::OpenPopup(kPopupId);
303 if (!ImGui::IsPopupOpen(kPopupId)) {
305 for (
size_t idx = 0; idx < pot_items.size(); ++idx) {
306 const int item_px = pot_items[idx].GetPixelX();
307 const int item_py = pot_items[idx].GetPixelY();
308 if (rel_x >= item_px && rel_x < item_px + hit_size &&
309 rel_y >= item_py && rel_y < item_py + hit_size) {
311 ImGui::OpenPopup(kPopupId);
317 if (!ImGui::IsPopupOpen(kPopupId)) {
318 const auto object_index =
322 static_cast<int>(rel_y));
323 if (object_index.has_value()) {
325 ImGui::OpenPopup(kPopupId);
330 if (ImGui::BeginPopup(kPopupId)) {
335 if (sel.index < sprites.size()) {
337 ImGui::TextDisabled(tr(
"Sprite: %02X %s"), sprites[sel.index].id(),
341 if (ImGui::MenuItem(tr(
"Delete Sprite"))) {
345 ImGui::TextDisabled(tr(
"Pot Item"));
347 if (ImGui::MenuItem(tr(
"Delete Item"))) {
353 if (indices.size() == 1) {
355 if (indices[0] < objects.size()) {
357 ImGui::TextDisabled(tr(
"Object: %03X %s"), objects[indices[0]].id_,
362 ImGui::TextDisabled(tr(
"%zu objects selected"),
366 if (ImGui::MenuItem(tr(
"Delete"))) {
369 if (ImGui::MenuItem(tr(
"Copy"))) {
373 if (ImGui::MenuItem(tr(
"Send to Front"))) {
376 if (ImGui::MenuItem(tr(
"Send to Back"))) {
389 for (
const auto& obj : objects) {
390 bool show_this_type =
true;
391 if (obj.id_ < 0x100) {
393 }
else if (obj.id_ >= 0x100 && obj.id_ < 0x200) {
395 }
else if (obj.id_ >= 0xF00) {
399 bool show_this_layer =
true;
400 if (obj.GetLayerValue() == 0) {
402 }
else if (obj.GetLayerValue() == 1) {
404 }
else if (obj.GetLayerValue() == 2) {
408 if (!show_this_type || !show_this_layer) {
412 auto [canvas_x, canvas_y, width, height] =
414 width = std::min(width, 512);
415 height = std::min(height, 512);
417 ImVec4 outline_color;
418 if (obj.GetLayerValue() == 0) {
419 outline_color = theme.dungeon_outline_layer0;
420 }
else if (obj.GetLayerValue() == 1) {
421 outline_color = theme.dungeon_outline_layer1;
423 outline_color = theme.dungeon_outline_layer2;
426 gui::DrawRect(rt, canvas_x, canvas_y, width, height, outline_color);
429 if (name.length() > 12) {
430 name = name.substr(0, 10) +
"..";
433 if (obj.id_ >= 0x100) {
434 label = absl::StrFormat(
"0x%03X\n%s\n%s [%dx%d]", obj.id_, name.c_str(),
435 GetStoredPlacementLabel(obj), width, height);
437 label = absl::StrFormat(
"0x%02X\n%s\n%s [%dx%d]", obj.id_, name.c_str(),
438 GetStoredPlacementLabel(obj), width, height);
465 const auto& map = map_or.value();
468 for (
int y = 0; y < 64; ++y) {
469 for (
int x = 0; x < 64; ++x) {
470 const uint8_t tile = map.tiles[
static_cast<size_t>(y * 64 + x)];
476 static_cast<uint8_t
>(x),
static_cast<uint8_t
>(y), tile});
490 if (composite !=
nullptr && room !=
nullptr) {
501 entry.output = std::make_unique<RoomCompositeOutput>();
503 entry.last_used_frame =
504 ImGui::GetCurrentContext() !=
nullptr ? ImGui::GetFrameCount() : 0;
519 auto& room = *room_ptr;
521 auto& composite = output.
Prepare(room, layer_mgr);
522 if (!composite.is_active() || composite.width() <= 0) {
562 const ImVec4 mask_color(0.2f, 0.6f, 1.0f, 0.4f);
564 for (
const auto& obj : objects) {
565 if (obj.GetLayerValue() != 1) {
569 auto [canvas_x, canvas_y] =
571 auto [width, height] =
573 width = std::min(width, 512);
574 height = std::min(height, 512);
575 gui::DrawRect(rt, canvas_x, canvas_y, width, height, mask_color);
const std::string & hack_name() const
bool loaded() const
Check if the manifest has been loaded.
bool DrawRoomBackgroundLayers(int room_id)
gfx::Bitmap * PrepareConnectedRoomCompositeBitmap(int room_id)
zelda3::GameData * game_data_
ObjectOutlineToggles object_outline_toggles_
void RenderPotItems(const gui::CanvasRuntime &rt, const zelda3::Room &room)
gfx::Bitmap * PrepareRoomCompositeWithOutput(int room_id, RoomCompositeOutput &output)
RoomCompositeOutput primary_composite_output_
zelda3::SpritePreviewResourceCache sprite_preview_resources_
const DungeonRenderingHelpers::CollisionOverlayCache & GetCollisionOverlayCache(int room_id)
std::map< int, ConnectedRoomCompositeEntry > connected_composite_outputs_
DungeonObjectInteraction object_interaction_
const project::YazeProject * project_
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_
util::LruCache< SpritePreviewKey, CachedSpritePreview > sprite_preview_cache_
gui::CanvasTouchHandler touch_handler_
EntityVisibility entity_visibility_
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 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 * GetIfMaterialized(int room_id)
zelda3::Room * TryEnsureRoom(int room_id)
void DeleteSelectedEntity()
Delete currently selected entity.
TileObjectHandler & tile_handler()
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)
Represents a bitmap image optimized for SNES ROM hacking.
TextureHandle texture() const
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
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
const std::vector< SDL_Color > & rendered_palette() const
const gfx::SnesPalette & rendered_dungeon_palette() const
const std::vector< zelda3::Sprite > & GetSprites() const
uint64_t graphics_revision() const
const std::vector< RoomObject > & GetTileObjects() const
const std::vector< PotItem > & GetPotItems() const
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.
void RenderPreviewGraphics(std::span< const uint8_t > graphics, const SpriteOamLayout *layout_override=nullptr, std::span< const uint8_t > graphics_resource={})
auto preview_graphics() const
SDL_Rect preview_bounds() const
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 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)
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::array< SDL_Color, 256 > BuildDungeonSpriteRenderPalette(const Room &room, const GameData *game_data)
std::vector< CollisionOverlayEntry > entries
std::array< bool, 256 > stop_tiles
std::array< bool, 256 > switch_tiles
std::array< bool, 256 > track_tiles
core::HackManifest hack_manifest
std::string assets_folder
std::string GetAbsolutePath(const std::string &relative_path) const