yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
sprite_editor_panel.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cctype>
5#include <utility>
6
7#include "absl/strings/str_format.h"
14#include "app/gui/core/icons.h"
16#include "imgui/imgui.h"
17#include "util/i18n/tr.h"
18#include "zelda3/dungeon/room.h"
20
21namespace yaze {
22namespace editor {
23
25 DungeonRoomStore* rooms,
26 DungeonCanvasViewer* canvas_viewer)
27 : current_room_id_(current_room_id),
28 rooms_(rooms),
29 canvas_viewer_(canvas_viewer) {}
30
31std::string SpriteEditorPanel::GetId() const {
32 return "dungeon.sprite_editor";
33}
34
36 return "Sprite Editor";
37}
38
39std::string SpriteEditorPanel::GetIcon() const {
40 return ICON_MD_PERSON;
41}
42
44 return "Dungeon";
45}
46
48 return 65;
49}
50
52 return "Editors";
53}
54
55void SpriteEditorPanel::Draw(bool* /*p_open*/) {
56 DrawContent(true);
57}
58
62
63void SpriteEditorPanel::DrawContent(bool show_room_sprites) {
64 gui::AutoWidgetScope automation_scope("Dungeon/SpriteEditor");
65 if (!current_room_id_ || !rooms_ || *current_room_id_ < 0 ||
66 *current_room_id_ >= static_cast<int>(rooms_->size())) {
67 ImGui::TextDisabled("No room selected");
68 return;
69 }
71 // Standalone selection stays above the library and room list. Both hosts
72 // share these fields and their validation; Workbench owns its inspector.
73 if (show_room_sprites && canvas_viewer_ &&
75 const auto& selected = canvas_viewer_->object_interaction()
78 if (selected.size() == 1 && selected.front().type == EntityType::Sprite) {
80 }
81 }
82 DrawSpriteSelector(show_room_sprites);
83 if (show_room_sprites) {
84 ImGui::Separator();
86 }
87}
88
92
94 std::function<void(const zelda3::Sprite&)> callback) {
95 sprite_placed_callback_ = std::move(callback);
96}
97
99 std::function<void()> callback) {
100 open_selection_inspector_callback_ = std::move(callback);
101}
102
104 const bool placing =
108 if (placing) {
112 .GetSpriteId();
113 ImGui::Text("Placing sprite %02X", selected_sprite_id_);
114 if (ImGui::IsItemHovered())
115 ImGui::SetTooltip("%s", SpriteName(selected_sprite_id_).c_str());
116 ImGui::SameLine();
117 const bool done = ImGui::SmallButton("Done (Esc)");
118 gui::AutoRegisterLastItem("button", "finish_sprite_placement");
119 if (done)
121 } else {
122 ImGui::TextDisabled("Click a sprite, then the canvas.");
123 }
124}
125
126void SpriteEditorPanel::DrawSpriteSelector(bool show_room_sprites) {
127 ImGui::SetNextItemWidth(-1);
128 ImGui::InputTextWithHint("##Search", "Search sprites by name or hex ID...",
130 gui::AutoRegisterLastItem("input_text", "sprite_search");
131
132 const float available = std::max(1.0f, ImGui::GetContentRegionAvail().y);
133 const float list_height =
134 show_room_sprites ? std::max(1.0f, available * 0.55f) : available;
135 const bool can_place =
140 if (ImGui::BeginChild("##SpriteLibrary", ImVec2(0, list_height), true)) {
141 ImGui::BeginDisabled(!can_place);
142 for (int id = 0; id < 256; ++id) {
143 if (!MatchesFilter(id))
144 continue;
145 const std::string name = SpriteName(id);
146 const std::string label = absl::StrFormat("%02X %s", id, name);
147 ImGui::PushID(id);
148 const bool selected =
149 ImGui::Selectable(label.c_str(), selected_sprite_id_ == id);
150 gui::AutoRegisterLastItem("selectable",
151 absl::StrFormat("place_sprite_%02X", id));
152 if (selected) {
155 }
156 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
157 ImGui::SetTooltip("%s (%02X)\n%s", name.c_str(), id,
158 can_place
159 ? "Click, then click the room to place."
160 : "Select an editable room to place sprites.");
161 ImGui::PopID();
162 }
163 ImGui::EndDisabled();
164 }
165 ImGui::EndChild();
166}
167
169 const auto& theme = AgentUI::GetTheme();
170 auto& room = (*rooms_)[*current_room_id_];
171 auto& sprites = room.GetSprites();
172 int selected_sprite_list_index = -1;
173 if (canvas_viewer_) {
174 const auto& selected_entities = canvas_viewer_->object_interaction()
177 if (selected_entities.size() == 1 &&
178 selected_entities.front().type == EntityType::Sprite) {
179 selected_sprite_list_index =
180 static_cast<int>(selected_entities.front().index);
181 }
182 }
183
184 // Sprite count with limit warning
185 int sprite_count = static_cast<int>(sprites.size());
186 ImVec4 count_color =
187 sprite_count > 16 ? theme.text_error_red : theme.text_primary;
188 ImGui::TextColored(count_color, ICON_MD_LIST " Room Sprites: %d/16",
189 sprite_count);
190 if (selected_sprite_list_index >= 0 && open_selection_inspector_callback_) {
191 ImGui::SameLine();
192 if (ImGui::SmallButton(ICON_MD_OPEN_IN_NEW " Inspect Selected")) {
194 }
195 }
196
197 if (sprite_count > 16) {
198 ImGui::SameLine();
199 ImGui::TextColored(theme.text_warning_yellow, ICON_MD_WARNING);
200 if (ImGui::IsItemHovered()) {
201 ImGui::SetTooltip(
202 tr("Room exceeds sprite limit (16 max)!\n"
203 "This may cause game crashes."));
204 }
205 }
206
207 if (sprites.empty()) {
208 ImGui::TextColored(theme.text_secondary_gray,
209 ICON_MD_INFO " No sprites in this room");
210 return;
211 }
212
213 // Split view: list on top, properties below
214 float available = ImGui::GetContentRegionAvail().y;
215 float list_height = std::max(100.0f, available * 0.4f);
216
217 ImGui::BeginChild("##SpriteList", ImVec2(0, list_height), true);
218 for (size_t i = 0; i < sprites.size(); ++i) {
219 const auto& sprite = sprites[i];
220 bool is_selected = (selected_sprite_list_index == static_cast<int>(i));
221
222 ImGui::PushID(static_cast<int>(i));
223
224 // Build display string with indicators
225 std::string label =
226 absl::StrFormat("[%02X] %s", sprite.id(), SpriteName(sprite.id()));
227
228 // Add key drop indicator
229 if (sprite.key_drop() == 1) {
230 label += " " ICON_MD_KEY; // Small key
231 } else if (sprite.key_drop() == 2) {
232 label += " " ICON_MD_VPN_KEY; // Big key
233 }
234
235 // Add overlord indicator
236 if (sprite.IsOverlord()) {
237 label += " " ICON_MD_STAR;
238 }
239
240 const bool row_selected = ImGui::Selectable(label.c_str(), is_selected);
241 gui::AutoRegisterLastItem("selectable",
242 absl::StrFormat("sprite_row_%zu", i),
243 "Select a sprite in the current dungeon room");
244 if (row_selected) {
245 if (canvas_viewer_) {
246 canvas_viewer_->object_interaction().SelectEntity(EntityType::Sprite,
247 i);
248 }
249 }
250
251 // Show position on same line
252 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 80);
253 ImGui::TextColored(theme.text_secondary_gray, tr("(%d,%d) L%d"), sprite.x(),
254 sprite.y(), sprite.layer());
255
256 ImGui::PopID();
257 }
258 ImGui::EndChild();
259}
260
261std::string SpriteEditorPanel::SpriteName(int sprite_id) const {
263 canvas_viewer_ ? canvas_viewer_->project() : nullptr);
264 return labels.GetLabel(zelda3::ResourceType::kSprite, sprite_id);
265}
266
267bool SpriteEditorPanel::MatchesFilter(int sprite_id) const {
268 if (search_filter_[0] == '\0')
269 return true;
270 std::string name =
271 absl::StrFormat("%02X %s", sprite_id, SpriteName(sprite_id));
272 std::string filter = search_filter_;
273 const auto lower = [](unsigned char c) {
274 return static_cast<char>(tolower(c));
275 };
276 std::transform(name.begin(), name.end(), name.begin(), lower);
277 std::transform(filter.begin(), filter.end(), filter.begin(), lower);
278 return name.find(filter) != std::string::npos;
279}
280
281} // namespace editor
282} // namespace yaze
DungeonObjectInteraction & object_interaction()
const project::YazeProject * project() const
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()
std::function< void(const zelda3::Sprite &) sprite_placed_callback_)
std::string SpriteName(int sprite_id) const
void SetOpenSelectionInspectorCallback(std::function< void()> callback)
void DrawContent(bool show_room_sprites)
void SetCanvasViewer(DungeonCanvasViewer *viewer)
std::string GetEditorCategory() const override
Editor category this panel belongs to.
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
std::function< void()> open_selection_inspector_callback_
void DrawSpriteSelector(bool show_room_sprites)
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.
uint8_t GetSpriteId() const
Get current sprite ID for placement.
RAII scope that enables automatic widget registration.
std::string GetLabel(ResourceType type, int id) const
Get a label for a resource by type and ID.
A class for managing sprites in the overworld and underworld.
Definition sprite.h:39
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_WARNING
Definition icons.h:2123
#define ICON_MD_STAR
Definition icons.h:1848
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_PERSON
Definition icons.h:1415
#define ICON_MD_KEY
Definition icons.h:1026
#define ICON_MD_OPEN_IN_NEW
Definition icons.h:1354
#define ICON_MD_VPN_KEY
Definition icons.h:2113
const AgentUITheme & GetTheme()
zelda3::ResourceLabelProvider LabelsForProject(const project::YazeProject *project)
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.
Automatic widget registration helpers for ImGui Test Engine integration.