10#include "absl/strings/str_format.h"
20#include "imgui/imgui.h"
35 int height,
bool selected) {
38 if (!rt.
hovered || !ImGui::IsWindowHovered())
42 return ImGui::IsMouseHoveringRect(
43 min, ImVec2(min.x + width * rt.
scale, min.y + height * rt.
scale));
47 const std::vector<uint8_t>& preview,
48 const SDL_Rect& bounds,
int room_x,
int room_y,
49 const std::array<SDL_Color, 256>& color_table) {
50 if (!rt.
draw_list || bounds.w <= 0 || bounds.h <= 0) {
53 const size_t row_stride =
static_cast<size_t>(bounds.w);
54 if (
static_cast<size_t>(bounds.h) > preview.size() / row_stride) {
58 const float scale = std::max(rt.
scale, 0.01f);
63 bool drew_any_pixel =
false;
66 for (
int y = 0; y < bounds.h; ++y) {
68 while (x < bounds.w) {
69 const uint8_t palette_index =
70 preview[
static_cast<size_t>(y) * row_stride + x];
71 const SDL_Color& palette_color = color_table[palette_index];
75 if (palette_index == 0 || palette_color.a == 0) {
79 const ImU32 color = IM_COL32(palette_color.r, palette_color.g,
80 palette_color.b, palette_color.a);
82 const int start_x = x;
83 while (x < bounds.w && preview[
static_cast<size_t>(y) * row_stride + x] ==
88 const ImVec2 min(origin.x + (room_x + start_x) * scale,
89 origin.y + (room_y + y) * scale);
90 const ImVec2 max(origin.x + (room_x + x) * scale,
91 origin.y + (room_y + y + 1) * scale);
98 drew_any_pixel =
true;
103 return drew_any_pixel;
107 const int layer_value =
object.GetLayerValue();
109 if (layer_value == 0) {
112 return layer_value == 1 ?
"Lower BG2" :
"Invalid selector";
114 switch (layer_value) {
118 return "BG2 overlay";
120 return "BG1 overlay";
136 const int entity_size = is_touch ? 24 : 16;
137 const auto sprite_colors =
139 const std::string_view hack_name =
142 : std::string_view{};
151 const std::span<const uint8_t> room_gfx_span(room_gfx.data(),
154 const auto& selected =
157 for (
size_t index = 0; index < sprites.size(); ++index) {
158 const auto& sprite = sprites[index];
159 const int canvas_x = sprite.x() * 16;
160 const int canvas_y = sprite.y() * 16;
162 if (canvas_x >= -entity_size && canvas_y >= -entity_size &&
163 canvas_x < 512 + entity_size && canvas_y < 512 + entity_size) {
164 ImVec4 sprite_color = sprite.layer() == 0 ? theme.dungeon_sprite_layer0
165 : theme.dungeon_sprite_layer1;
167 const auto* preview_layout =
170 sprite.subtype(), sprite.IsOverlord()};
177 if (preview ==
nullptr) {
180 room_gfx_span, preview_layout,
188 uncached_preview = std::move(rendered);
189 preview = &uncached_preview;
192 const SDL_Rect preview_bounds = preview->
bounds;
193 const bool drew_preview = DrawSpritePreviewPixels(
194 rt, preview->pixels, preview_bounds, canvas_x + preview_bounds.x,
195 canvas_y + preview_bounds.y, sprite_colors);
197 const bool show_label = ShowEntityLabel(
198 rt, canvas_x, canvas_y, entity_size, entity_size,
199 std::any_of(selected.begin(), selected.end(),
200 [index](
const auto& entity) {
201 return entity.type == EntityType::Sprite &&
202 entity.index == index;
204 if (drew_preview && show_label) {
206 ImGui::GetColorU32(sprite_color));
207 }
else if (!drew_preview) {
208 gui::DrawRect(rt, canvas_x, canvas_y, entity_size, entity_size,
212 std::string full_name = preview_layout !=
nullptr
213 ? preview_layout->name
215 std::string sprite_text;
216 if (full_name.length() > 12) {
217 sprite_text = absl::StrFormat(
"%02X %s..", sprite.id(),
218 full_name.substr(0, 8).c_str());
221 absl::StrFormat(
"%02X %s", sprite.id(), full_name.c_str());
237 if (pot_items.empty()) {
241 const auto sprite_colors =
243 const auto& selected =
245 for (
size_t index = 0; index < pot_items.size(); ++index) {
246 const auto& pot_item = pot_items[index];
247 const int pixel_x = pot_item.GetPixelX();
248 const int pixel_y = pot_item.GetPixelY();
249 auto [canvas_x, canvas_y] =
254 const int entity_size = is_touch ? 24 : 16;
256 if (canvas_x >= -entity_size && canvas_y >= -entity_size &&
257 canvas_x < 512 + entity_size && canvas_y < 512 + entity_size) {
259 ImVec4 pot_item_color;
260 if (pot_item.item == 0) {
261 pot_item_color = theme.status_inactive;
262 pot_item_color.w = 0.4f;
264 pot_item_color = theme.item_color;
265 pot_item_color.w = 0.75f;
273 if (preview ==
nullptr) {
276 pot_item.item, room, rendered.
pixels, rendered.
bounds)) {
281 uncached_preview = std::move(rendered);
282 preview = &uncached_preview;
287 bool drew_preview =
false;
288 if (preview !=
nullptr && preview->bounds.w > 0 &&
289 preview->bounds.h > 0 && !preview->pixels.empty()) {
290 const SDL_Rect preview_bounds = preview->
bounds;
291 const int offset_x = (entity_size > preview_bounds.w)
292 ? (entity_size - preview_bounds.w) / 2
294 const int offset_y = (entity_size > preview_bounds.h)
295 ? (entity_size - preview_bounds.h) / 2
297 drew_preview = DrawSpritePreviewPixels(
298 rt, preview->pixels, preview_bounds,
299 canvas_x + preview_bounds.x + offset_x,
300 canvas_y + preview_bounds.y + offset_y, sprite_colors);
303 const bool is_selected = std::any_of(
304 selected.begin(), selected.end(), [index](
const auto& entity) {
305 return entity.type == EntityType::Item && entity.index == index;
307 const bool show_label = ShowEntityLabel(
308 rt, canvas_x, canvas_y, entity_size, entity_size, is_selected);
310 if (drew_preview && (show_label || is_selected)) {
312 ImGui::GetColorU32(pot_item_color));
313 }
else if (!drew_preview) {
314 gui::DrawRect(rt, canvas_x, canvas_y, entity_size, entity_size,
318 std::string item_name;
322 item_name = absl::StrFormat(
"Unk%02X", pot_item.item);
325 std::string item_text =
326 absl::StrFormat(
"%02X %s", pot_item.item, item_name.c_str());
344 if (chests.empty() ||
353 const float padding = 4.0f;
354 const float wrap_width = std::min(180.0f, rt.
canvas_sz.x - padding * 4);
356 for (
size_t i = 0; i < objects.size(); ++i) {
361 const auto&
object = objects[i];
362 const float chest_width =
object.id_ == 0xFB1 ? 32.0f : 16.0f;
364 ImVec2(
object.x_ * 8.0f,
object.y_ * 8.0f));
366 ImVec2(
object.x_ * 8.0f + chest_width,
367 object.y_ * 8.0f + (
object.id_ == 0xFB1 ? 24.0f : 16.0f)));
368 if (chest_max.x < rt.
canvas_p0.x || chest_min.x > canvas_max.x ||
369 chest_max.y < rt.
canvas_p0.y || chest_min.y > canvas_max.y) {
372 const auto item_id = chests[*chest_index].id;
373 const std::string text = absl::StrFormat(
377 const ImVec2 text_size =
378 ImGui::CalcTextSize(text.c_str(),
nullptr,
false, wrap_width);
379 const float width = text_size.x + padding * 2;
380 const float height = text_size.y + padding * 2;
382 std::clamp((chest_min.x + chest_max.x - width) * 0.5f, rt.
canvas_p0.x,
383 std::max(rt.
canvas_p0.x, canvas_max.x - width)),
384 std::max(rt.
canvas_p0.y, chest_min.y - height - 3.0f));
385 const ImVec2 max(min.x + width, min.y + height);
386 rt.
draw_list->AddRectFilled(min, max, IM_COL32(0, 0, 0, 200), 3.0f);
387 rt.
draw_list->AddRect(min, max, ImGui::GetColorU32(theme.item_color), 3.0f);
388 rt.
draw_list->AddText(ImGui::GetFont(), ImGui::GetFontSize(),
389 ImVec2(min.x + padding, min.y + padding),
390 ImGui::GetColorU32(ImGuiCol_Text), text.c_str(),
391 nullptr, wrap_width);
407 constexpr const char* kPopupId =
"##TouchEntityContextMenu";
408 const ImGuiIO& io = ImGui::GetIO();
409 const bool touch_context_click =
410 rt.
hovered && io.MouseSource == ImGuiMouseSource_TouchScreen &&
411 ImGui::IsMouseClicked(ImGuiMouseButton_Right);
413 const bool should_open_context = gesture_long_press || touch_context_click;
415 if (should_open_context) {
416 const ImVec2 gesture_pos = gesture_long_press
418 : ImGui::GetMousePos();
422 const auto [room_x, room_y] =
427 const float rel_x = room_pos.x;
428 const float rel_y = room_pos.y;
430 const int hit_size = is_touch ? 24 : 16;
433 for (
size_t idx = 0; idx < sprites.size(); ++idx) {
434 const int sprite_px = sprites[idx].x() * 16;
435 const int sprite_py = sprites[idx].y() * 16;
436 if (rel_x >= sprite_px && rel_x < sprite_px + hit_size &&
437 rel_y >= sprite_py && rel_y < sprite_py + hit_size) {
439 ImGui::OpenPopup(kPopupId);
444 if (!ImGui::IsPopupOpen(kPopupId)) {
446 for (
size_t idx = 0; idx < pot_items.size(); ++idx) {
447 const int item_px = pot_items[idx].GetPixelX();
448 const int item_py = pot_items[idx].GetPixelY();
449 if (rel_x >= item_px && rel_x < item_px + hit_size &&
450 rel_y >= item_py && rel_y < item_py + hit_size) {
452 ImGui::OpenPopup(kPopupId);
458 if (!ImGui::IsPopupOpen(kPopupId)) {
459 const auto object_index =
463 static_cast<int>(rel_y));
464 if (object_index.has_value()) {
466 ImGui::OpenPopup(kPopupId);
471 if (ImGui::BeginPopup(kPopupId)) {
474 if (sel.type == EntityType::Sprite) {
476 if (sel.index < sprites.size()) {
478 ImGui::TextDisabled(tr(
"Sprite: %02X %s"), sprites[sel.index].id(),
482 if (ImGui::MenuItem(tr(
"Delete Sprite"))) {
485 }
else if (sel.type == EntityType::Item) {
486 ImGui::TextDisabled(tr(
"Pot Item"));
488 if (ImGui::MenuItem(tr(
"Delete Item"))) {
494 if (indices.size() == 1) {
496 if (indices[0] < objects.size()) {
498 ImGui::TextDisabled(tr(
"Object: %03X %s"), objects[indices[0]].id_,
503 ImGui::TextDisabled(tr(
"%zu objects selected"),
507 if (ImGui::MenuItem(tr(
"Delete"))) {
510 if (ImGui::MenuItem(tr(
"Copy"))) {
514 if (ImGui::MenuItem(tr(
"Send to Front"))) {
517 if (ImGui::MenuItem(tr(
"Send to Back"))) {
530 for (
const auto& obj : objects) {
531 bool show_this_type =
true;
532 if (obj.id_ < 0x100) {
534 }
else if (obj.id_ >= 0x100 && obj.id_ < 0x200) {
536 }
else if (obj.id_ >= 0xF00) {
540 bool show_this_layer =
true;
541 if (obj.GetLayerValue() == 0) {
543 }
else if (obj.GetLayerValue() == 1) {
545 }
else if (obj.GetLayerValue() == 2) {
549 if (!show_this_type || !show_this_layer) {
553 auto [canvas_x, canvas_y, width, height] =
555 width = std::min(width, 512);
556 height = std::min(height, 512);
558 ImVec4 outline_color;
559 if (obj.GetLayerValue() == 0) {
560 outline_color = theme.dungeon_outline_layer0;
561 }
else if (obj.GetLayerValue() == 1) {
562 outline_color = theme.dungeon_outline_layer1;
564 outline_color = theme.dungeon_outline_layer2;
567 gui::DrawRect(rt, canvas_x, canvas_y, width, height, outline_color);
570 if (name.length() > 12) {
571 name = name.substr(0, 10) +
"..";
574 if (obj.id_ >= 0x100) {
575 label = absl::StrFormat(
"0x%03X\n%s\n%s [%dx%d]", obj.id_, name.c_str(),
576 GetStoredPlacementLabel(obj), width, height);
578 label = absl::StrFormat(
"0x%02X\n%s\n%s [%dx%d]", obj.id_, name.c_str(),
579 GetStoredPlacementLabel(obj), width, height);
604 auto map_or = working ? absl::StatusOr<zelda3::CustomCollisionMap>(
605 working->custom_collision())
612 const auto& map = map_or.value();
615 for (
int y = 0; y < 64; ++y) {
616 for (
int x = 0; x < 64; ++x) {
617 const uint8_t tile = map.tiles[
static_cast<size_t>(y * 64 + x)];
618 if (map.HasOverride(
static_cast<size_t>(y * 64 + x)) &&
624 static_cast<uint8_t
>(x),
static_cast<uint8_t
>(y), tile});
638 if (composite !=
nullptr && room !=
nullptr) {
658 entry.output = std::make_unique<RoomCompositeOutput>();
660 entry.last_used_frame =
661 ImGui::GetCurrentContext() !=
nullptr ? ImGui::GetFrameCount() : 0;
676 auto& room = *room_ptr;
678 auto& composite = output.
Prepare(room, layer_mgr);
679 if (!composite.is_active() || composite.width() <= 0) {
719 const ImVec4 mask_color(0.2f, 0.6f, 1.0f, 0.4f);
721 for (
const auto& obj : objects) {
722 if (obj.GetLayerValue() != 1) {
726 auto [canvas_x, canvas_y] =
728 auto [width, height] =
730 width = std::min(width, 512);
731 height = std::min(height, 512);
732 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_
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)
uint8_t current_entrance_blockset_
void DrawMaskHighlights(const gui::CanvasRuntime &rt, const zelda3::Room &room)
DungeonRoomStore * rooms_
gfx::IRenderer * renderer_
util::LruCache< SpritePreviewKey, CachedSpritePreview > sprite_preview_cache_
void RenderChestContents(const gui::CanvasRuntime &rt, const zelda3::Room &room)
gui::CanvasTouchHandler touch_handler_
std::pair< uint64_t, uint8_t > ItemPreviewKey
EntityVisibility entity_visibility_
util::LruCache< ItemPreviewKey, CachedSpritePreview > item_preview_cache_
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)
absl::Status HandleDeleteSelected()
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
absl::Status HandleCopySelected()
bool HasEntitySelection() const
void SelectEntity(EntityType type, size_t index)
void SendSelectedToFront()
static std::pair< int, int > RoomToCanvasCoordinates(int room_x, int room_y)
static bool BuildPotItemPreview(uint8_t item_id, const zelda3::Room &room, std::vector< uint8_t > &out_pixels, SDL_Rect &out_bounds)
zelda3::Room * GetIfMaterialized(int room_id)
zelda3::Room * GetIfLoaded(int room_id)
zelda3::Room * TryEnsureRoom(int room_id)
const std::vector< SelectedEntity > & GetSelectedEntities() const
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< chest_data > & GetChests() 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
bool AreChestsLoaded() 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 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.
const char * GetDungeonPotItemName(uint8_t item_id)
constexpr size_t kDungeonPotItemCount
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)
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)
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)
std::vector< uint8_t > pixels
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::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
std::string assets_folder
std::string GetAbsolutePath(const std::string &relative_path) const