7#include "absl/strings/str_format.h"
17#include "imgui/imgui.h"
29 : current_room_id_(current_room_id),
31 canvas_viewer_(canvas_viewer) {}
34 return "dungeon.sprite_editor";
38 return "Sprite Editor";
69 constexpr const char* categories[] = {
"All sprites",
"Used here"};
72 std::vector<std::pair<int, std::string>> entries;
73 for (
int id = 0;
id < 256; ++id) {
75 std::none_of(room.GetSprites().begin(), room.GetSprites().end(),
76 [
id](
const auto& sprite) { return sprite.id() == id; }))
80 entries.emplace_back(
id, std::move(name));
83 std::stable_sort(entries.begin(), entries.end(),
84 [](
const auto& a,
const auto& b) {
85 return workbench::DungeonBrowserFold(a.second) <
86 workbench::DungeonBrowserFold(b.second);
88 const bool can_place =
95 if (ImGui::BeginChild(
"##SpriteLibrary", ImVec2(0, 0),
false)) {
96 ImGui::BeginDisabled(!can_place);
97 for (
const auto& [
id, name] : entries) {
101 [&,
id =
id](ImVec2 pos, ImVec2 size) {
104 ImGui::GetWindowDrawList(), preview.pixels, preview.bounds, pos,
105 ImVec2(pos.x + size.x, pos.y + size.y), colors);
108 absl::StrFormat(
"place_sprite_%02X",
id));
109 if (picked && can_place) {
115 ImGui::EndDisabled();
117 ImGui::TextDisabled(
"No matches");
126 ImGui::TextDisabled(
"No room selected");
137 if (selected.size() == 1 && selected.front().type == EntityType::Sprite) {
142 if (show_room_sprites) {
158 std::function<
void()> callback) {
173 if (ImGui::IsItemHovered())
176 const bool done = ImGui::SmallButton(
"Done (Esc)");
181 ImGui::TextDisabled(
"Click a sprite, then the canvas.");
190 const std::string_view hack_name =
193 : std::string_view{};
195 project ? project->
filepath : std::string{},
199 if (resources_changed ||
211 sprite.
set_id(
static_cast<uint8_t
>(sprite_id));
213 const auto* preview_layout =
215 if (!preview_layout) {
220 const std::span<const uint8_t> room_gfx_span(room_gfx.data(),
223 room_gfx_span, preview_layout,
230 auto [inserted_it, _] =
232 return inserted_it->second;
236 ImGui::SetNextItemWidth(-1);
237 ImGui::InputTextWithHint(
"##Search",
"Search sprites by name or hex ID...",
241 const float available = std::max(1.0f, ImGui::GetContentRegionAvail().y);
242 const float list_height =
243 show_room_sprites ? std::max(1.0f, available * 0.55f) : available;
244 const bool can_place =
249 if (ImGui::BeginChild(
"##SpriteLibrary", ImVec2(0, list_height),
true)) {
254 ImDrawList* draw_list = ImGui::GetWindowDrawList();
256 ImGui::BeginDisabled(!can_place);
257 for (
int id = 0;
id < 256; ++id) {
263 const float row_height = 28.0f;
264 const ImVec2 p0 = ImGui::GetCursorScreenPos();
266 const bool selected =
267 ImGui::Selectable(
"##row", is_selected, 0, ImVec2(0, row_height));
269 absl::StrFormat(
"place_sprite_%02X",
id));
276 const ImVec2 preview_min(p0.x + 4.0f, p0.y + 2.0f);
277 const ImVec2 preview_max(p0.x + 28.0f, p0.y + 26.0f);
278 bool drew_preview =
false;
279 if (preview.bounds.w > 0 && preview.bounds.h > 0 &&
280 !preview.pixels.empty()) {
282 draw_list, preview.pixels, preview.bounds, preview_min, preview_max,
286 draw_list->AddRect(preview_min, preview_max,
287 ImGui::GetColorU32(theme.status_inactive), 2.0f);
288 draw_list->AddText(ImVec2(preview_min.x + 5.0f, preview_min.y + 4.0f),
289 ImGui::GetColorU32(theme.status_inactive),
293 const std::string label = absl::StrFormat(
"%02X %s",
id, name);
294 ImGui::LogText(
"%s\n", label.c_str());
296 ImVec2(p0.x + 34.0f, p0.y + 6.0f),
297 ImGui::GetColorU32(is_selected ? theme.text_primary
298 : theme.text_secondary_gray),
301 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
302 ImGui::SetTooltip(
"%s (%02X)\n%s", name.c_str(), id,
304 ?
"Click, then click the room to place."
305 :
"Select an editable room to place sprites.");
308 ImGui::EndDisabled();
316 auto& sprites = room.GetSprites();
317 int selected_sprite_list_index = -1;
322 if (selected_entities.size() == 1 &&
323 selected_entities.front().type == EntityType::Sprite) {
324 selected_sprite_list_index =
325 static_cast<int>(selected_entities.front().index);
330 int sprite_count =
static_cast<int>(sprites.size());
332 sprite_count > 16 ? theme.text_error_red : theme.text_primary;
333 ImGui::TextColored(count_color,
ICON_MD_LIST " Room Sprites: %d/16",
342 if (sprite_count > 16) {
345 if (ImGui::IsItemHovered()) {
347 tr(
"Room exceeds sprite limit (16 max)!\n"
348 "This may cause game crashes."));
352 if (sprites.empty()) {
353 ImGui::TextColored(theme.text_secondary_gray,
359 float available = ImGui::GetContentRegionAvail().y;
360 float list_height = std::max(100.0f, available * 0.4f);
364 ImGui::BeginChild(
"##SpriteList", ImVec2(0, list_height),
true);
365 ImDrawList* draw_list = ImGui::GetWindowDrawList();
366 for (
size_t i = 0; i < sprites.size(); ++i) {
367 const auto& sprite = sprites[i];
368 bool is_selected = (selected_sprite_list_index ==
static_cast<int>(i));
370 ImGui::PushID(
static_cast<int>(i));
372 const float row_height = 24.0f;
373 const ImVec2 p0 = ImGui::GetCursorScreenPos();
374 const bool row_selected =
375 ImGui::Selectable(
"##row", is_selected, 0, ImVec2(0, row_height));
377 absl::StrFormat(
"sprite_row_%zu", i),
378 "Select a sprite in the current dungeon room");
387 const ImVec2 preview_min(p0.x + 2.0f, p0.y + 1.0f);
388 const ImVec2 preview_max(p0.x + 24.0f, p0.y + 23.0f);
389 if (preview.bounds.w > 0 && preview.bounds.h > 0 &&
390 !preview.pixels.empty()) {
392 preview.bounds, preview_min,
393 preview_max, sprite_colors);
397 absl::StrFormat(
"[%02X] %s", sprite.id(),
SpriteName(sprite.id()));
398 if (sprite.key_drop() == 1) {
400 }
else if (sprite.key_drop() == 2) {
403 if (sprite.IsOverlord()) {
408 ImVec2(p0.x + 28.0f, p0.y + 4.0f),
409 ImGui::GetColorU32(is_selected ? theme.text_primary
410 : theme.text_secondary_gray),
413 std::string pos_text =
414 absl::StrFormat(
"(%d,%d) L%d", sprite.x(), sprite.y(), sprite.layer());
415 ImVec2 pos_size = ImGui::CalcTextSize(pos_text.c_str());
416 const float right_margin = 8.0f;
418 std::max(p0.x + 180.0f, p0.x + ImGui::GetContentRegionAvail().x -
419 pos_size.x - right_margin);
420 draw_list->AddText(ImVec2(pos_x, p0.y + 4.0f),
421 ImGui::GetColorU32(theme.text_secondary_gray),
439 absl::StrFormat(
"%02X %s", sprite_id,
SpriteName(sprite_id));
441 const auto lower = [](
unsigned char c) {
442 return static_cast<char>(tolower(c));
444 std::transform(name.begin(), name.end(), name.begin(), lower);
445 std::transform(filter.begin(), filter.end(), filter.begin(), lower);
446 return name.find(filter) != std::string::npos;
const std::string & hack_name() const
bool loaded() const
Check if the manifest has been loaded.
int current_room_id() const
zelda3::GameData * game_data() const
DungeonObjectInteraction & object_interaction()
bool IsObjectInteractionEnabled() const
bool header_read_only() const
const project::YazeProject * project() const
void SetSpritePlacementMode(bool enabled, uint8_t sprite_id=0)
bool IsSpritePlacementActive() const
InteractionCoordinator & entity_coordinator()
Get the interaction coordinator for entity handling.
void SelectEntity(EntityType type, size_t index)
static bool DrawSpritePreview(ImDrawList *draw_list, const std::vector< uint8_t > &preview, const SDL_Rect &bounds, const ImVec2 &box_min, const ImVec2 &box_max, const std::array< SDL_Color, 256 > &color_table, uint8_t transparent_index=kSpritePreviewTransparentIndex)
const std::vector< SelectedEntity > & GetSelectedEntities() const
SpriteInteractionHandler & sprite_handler()
std::function< void(const zelda3::Sprite &) sprite_placed_callback_)
workbench::DungeonBrowserState browser_state_
std::string SpriteName(int sprite_id) const
void SetOpenSelectionInspectorCallback(std::function< void()> callback)
void DrawContent(bool show_room_sprites)
void SetCanvasViewer(DungeonCanvasViewer *viewer)
std::unordered_map< int, CachedSpritePreview > sprite_preview_cache_
std::string GetEditorCategory() const override
Editor category this panel belongs to.
DungeonRoomStore * rooms_
std::string GetDisplayName() const override
Human-readable name shown in menus and title bars.
void Draw(bool *p_open) override
Draw the panel content.
std::string GetWorkflowGroup() const override
Optional workflow group for hack-centric actions.
bool MatchesFilter(int sprite_id) const
DungeonCanvasViewer * canvas_viewer_
void DrawPlacementControls()
std::function< void()> open_selection_inspector_callback_
void DrawSpriteSelector(bool show_room_sprites)
zelda3::SpritePreviewResourceCache sprite_preview_resources_
void SetSpritePlacedCallback(std::function< void(const zelda3::Sprite &)> callback)
const CachedSpritePreview & GetOrCreateSpritePreview(int sprite_id, const zelda3::Room &room)
std::string GetId() const override
Unique identifier for this panel.
uint64_t cached_graphics_revision_
int GetPriority() const override
Get display priority for menu ordering.
SpriteEditorPanel(int *current_room_id, DungeonRoomStore *rooms, DungeonCanvasViewer *canvas_viewer=nullptr)
std::string GetIcon() const override
Material Design icon for this panel.
uint8_t GetSpriteId() const
Get current sprite ID for placement.
std::string GetLabel(ResourceType type, int id) const
Get a label for a resource by type and ID.
const std::array< uint8_t, 0x10000 > & get_gfx_buffer() const
uint64_t graphics_revision() const
static const SpriteOamLayout * GetLayout(uint8_t sprite_id)
Get the OAM layout for a sprite ID.
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
#define ICON_MD_OPEN_IN_NEW
const AgentUITheme & GetTheme()
zelda3::ResourceLabelProvider LabelsForProject(const project::YazeProject *project)
bool DungeonBrowserMatches(std::string_view query, int id, std::string_view name)
void DrawDungeonBrowserControls(DungeonBrowserState &state, std::span< const char *const > categories)
bool DrawDungeonBrowserRow(int id, const std::string &name, bool selected, DrawPreview preview, std::string_view badge={})
void DrawDungeonEntityInspector(DungeonCanvasViewer &viewer, const std::function< void(int, size_t)> &jump_to_reciprocal)
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
std::array< SDL_Color, 256 > BuildDungeonSpriteRenderPalette(const Room &room, const GameData *game_data)
std::vector< uint8_t > pixels
Modern project structure with comprehensive settings consolidation.
core::HackManifest hack_manifest
std::string assets_folder
std::string GetAbsolutePath(const std::string &relative_path) const