yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
item_editor_panel.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstddef>
5#include <cstdint>
6#include <string>
7
8#include "absl/strings/str_format.h"
12#include "app/gui/core/input.h"
14#include "util/i18n/tr.h"
15
16namespace yaze {
17namespace editor {
18
19void ItemEditorPanel::Draw(bool* /*p_open*/) {
20 DrawContent(true);
21}
22
24 gui::AutoWidgetScope scope("Dungeon/ItemEditor");
25 if (!current_room_id_ || !rooms_ || *current_room_id_ < 0 ||
26 *current_room_id_ >= static_cast<int>(rooms_->size()))
27 return;
28 if (HasMatchingViewer() &&
32 constexpr const char* categories[] = {"All items", "Used here"};
34 auto& room = (*rooms_)[*current_room_id_];
35 std::vector<std::pair<int, std::string>> entries;
36 for (int id = 0; id < static_cast<int>(kDungeonPotItemCount); ++id) {
37 if (browser_state_.category == 1 &&
38 std::none_of(room.GetPotItems().begin(), room.GetPotItems().end(),
39 [id](const auto& item) { return item.item == id; }))
40 continue;
41 const std::string name = GetDungeonPotItemName(static_cast<uint8_t>(id));
43 entries.emplace_back(id, name);
44 }
45 if (browser_state_.sort == 1)
46 std::stable_sort(entries.begin(), entries.end(),
47 [](const auto& a, const auto& b) {
48 return workbench::DungeonBrowserFold(a.second) <
49 workbench::DungeonBrowserFold(b.second);
50 });
52 room, canvas_viewer_ ? canvas_viewer_->game_data() : nullptr);
53 const bool can_place = CanEditRoom();
54 if (ImGui::BeginChild("##ItemLibrary", ImVec2(0, 0), false)) {
55 ImGui::BeginDisabled(!can_place);
56 for (const auto& [id, name] : entries) {
57 ImGui::PushID(id);
58 const bool picked = workbench::DrawDungeonBrowserRow(
59 id, name, selected_item_id_ == id,
60 [&, id = id](ImVec2 pos, ImVec2 size) {
61 const auto& preview =
62 GetOrCreateItemPreview(static_cast<uint8_t>(id), room);
64 ImGui::GetWindowDrawList(), preview.pixels, preview.bounds, pos,
65 ImVec2(pos.x + size.x, pos.y + size.y), colors);
66 });
67 gui::AutoRegisterLastItem("selectable",
68 absl::StrFormat("place_item_%02X", id));
69 if (picked && can_place) {
72 true, static_cast<uint8_t>(id));
73 }
74 ImGui::PopID();
75 }
76 ImGui::EndDisabled();
77 if (entries.empty())
78 ImGui::TextDisabled("No matches");
79 }
80 ImGui::EndChild();
81}
82
83void ItemEditorPanel::DrawContent(bool show_room_items) {
84 gui::AutoWidgetScope automation_scope("Dungeon/ItemEditor");
85 if (!current_room_id_ || !rooms_) {
86 ImGui::TextDisabled(tr("No room data available"));
87 return;
88 }
89
90 if (*current_room_id_ < 0 ||
91 *current_room_id_ >= static_cast<int>(rooms_->size())) {
92 ImGui::TextDisabled(tr("No room selected"));
93 return;
94 }
95
98 ImGui::Separator();
99 DrawItemSelector(show_room_items);
100 if (show_room_items) {
101 ImGui::Separator();
103 }
104}
105
109 *current_room_id_ >= static_cast<int>(rooms_->size()) ||
111 return false;
112 }
113 const auto* context = canvas_viewer_->object_interaction()
115 .tile_handler()
116 .context();
117 return context && context->current_room_id == *current_room_id_;
118}
119
124
126 const auto& theme = AgentUI::GetTheme();
127 // Follow canvas cancellation and selection instead of a stale panel mode.
131 if (placement_mode_) {
134 .item_handler()
135 .GetItemId();
136 const char* item_name =
137 GetDungeonPotItemName(static_cast<uint8_t>(selected_item_id_));
138 ImGui::TextColored(theme.status_warning,
139 ICON_MD_PLACE " Placing: %s (0x%02X)", item_name,
141 if (ImGui::SmallButton(ICON_MD_CANCEL " Cancel")) {
142 placement_mode_ = false;
143 if (canvas_viewer_) {
145 }
146 }
147 } else {
148 ImGui::TextColored(theme.text_secondary_gray,
149 ICON_MD_INFO " Select an item to place");
150 }
151}
152
155 const zelda3::Room& room) {
157 item_preview_cache_.clear();
159 }
160 auto it = item_preview_cache_.find(item_id);
161 if (it != item_preview_cache_.end()) {
162 return it->second;
163 }
164
165 CachedItemPreview cached;
167 cached.bounds);
168 auto [inserted_it, _] =
169 item_preview_cache_.emplace(item_id, std::move(cached));
170 return inserted_it->second;
171}
172
173void ItemEditorPanel::DrawItemSelector(bool show_room_items) {
174 const bool can_edit = CanEditRoom();
175 const auto& theme = AgentUI::GetTheme();
176 auto& room = (*rooms_)[*current_room_id_];
177 const auto sprite_colors = zelda3::BuildDungeonSpriteRenderPalette(
178 room, canvas_viewer_ ? canvas_viewer_->game_data() : nullptr);
179
180 ImGui::Text(ICON_MD_INVENTORY " Select Item:");
181
182 // Item grid with responsive sizing
183 float available_height = ImGui::GetContentRegionAvail().y;
184 float reserved_height = 120.0f;
185 const float grid_height =
186 show_room_items
187 ? std::max(150.0f,
188 std::min(400.0f, available_height - reserved_height))
189 : std::max(1.0f, available_height);
190
191 // Responsive item size based on panel width
192 float panel_width = ImGui::GetContentRegionAvail().x;
193 float item_size =
194 std::max(36.0f, std::min(48.0f, (panel_width - 32.0f) / 6.0f));
195 float spacing = 4.0f;
196 int items_per_row = std::max(
197 1, static_cast<int>((panel_width + spacing) / (item_size + spacing)));
198
199 ImGui::BeginChild("##ItemGrid", ImVec2(0, grid_height), true,
200 ImGuiWindowFlags_HorizontalScrollbar);
201 ImDrawList* draw_list = ImGui::GetWindowDrawList();
202 ImGui::BeginDisabled(!can_edit);
203
204 int col = 0;
205 for (size_t i = 0; i < kDungeonPotItemCount; ++i) {
206 bool is_selected = (selected_item_id_ == static_cast<int>(i));
207
208 ImGui::PushID(static_cast<int>(i));
209
210 const ImVec2 p0 = ImGui::GetCursorScreenPos();
211 if (is_selected) {
212 ImGui::PushStyleColor(ImGuiCol_Button, theme.dungeon_selection_primary);
213 }
214 const bool picked =
215 ImGui::Button("##item_btn", ImVec2(item_size, item_size));
216 gui::AutoRegisterLastItem("button", absl::StrFormat("place_item_%02X", i));
217 if (is_selected) {
218 ImGui::PopStyleColor();
219 }
220 if (picked && can_edit) {
221 selected_item_id_ = static_cast<int>(i);
222 placement_mode_ = true;
223 if (canvas_viewer_) {
225 true, static_cast<uint8_t>(i));
226 }
227 }
228
229 const auto& preview = GetOrCreateItemPreview(static_cast<uint8_t>(i), room);
230 const ImVec2 preview_min(p0.x + 3.0f, p0.y + 2.0f);
231 const ImVec2 preview_max(p0.x + item_size - 3.0f, p0.y + item_size - 12.0f);
232 bool drew_preview = false;
233 if (preview.bounds.w > 0 && preview.bounds.h > 0 &&
234 !preview.pixels.empty()) {
236 draw_list, preview.pixels, preview.bounds, preview_min, preview_max,
237 sprite_colors);
238 }
239 if (!drew_preview) {
240 const char* icon = GetItemTypeIcon(static_cast<int>(i));
241 const ImVec2 icon_size = ImGui::CalcTextSize(icon);
242 draw_list->AddText(
243 ImVec2(preview_min.x +
244 (preview_max.x - preview_min.x - icon_size.x) * 0.5f,
245 preview_min.y +
246 (preview_max.y - preview_min.y - icon_size.y) * 0.5f),
247 ImGui::GetColorU32(theme.text_secondary_gray), icon);
248 }
249
250 const std::string id_text = absl::StrFormat("%02X", i);
251 const ImVec2 id_size = ImGui::CalcTextSize(id_text.c_str());
252 draw_list->AddText(
253 ImVec2(p0.x + (item_size - id_size.x) * 0.5f,
254 p0.y + item_size - id_size.y - 1.0f),
255 ImGui::GetColorU32(is_selected ? theme.text_primary
256 : theme.text_secondary_gray),
257 id_text.c_str());
258
259 ImGui::LogText("%s (0x%02X)\n",
260 GetDungeonPotItemName(static_cast<uint8_t>(i)),
261 static_cast<int>(i));
262
263 if (ImGui::IsItemHovered()) {
264 ImGui::SetTooltip(tr("%s (0x%02X)\nClick to select for placement"),
265 GetDungeonPotItemName(static_cast<uint8_t>(i)),
266 static_cast<int>(i));
267 }
268
269 if (is_selected) {
270 ImVec2 min = p0;
271 ImVec2 max = ImVec2(p0.x + item_size, p0.y + item_size);
272 ImU32 sel_color =
273 ImGui::ColorConvertFloat4ToU32(theme.dungeon_selection_primary);
274 draw_list->AddRect(min, max, sel_color, 2.0f, 0, 2.0f);
275 }
276
277 ImGui::PopID();
278
279 col++;
280 if (col < items_per_row) {
281 ImGui::SameLine(0, spacing);
282 } else {
283 col = 0;
284 }
285 }
286
287 ImGui::EndDisabled();
288 ImGui::EndChild();
289}
290
292 const auto& theme = AgentUI::GetTheme();
293 auto& room = (*rooms_)[*current_room_id_];
294 const auto& items = room.GetPotItems();
295 int selected_item_index = -1;
296 if (HasMatchingViewer() &&
298 const auto selected_entity =
300 if (selected_entity.type == EntityType::Item) {
301 selected_item_index = static_cast<int>(selected_entity.index);
302 }
303 }
304
305 ImGui::Text(ICON_MD_LIST " Room Items (%zu):", items.size());
306 if (selected_item_index >= 0 && open_selection_inspector_callback_) {
307 ImGui::SameLine();
308 if (ImGui::SmallButton(ICON_MD_OPEN_IN_NEW " Inspect Selected")) {
310 }
311 }
312
313 if (items.empty()) {
314 ImGui::TextColored(theme.text_secondary_gray,
315 ICON_MD_INFO " No items in this room");
316 return;
317 }
318
319 if (selected_item_index >= 0 &&
320 selected_item_index < static_cast<int>(items.size()) && canvas_viewer_) {
321 const bool can_edit = CanEditRoom();
322 ImGui::BeginDisabled(!can_edit);
323 uint8_t raw_item_type = items[selected_item_index].item;
324 ImGui::AlignTextToFramePadding();
325 ImGui::TextUnformatted(tr("Selected raw type"));
326 ImGui::SameLine();
327 ImGui::SetNextItemWidth(72.0f);
328 const bool type_changed = gui::InputScalarDeferred(
329 "##SelectedItemRawType", ImGuiDataType_U8, &raw_item_type, "%02X",
330 ImGuiInputTextFlags_CharsHexadecimal,
331 {reinterpret_cast<uintptr_t>(rooms_),
332 static_cast<uint64_t>(*current_room_id_),
333 static_cast<uint64_t>(selected_item_index)});
334 gui::AutoRegisterLastItem("input_scalar", "selected_item_raw_type",
335 "Selected pot item raw type byte");
336 ImGui::EndDisabled();
337 if (type_changed && can_edit) {
340 .item_handler()
341 .MutateItemType(selected_item_index, raw_item_type);
342 }
343 }
344
345 float list_height = std::max(100.0f, ImGui::GetContentRegionAvail().y);
346 const auto sprite_colors = zelda3::BuildDungeonSpriteRenderPalette(
347 room, canvas_viewer_ ? canvas_viewer_->game_data() : nullptr);
348 ImGui::BeginChild("##ItemList", ImVec2(0, list_height), true);
349 ImDrawList* draw_list = ImGui::GetWindowDrawList();
350 for (size_t i = 0; i < items.size(); ++i) {
351 const auto& item = items[i];
352
353 ImGui::PushID(static_cast<int>(i));
354
355 const float row_height = 24.0f;
356 const ImVec2 p0 = ImGui::GetCursorScreenPos();
357 const bool is_selected = (selected_item_index == static_cast<int>(i));
358 const bool row_selected =
359 ImGui::Selectable("##row", is_selected, 0, ImVec2(0, row_height));
360 gui::AutoRegisterLastItem("selectable", absl::StrFormat("item_row_%zu", i),
361 "Select a pot item in the current dungeon room");
362 if (row_selected && HasMatchingViewer()) {
363 canvas_viewer_->object_interaction().SelectEntity(EntityType::Item, i);
364 }
365
366 const auto& preview = GetOrCreateItemPreview(item.item, room);
367 const ImVec2 preview_min(p0.x + 2.0f, p0.y + 1.0f);
368 const ImVec2 preview_max(p0.x + 24.0f, p0.y + 23.0f);
369 bool drew_preview = false;
370 if (preview.bounds.w > 0 && preview.bounds.h > 0 &&
371 !preview.pixels.empty()) {
373 draw_list, preview.pixels, preview.bounds, preview_min, preview_max,
374 sprite_colors);
375 }
376 if (!drew_preview) {
377 const char* icon = GetItemTypeIcon(item.item);
378 draw_list->AddText(ImVec2(preview_min.x + 4.0f, preview_min.y + 3.0f),
379 ImGui::GetColorU32(theme.text_secondary_gray), icon);
380 }
381
382 const char* item_name = GetDungeonPotItemName(item.item);
383 const std::string row_label =
384 absl::StrFormat("[%zu] %s (0x%02X)", i, item_name, item.item);
385 draw_list->AddText(
386 ImVec2(p0.x + 28.0f, p0.y + 4.0f),
387 ImGui::GetColorU32(is_selected ? theme.text_primary
388 : theme.text_secondary_gray),
389 row_label.c_str());
390
391 const std::string coords_label =
392 absl::StrFormat("@ (%d,%d)", item.GetTileX(), item.GetTileY());
393 const ImVec2 coords_size = ImGui::CalcTextSize(coords_label.c_str());
394 draw_list->AddText(
395 ImVec2(ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x -
396 coords_size.x - 4.0f,
397 p0.y + 4.0f),
398 ImGui::GetColorU32(theme.text_secondary_gray), coords_label.c_str());
399
400 ImGui::PopID();
401 }
402 ImGui::EndChild();
403}
404
406 const AgentUITheme& theme) const {
407 if (item_id == 0 || item_id == 22) {
408 return theme.dungeon_object_default; // Gray for "Nothing"
409 } else if (item_id >= 1 && item_id <= 7) {
410 return theme.dungeon_sprite_layer0; // Green for rupees/items
411 } else if (item_id == 8) {
412 return theme.dungeon_object_chest; // Gold for key
413 } else if (item_id >= 15 && item_id <= 17) {
414 return theme.status_error; // Red for enemies
415 } else if (item_id >= 23 && item_id <= 27) {
416 return theme.dungeon_object_stairs; // Yellow for special
417 }
418 return theme.dungeon_object_pot; // Pot color for default
419}
420
421const char* ItemEditorPanel::GetItemTypeIcon(int item_id) const {
422 if (item_id == 0 || item_id == 22) {
423 return ICON_MD_BLOCK; // Nothing
424 } else if (item_id == 1 || item_id == 7) {
425 return ICON_MD_MONETIZATION_ON; // Rupees (green/blue)
426 } else if (item_id == 4 || item_id == 6 || item_id == 11 || item_id == 19 ||
427 item_id == 21) {
428 return ICON_MD_FAVORITE; // Hearts
429 } else if (item_id == 8) {
430 return ICON_MD_KEY; // Key
431 } else if (item_id == 5 || item_id == 10) {
432 return ICON_MD_CIRCLE; // Bombs
433 } else if (item_id == 9) {
434 return ICON_MD_ARROW_UPWARD; // Arrows
435 } else if (item_id == 12 || item_id == 13) {
436 return ICON_MD_AUTO_AWESOME; // Magic
437 } else if (item_id == 14) {
438 return ICON_MD_EGG; // Cucco
439 } else if (item_id >= 15 && item_id <= 17) {
440 return ICON_MD_PERSON; // Soldiers
441 } else if (item_id == 18) {
442 return ICON_MD_WARNING; // Landmine
443 } else if (item_id == 20) {
444 return ICON_MD_FLUTTER_DASH; // Fairy
445 } else if (item_id == 23) {
446 return ICON_MD_TERRAIN; // Hole
447 } else if (item_id == 24) {
448 return ICON_MD_SWAP_HORIZ; // Warp
449 } else if (item_id == 25) {
450 return ICON_MD_STAIRS; // Staircase
451 } else if (item_id == 26) {
452 return ICON_MD_BROKEN_IMAGE; // Bombable
453 } else if (item_id == 27) {
454 return ICON_MD_TOGGLE_ON; // Switch
455 } else if (item_id == 2) {
456 return ICON_MD_LANDSCAPE; // Rock
457 } else if (item_id == 3) {
458 return ICON_MD_BUG_REPORT; // Bee
459 }
460 return ICON_MD_HELP; // Unknown
461}
462
463} // namespace editor
464} // namespace yaze
InteractionContext * context() const
Get the interaction context.
DungeonObjectInteraction & object_interaction()
void SetItemPlacementMode(bool enabled, uint8_t item_id=0)
InteractionCoordinator & entity_coordinator()
Get the interaction coordinator for entity handling.
void SelectEntity(EntityType type, size_t index)
static bool BuildPotItemPreview(uint8_t item_id, const zelda3::Room &room, std::vector< uint8_t > &out_pixels, SDL_Rect &out_bounds)
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)
void DrawContent(bool show_room_items)
std::unordered_map< uint8_t, CachedItemPreview > item_preview_cache_
void DrawItemSelector(bool show_room_items)
DungeonCanvasViewer * canvas_viewer_
const char * GetItemTypeIcon(int item_id) const
ImVec4 GetItemTypeColor(int item_id, const AgentUITheme &theme) const
workbench::DungeonBrowserState browser_state_
const CachedItemPreview & GetOrCreateItemPreview(uint8_t item_id, const zelda3::Room &room)
std::function< void()> open_selection_inspector_callback_
void Draw(bool *p_open) override
Draw the panel content.
uint8_t GetItemId() const
Get current item ID for placement.
bool MutateItemType(size_t index, uint8_t new_type)
Change one pot item's raw type byte in place.
RAII scope that enables automatic widget registration.
uint64_t graphics_revision() const
Definition room.h:1125
#define ICON_MD_BLOCK
Definition icons.h:269
#define ICON_MD_FLUTTER_DASH
Definition icons.h:805
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_CANCEL
Definition icons.h:364
#define ICON_MD_LANDSCAPE
Definition icons.h:1059
#define ICON_MD_WARNING
Definition icons.h:2123
#define ICON_MD_TERRAIN
Definition icons.h:1952
#define ICON_MD_PLACE
Definition icons.h:1477
#define ICON_MD_SWAP_HORIZ
Definition icons.h:1896
#define ICON_MD_CIRCLE
Definition icons.h:411
#define ICON_MD_STAIRS
Definition icons.h:1847
#define ICON_MD_AUTO_AWESOME
Definition icons.h:214
#define ICON_MD_BUG_REPORT
Definition icons.h:327
#define ICON_MD_TOGGLE_ON
Definition icons.h:1994
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_BROKEN_IMAGE
Definition icons.h:320
#define ICON_MD_EGG
Definition icons.h:654
#define ICON_MD_INVENTORY
Definition icons.h:1011
#define ICON_MD_ARROW_UPWARD
Definition icons.h:189
#define ICON_MD_PERSON
Definition icons.h:1415
#define ICON_MD_FAVORITE
Definition icons.h:727
#define ICON_MD_KEY
Definition icons.h:1026
#define ICON_MD_OPEN_IN_NEW
Definition icons.h:1354
#define ICON_MD_HELP
Definition icons.h:933
#define ICON_MD_MONETIZATION_ON
Definition icons.h:1229
const AgentUITheme & GetTheme()
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={})
const char * GetDungeonPotItemName(uint8_t item_id)
constexpr size_t kDungeonPotItemCount
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)
Definition input.cc:360
std::array< SDL_Color, 256 > BuildDungeonSpriteRenderPalette(const Room &room, const GameData *game_data)
Definition room.cc:200
Centralized theme colors for Agent UI components.
Definition agent_theme.h:19
Automatic widget registration helpers for ImGui Test Engine integration.