1#ifndef YAZE_APP_EDITOR_DUNGEON_PANELS_SPRITE_EDITOR_PANEL_H_
2#define YAZE_APP_EDITOR_DUNGEON_PANELS_SPRITE_EDITOR_PANEL_H_
11#include "absl/strings/str_format.h"
21#include "imgui/imgui.h"
50 std::string
GetId()
const override {
return "dungeon.sprite_editor"; }
61 void Draw(
bool* p_open)
override {
64 ImGui::TextDisabled(tr(
"No room data available"));
70 ImGui::TextDisabled(tr(
"No room selected"));
110 ImGui::TextColored(theme.text_secondary_gray,
120 static const char* kCategories[] = {
"All",
"Enemies",
"NPCs",
"Bosses",
122 ImGui::SetNextItemWidth(100);
124 IM_ARRAYSIZE(kCategories));
128 ImGui::SetNextItemWidth(120);
133 float available_height = ImGui::GetContentRegionAvail().y;
135 float reserved_height = 120.0f;
138 std::max(150.0f, std::min(400.0f, available_height - reserved_height));
141 float panel_width = ImGui::GetContentRegionAvail().x;
143 std::max(28.0f, std::min(40.0f, (panel_width - 40.0f) / 8.0f));
145 std::max(1,
static_cast<int>(panel_width / (sprite_size + 6)));
147 ImGui::BeginChild(
"##SpriteGrid", ImVec2(0, grid_height),
true,
148 ImGuiWindowFlags_HorizontalScrollbar);
151 for (
int i = 0; i < 256; ++i) {
163 button_color.x = std::min(1.0f, button_color.x + 0.2f);
164 button_color.y = std::min(1.0f, button_color.y + 0.2f);
165 button_color.z = std::min(1.0f, button_color.z + 0.2f);
170 {{ImGuiCol_Button, button_color},
171 {ImGuiCol_ButtonHovered,
172 ImVec4(std::min(1.0f, button_color.x + 0.1f),
173 std::min(1.0f, button_color.y + 0.1f),
174 std::min(1.0f, button_color.z + 0.1f), 1.0f)},
175 {ImGuiCol_ButtonActive,
176 ImVec4(std::min(1.0f, button_color.x + 0.2f),
177 std::min(1.0f, button_color.y + 0.2f),
178 std::min(1.0f, button_color.z + 0.2f), 1.0f)}});
182 std::string label = absl::StrFormat(
"%s\n%02X", icon, i);
183 if (ImGui::Button(label.c_str(), ImVec2(sprite_size, sprite_size))) {
193 if (ImGui::IsItemHovered()) {
196 tr(
"%s (0x%02X)\n[%s]\nClick to select for placement"),
202 ImVec2 min = ImGui::GetItemRectMin();
203 ImVec2 max = ImGui::GetItemRectMax();
205 ImGui::ColorConvertFloat4ToU32(theme.dungeon_selection_primary);
206 ImGui::GetWindowDrawList()->AddRect(min, max, sel_color, 0.0f, 0, 2.0f);
212 if (col < items_per_row) {
225 auto& sprites = room.GetSprites();
226 int selected_sprite_list_index = -1;
231 if (selected_entities.size() == 1 &&
233 selected_sprite_list_index =
234 static_cast<int>(selected_entities.front().index);
239 int sprite_count =
static_cast<int>(sprites.size());
241 sprite_count > 16 ? theme.text_error_red : theme.text_primary;
242 ImGui::TextColored(count_color,
ICON_MD_LIST " Room Sprites: %d/16",
251 if (sprite_count > 16) {
254 if (ImGui::IsItemHovered()) {
256 tr(
"Room exceeds sprite limit (16 max)!\n"
257 "This may cause game crashes."));
261 if (sprites.empty()) {
262 ImGui::TextColored(theme.text_secondary_gray,
267 if (selected_sprite_list_index >= 0 &&
268 selected_sprite_list_index <
static_cast<int>(sprites.size()) &&
270 const auto& sprite = sprites[selected_sprite_list_index];
271 int sprite_x = sprite.x();
272 int sprite_y = sprite.y();
273 ImGui::TextUnformatted(tr(
"Selected position"));
275 ImGui::SetNextItemWidth(64.0f);
277 "##SelectedSpriteX", ImGuiDataType_S32, &sprite_x,
"%d", 0,
278 {
reinterpret_cast<uintptr_t
>(
rooms_),
280 static_cast<uint64_t
>(selected_sprite_list_index)});
282 "Selected sprite X coordinate");
284 ImGui::SetNextItemWidth(64.0f);
286 "##SelectedSpriteY", ImGuiDataType_S32, &sprite_y,
"%d", 0,
287 {
reinterpret_cast<uintptr_t
>(
rooms_),
289 static_cast<uint64_t
>(selected_sprite_list_index)});
291 "Selected sprite Y coordinate");
292 if (x_changed || y_changed) {
293 const int clamped_x =
295 const int clamped_y =
300 handler.
NudgeSelected(clamped_x -
static_cast<int>(sprite.x()),
301 clamped_y -
static_cast<int>(sprite.y()));
306 float available = ImGui::GetContentRegionAvail().y;
307 float list_height = std::max(100.0f, available * 0.4f);
309 ImGui::BeginChild(
"##SpriteList", ImVec2(0, list_height),
true);
310 for (
size_t i = 0; i < sprites.size(); ++i) {
311 const auto& sprite = sprites[i];
312 bool is_selected = (selected_sprite_list_index ==
static_cast<int>(i));
314 ImGui::PushID(
static_cast<int>(i));
317 std::string label = absl::StrFormat(
321 if (sprite.key_drop() == 1) {
323 }
else if (sprite.key_drop() == 2) {
328 if (sprite.IsOverlord()) {
332 const bool row_selected = ImGui::Selectable(label.c_str(), is_selected);
334 absl::StrFormat(
"sprite_row_%zu", i),
335 "Select a sprite in the current dungeon room");
344 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 80);
345 ImGui::TextColored(theme.text_secondary_gray, tr(
"(%d,%d) L%d"),
346 sprite.x(), sprite.y(), sprite.layer());
357 bool is_enemy = (sprite_id >= 0x09 && sprite_id <= 0x7F);
358 bool is_npc = (sprite_id >= 0x80 && sprite_id <= 0xBF);
359 bool is_boss = (sprite_id >= 0xC0 && sprite_id <= 0xD8);
360 bool is_item = (sprite_id >= 0xD9 && sprite_id <= 0xFF);
376 std::string name_lower = name;
378 for (
auto& c : name_lower)
379 c =
static_cast<char>(tolower(c));
380 for (
auto& c : filter_lower)
381 c =
static_cast<char>(tolower(c));
382 if (name_lower.find(filter_lower) == std::string::npos) {
392 if (sprite_id >= 0xC0 && sprite_id <= 0xD8) {
394 }
else if (sprite_id >= 0x80 && sprite_id <= 0xBF) {
396 }
else if (sprite_id >= 0xD9) {
404 if (sprite_id >= 0xC0 && sprite_id <= 0xD8) {
406 }
else if (sprite_id >= 0x80 && sprite_id <= 0xBF) {
408 }
else if (sprite_id >= 0xD9) {
415 if (sprite_id >= 0xC0 && sprite_id <= 0xD8) {
417 }
else if (sprite_id >= 0x80 && sprite_id <= 0xBF) {
419 }
else if (sprite_id >= 0xD9) {
DungeonObjectInteraction & object_interaction()
void SetSpritePlacementMode(bool enabled, uint8_t sprite_id=0)
InteractionCoordinator & entity_coordinator()
Get the interaction coordinator for entity handling.
void SelectEntity(EntityType type, size_t index)
const std::vector< SelectedEntity > & GetSelectedEntities() const
SpriteInteractionHandler & sprite_handler()
WindowContent for placing and managing dungeon sprites.
std::function< void(const zelda3::Sprite &) sprite_placed_callback_)
void DrawSpriteSelector()
void SetOpenSelectionInspectorCallback(std::function< void()> callback)
bool MatchesFilter(int sprite_id)
void SetCanvasViewer(DungeonCanvasViewer *viewer)
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.
ImVec4 GetSpriteTypeColor(int sprite_id, const AgentUITheme &theme)
const char * GetSpriteCategoryName(int sprite_id)
const char * GetSpriteTypeIcon(int sprite_id)
DungeonCanvasViewer * canvas_viewer_
void DrawPlacementControls()
std::function< void()> open_selection_inspector_callback_
void SetSpritePlacedCallback(std::function< void(const zelda3::Sprite &)> callback)
std::string GetId() const override
Unique identifier for this panel.
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.
bool NudgeSelected(int delta_x, int delta_y)
Base interface for all logical window content components.
RAII guard for ImGui style colors.
A class for managing sprites in the overworld and underworld.
#define ICON_MD_DANGEROUS
#define ICON_MD_PEST_CONTROL
#define ICON_MD_OPEN_IN_NEW
const AgentUITheme & GetTheme()
constexpr int kSpriteGridMax
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
bool InputScalarDeferred(const char *label, ImGuiDataType data_type, void *data, const char *format, ImGuiInputTextFlags flags, InputScalarTargetIdentity target_identity)
const char * ResolveSpriteName(uint16_t id)
Centralized theme colors for Agent UI components.
ImVec4 dungeon_sprite_layer0
ImVec4 dungeon_object_chest
ImVec4 dungeon_sprite_layer1