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"; }
60 void Draw(
bool* p_open)
override {
63 ImGui::TextDisabled(tr(
"No room data available"));
69 ImGui::TextDisabled(tr(
"No room selected"));
109 ImGui::TextColored(theme.text_secondary_gray,
119 static const char* kCategories[] = {
"All",
"Enemies",
"NPCs",
"Bosses",
121 ImGui::SetNextItemWidth(100);
123 IM_ARRAYSIZE(kCategories));
127 ImGui::SetNextItemWidth(120);
132 float available_height = ImGui::GetContentRegionAvail().y;
134 float reserved_height = 120.0f;
137 std::max(150.0f, std::min(400.0f, available_height - reserved_height));
140 float panel_width = ImGui::GetContentRegionAvail().x;
142 std::max(28.0f, std::min(40.0f, (panel_width - 40.0f) / 8.0f));
144 std::max(1,
static_cast<int>(panel_width / (sprite_size + 6)));
146 ImGui::BeginChild(
"##SpriteGrid", ImVec2(0, grid_height),
true,
147 ImGuiWindowFlags_HorizontalScrollbar);
150 for (
int i = 0; i < 256; ++i) {
162 button_color.x = std::min(1.0f, button_color.x + 0.2f);
163 button_color.y = std::min(1.0f, button_color.y + 0.2f);
164 button_color.z = std::min(1.0f, button_color.z + 0.2f);
169 {{ImGuiCol_Button, button_color},
170 {ImGuiCol_ButtonHovered,
171 ImVec4(std::min(1.0f, button_color.x + 0.1f),
172 std::min(1.0f, button_color.y + 0.1f),
173 std::min(1.0f, button_color.z + 0.1f), 1.0f)},
174 {ImGuiCol_ButtonActive,
175 ImVec4(std::min(1.0f, button_color.x + 0.2f),
176 std::min(1.0f, button_color.y + 0.2f),
177 std::min(1.0f, button_color.z + 0.2f), 1.0f)}});
181 std::string label = absl::StrFormat(
"%s\n%02X", icon, i);
182 if (ImGui::Button(label.c_str(), ImVec2(sprite_size, sprite_size))) {
192 if (ImGui::IsItemHovered()) {
195 tr(
"%s (0x%02X)\n[%s]\nClick to select for placement"),
201 ImVec2 min = ImGui::GetItemRectMin();
202 ImVec2 max = ImGui::GetItemRectMax();
204 ImGui::ColorConvertFloat4ToU32(theme.dungeon_selection_primary);
205 ImGui::GetWindowDrawList()->AddRect(min, max, sel_color, 0.0f, 0, 2.0f);
211 if (col < items_per_row) {
224 auto& sprites = room.GetSprites();
225 int selected_sprite_list_index = -1;
230 if (selected_entities.size() == 1 &&
232 selected_sprite_list_index =
233 static_cast<int>(selected_entities.front().index);
238 int sprite_count =
static_cast<int>(sprites.size());
240 sprite_count > 16 ? theme.text_error_red : theme.text_primary;
241 ImGui::TextColored(count_color,
ICON_MD_LIST " Room Sprites: %d/16",
250 if (sprite_count > 16) {
253 if (ImGui::IsItemHovered()) {
255 tr(
"Room exceeds sprite limit (16 max)!\n"
256 "This may cause game crashes."));
260 if (sprites.empty()) {
261 ImGui::TextColored(theme.text_secondary_gray,
266 if (selected_sprite_list_index >= 0 &&
267 selected_sprite_list_index <
static_cast<int>(sprites.size()) &&
269 const auto& sprite = sprites[selected_sprite_list_index];
270 int sprite_x = sprite.x();
271 int sprite_y = sprite.y();
272 ImGui::TextUnformatted(tr(
"Selected position"));
274 ImGui::SetNextItemWidth(64.0f);
276 "##SelectedSpriteX", ImGuiDataType_S32, &sprite_x,
"%d", 0,
277 {
reinterpret_cast<uintptr_t
>(
rooms_),
279 static_cast<uint64_t
>(selected_sprite_list_index)});
281 "Selected sprite X coordinate");
283 ImGui::SetNextItemWidth(64.0f);
285 "##SelectedSpriteY", ImGuiDataType_S32, &sprite_y,
"%d", 0,
286 {
reinterpret_cast<uintptr_t
>(
rooms_),
288 static_cast<uint64_t
>(selected_sprite_list_index)});
290 "Selected sprite Y coordinate");
291 if (x_changed || y_changed) {
292 const int clamped_x =
294 const int clamped_y =
299 handler.
NudgeSelected(clamped_x -
static_cast<int>(sprite.x()),
300 clamped_y -
static_cast<int>(sprite.y()));
305 float available = ImGui::GetContentRegionAvail().y;
306 float list_height = std::max(100.0f, available * 0.4f);
308 ImGui::BeginChild(
"##SpriteList", ImVec2(0, list_height),
true);
309 for (
size_t i = 0; i < sprites.size(); ++i) {
310 const auto& sprite = sprites[i];
311 bool is_selected = (selected_sprite_list_index ==
static_cast<int>(i));
313 ImGui::PushID(
static_cast<int>(i));
316 std::string label = absl::StrFormat(
320 if (sprite.key_drop() == 1) {
322 }
else if (sprite.key_drop() == 2) {
327 if (sprite.IsOverlord()) {
331 const bool row_selected = ImGui::Selectable(label.c_str(), is_selected);
333 absl::StrFormat(
"sprite_row_%zu", i),
334 "Select a sprite in the current dungeon room");
343 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 80);
344 ImGui::TextColored(theme.text_secondary_gray, tr(
"(%d,%d) L%d"),
345 sprite.x(), sprite.y(), sprite.layer());
356 bool is_enemy = (sprite_id >= 0x09 && sprite_id <= 0x7F);
357 bool is_npc = (sprite_id >= 0x80 && sprite_id <= 0xBF);
358 bool is_boss = (sprite_id >= 0xC0 && sprite_id <= 0xD8);
359 bool is_item = (sprite_id >= 0xD9 && sprite_id <= 0xFF);
375 std::string name_lower = name;
377 for (
auto& c : name_lower)
378 c =
static_cast<char>(tolower(c));
379 for (
auto& c : filter_lower)
380 c =
static_cast<char>(tolower(c));
381 if (name_lower.find(filter_lower) == std::string::npos) {
391 if (sprite_id >= 0xC0 && sprite_id <= 0xD8) {
393 }
else if (sprite_id >= 0x80 && sprite_id <= 0xBF) {
395 }
else if (sprite_id >= 0xD9) {
403 if (sprite_id >= 0xC0 && sprite_id <= 0xD8) {
405 }
else if (sprite_id >= 0x80 && sprite_id <= 0xBF) {
407 }
else if (sprite_id >= 0xD9) {
414 if (sprite_id >= 0xC0 && sprite_id <= 0xD8) {
416 }
else if (sprite_id >= 0x80 && sprite_id <= 0xBF) {
418 }
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.
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