yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
item_editor_panel.h
Go to the documentation of this file.
1#ifndef YAZE_APP_EDITOR_DUNGEON_PANELS_ITEM_EDITOR_PANEL_H_
2#define YAZE_APP_EDITOR_DUNGEON_PANELS_ITEM_EDITOR_PANEL_H_
3
4#include <array>
5#include <cstdint>
6#include <functional>
7#include <string>
8#include "util/i18n/tr.h"
9
10#include "absl/strings/str_format.h"
17#include "app/gui/core/icons.h"
18#include "app/gui/core/input.h"
20#include "imgui/imgui.h"
21#include "zelda3/dungeon/room.h"
22
23namespace yaze {
24namespace editor {
25
38 public:
39 ItemEditorPanel(int* current_room_id, DungeonRoomStore* rooms,
40 DungeonCanvasViewer* canvas_viewer = nullptr)
41 : current_room_id_(current_room_id),
42 rooms_(rooms),
43 canvas_viewer_(canvas_viewer) {}
44
45 // ==========================================================================
46 // WindowContent Identity
47 // ==========================================================================
48
49 std::string GetId() const override { return "dungeon.item_editor"; }
50 std::string GetDisplayName() const override { return "Item Editor"; }
51 std::string GetIcon() const override { return ICON_MD_INVENTORY; }
52 std::string GetEditorCategory() const override { return "Dungeon"; }
53 int GetPriority() const override { return 66; }
54 std::string GetWorkflowGroup() const override { return "Editors"; }
55
56 // ==========================================================================
57 // WindowContent Drawing
58 // ==========================================================================
59
60 void Draw(bool* /*p_open*/) override { DrawContent(true); }
61 void DrawInWorkbench() { DrawContent(false); }
62
63 // ==========================================================================
64 // Panel-Specific Methods
65 // ==========================================================================
66
68
70 std::function<void(const zelda3::PotItem&)> callback) {
71 item_placed_callback_ = std::move(callback);
72 }
73 void SetOpenSelectionInspectorCallback(std::function<void()> callback) {
74 open_selection_inspector_callback_ = std::move(callback);
75 }
76
77 private:
78 void DrawContent(bool show_room_items) {
79 gui::AutoWidgetScope automation_scope("Dungeon/ItemEditor");
80 if (!current_room_id_ || !rooms_) {
81 ImGui::TextDisabled(tr("No room data available"));
82 return;
83 }
84
85 if (*current_room_id_ < 0 ||
86 *current_room_id_ >= static_cast<int>(rooms_->size())) {
87 ImGui::TextDisabled(tr("No room selected"));
88 return;
89 }
90
93 ImGui::Separator();
94 DrawItemSelector(show_room_items);
95 if (show_room_items) {
96 ImGui::Separator();
98 }
99 }
100
101 bool HasMatchingViewer() const {
104 *current_room_id_ >= static_cast<int>(rooms_->size()) ||
106 return false;
107 }
108 const auto* context = canvas_viewer_->object_interaction()
110 .tile_handler()
111 .context();
112 return context && context->current_room_id == *current_room_id_;
113 }
114
120
121 // Pot item names from ZELDA3_DUNGEON_SPEC.md Section 7.2
122 static constexpr const char* kPotItemNames[] = {
123 "Nothing", // 0
124 "Green Rupee", // 1
125 "Rock", // 2
126 "Bee", // 3
127 "Health", // 4
128 "Bomb", // 5
129 "Heart", // 6
130 "Blue Rupee", // 7
131 "Key", // 8
132 "Arrow", // 9
133 "Bomb", // 10
134 "Heart", // 11
135 "Magic", // 12
136 "Full Magic", // 13
137 "Cucco", // 14
138 "Green Soldier", // 15
139 "Bush Stal", // 16
140 "Blue Soldier", // 17
141 "Landmine", // 18
142 "Heart", // 19
143 "Fairy", // 20
144 "Heart", // 21
145 "Nothing", // 22
146 "Hole", // 23
147 "Warp", // 24
148 "Staircase", // 25
149 "Bombable", // 26
150 "Switch" // 27
151 };
152 static constexpr size_t kPotItemCount =
153 sizeof(kPotItemNames) / sizeof(kPotItemNames[0]);
154
156 const auto& theme = AgentUI::GetTheme();
157 // Follow canvas cancellation and selection instead of a stale panel mode.
161 if (placement_mode_) {
164 .item_handler()
165 .GetItemId();
166 const char* item_name = (selected_item_id_ < kPotItemCount)
168 : "Unknown";
169 ImGui::TextColored(theme.status_warning,
170 ICON_MD_PLACE " Placing: %s (0x%02X)", item_name,
172 if (ImGui::SmallButton(ICON_MD_CANCEL " Cancel")) {
173 placement_mode_ = false;
174 if (canvas_viewer_) {
176 }
177 }
178 } else {
179 ImGui::TextColored(theme.text_secondary_gray,
180 ICON_MD_INFO " Select an item to place");
181 }
182 }
183
184 void DrawItemSelector(bool show_room_items) {
185 const bool can_edit = CanEditRoom();
186 const auto& theme = AgentUI::GetTheme();
187 ImGui::Text(ICON_MD_INVENTORY " Select Item:");
188
189 // Item grid with responsive sizing
190 float available_height = ImGui::GetContentRegionAvail().y;
191 // Reserve space for room items section (header + list + some margin)
192 float reserved_height = 120.0f;
193 // Calculate grid height: at least 150px, but responsive to available space
194 const float grid_height =
195 show_room_items
196 ? std::max(150.0f,
197 std::min(400.0f, available_height - reserved_height))
198 : std::max(1.0f, available_height);
199
200 // Responsive item size based on panel width
201 float panel_width = ImGui::GetContentRegionAvail().x;
202 float item_size =
203 std::max(36.0f, std::min(48.0f, (panel_width - 40.0f) / 6.0f));
204 int items_per_row =
205 std::max(1, static_cast<int>(panel_width / (item_size + 8)));
206
207 ImGui::BeginChild("##ItemGrid", ImVec2(0, grid_height), true,
208 ImGuiWindowFlags_HorizontalScrollbar);
209 ImGui::BeginDisabled(!can_edit);
210
211 int col = 0;
212 for (size_t i = 0; i < kPotItemCount; ++i) {
213 bool is_selected = (selected_item_id_ == static_cast<int>(i));
214
215 ImGui::PushID(static_cast<int>(i));
216
217 // Color-coded button based on item type using theme colors
218 ImVec4 button_color = GetItemTypeColor(static_cast<int>(i), theme);
219 if (is_selected) {
220 button_color.x = std::min(1.0f, button_color.x + 0.2f);
221 button_color.y = std::min(1.0f, button_color.y + 0.2f);
222 button_color.z = std::min(1.0f, button_color.z + 0.2f);
223 }
224
225 {
226 gui::StyleColorGuard btn_colors({
227 {ImGuiCol_Button, button_color},
228 {ImGuiCol_ButtonHovered,
229 ImVec4(std::min(1.0f, button_color.x + 0.1f),
230 std::min(1.0f, button_color.y + 0.1f),
231 std::min(1.0f, button_color.z + 0.1f), 1.0f)},
232 {ImGuiCol_ButtonActive,
233 ImVec4(std::min(1.0f, button_color.x + 0.2f),
234 std::min(1.0f, button_color.y + 0.2f),
235 std::min(1.0f, button_color.z + 0.2f), 1.0f)},
236 });
237
238 // Get icon and short name for item
239 const char* icon = GetItemTypeIcon(static_cast<int>(i));
240 std::string label =
241 absl::StrFormat("%s\n%02X", icon, static_cast<int>(i));
242 const bool picked =
243 ImGui::Button(label.c_str(), ImVec2(item_size, item_size));
245 absl::StrFormat("place_item_%02X", i));
246 if (picked && can_edit) {
247 selected_item_id_ = static_cast<int>(i);
248 placement_mode_ = true;
249 if (canvas_viewer_) {
251 true, static_cast<uint8_t>(i));
252 }
253 }
254 }
255
256 if (ImGui::IsItemHovered()) {
257 ImGui::SetTooltip(tr("%s (0x%02X)\nClick to select for placement"),
258 kPotItemNames[i], static_cast<int>(i));
259 }
260
261 // Selection highlight using theme color
262 if (is_selected) {
263 ImVec2 min = ImGui::GetItemRectMin();
264 ImVec2 max = ImGui::GetItemRectMax();
265 ImU32 sel_color =
266 ImGui::ColorConvertFloat4ToU32(theme.dungeon_selection_primary);
267 ImGui::GetWindowDrawList()->AddRect(min, max, sel_color, 0.0f, 0, 2.0f);
268 }
269
270 ImGui::PopID();
271
272 col++;
273 if (col < items_per_row) {
274 ImGui::SameLine();
275 } else {
276 col = 0;
277 }
278 }
279
280 ImGui::EndDisabled();
281 ImGui::EndChild();
282 }
283
285 const auto& theme = AgentUI::GetTheme();
286 auto& room = (*rooms_)[*current_room_id_];
287 const auto& items = room.GetPotItems();
288 int selected_item_index = -1;
289 if (HasMatchingViewer() &&
291 const auto selected_entity =
293 if (selected_entity.type == EntityType::Item) {
294 selected_item_index = static_cast<int>(selected_entity.index);
295 }
296 }
297
298 ImGui::Text(ICON_MD_LIST " Room Items (%zu):", items.size());
299 if (selected_item_index >= 0 && open_selection_inspector_callback_) {
300 ImGui::SameLine();
301 if (ImGui::SmallButton(ICON_MD_OPEN_IN_NEW " Inspect Selected")) {
303 }
304 }
305
306 if (items.empty()) {
307 ImGui::TextColored(theme.text_secondary_gray,
308 ICON_MD_INFO " No items in this room");
309 return;
310 }
311
312 if (selected_item_index >= 0 &&
313 selected_item_index < static_cast<int>(items.size()) &&
315 const bool can_edit = CanEditRoom();
316 ImGui::BeginDisabled(!can_edit);
317 uint8_t raw_item_type = items[selected_item_index].item;
318 ImGui::AlignTextToFramePadding();
319 ImGui::TextUnformatted(tr("Selected raw type"));
320 ImGui::SameLine();
321 ImGui::SetNextItemWidth(72.0f);
322 const bool type_changed = gui::InputScalarDeferred(
323 "##SelectedItemRawType", ImGuiDataType_U8, &raw_item_type, "%02X",
324 ImGuiInputTextFlags_CharsHexadecimal,
325 {reinterpret_cast<uintptr_t>(rooms_),
326 static_cast<uint64_t>(*current_room_id_),
327 static_cast<uint64_t>(selected_item_index)});
328 gui::AutoRegisterLastItem("input_scalar", "selected_item_raw_type",
329 "Selected pot item raw type byte");
330 ImGui::EndDisabled();
331 if (type_changed && can_edit) {
334 .item_handler()
335 .MutateItemType(selected_item_index, raw_item_type);
336 }
337 }
338
339 float list_height = std::max(100.0f, ImGui::GetContentRegionAvail().y);
340 ImGui::BeginChild("##ItemList", ImVec2(0, list_height), true);
341 for (size_t i = 0; i < items.size(); ++i) {
342 const auto& item = items[i];
343
344 ImGui::PushID(static_cast<int>(i));
345
346 const char* item_name =
347 (item.item < kPotItemCount) ? kPotItemNames[item.item] : "Unknown";
348
349 const std::string row_label =
350 absl::StrFormat("[%zu] %s (0x%02X)", i, item_name, item.item);
351 const bool row_selected = ImGui::Selectable(
352 row_label.c_str(), selected_item_index == static_cast<int>(i));
354 "selectable", absl::StrFormat("item_row_%zu", i),
355 "Select a pot item in the current dungeon room");
356 ImGui::SameLine();
357 ImGui::TextColored(theme.text_secondary_gray, "@ (%d,%d)",
358 item.GetTileX(), item.GetTileY());
359
360 if (row_selected && HasMatchingViewer()) {
361 canvas_viewer_->object_interaction().SelectEntity(EntityType::Item, i);
362 }
363
364 ImGui::PopID();
365 }
366 ImGui::EndChild();
367 }
368
369 ImVec4 GetItemTypeColor(int item_id, const AgentUITheme& theme) {
370 // Color-code based on item type using theme colors
371 if (item_id == 0 || item_id == 22) {
372 return theme.dungeon_object_default; // Gray for "Nothing"
373 } else if (item_id >= 1 && item_id <= 7) {
374 return theme.dungeon_sprite_layer0; // Green for rupees/items
375 } else if (item_id == 8) {
376 return theme.dungeon_object_chest; // Gold for key
377 } else if (item_id >= 15 && item_id <= 17) {
378 return theme.status_error; // Red for enemies
379 } else if (item_id >= 23 && item_id <= 27) {
380 return theme.dungeon_object_stairs; // Yellow for special
381 }
382 return theme.dungeon_object_pot; // Pot color for default
383 }
384
385 const char* GetItemTypeIcon(int item_id) {
386 // Return item-type-appropriate icons
387 if (item_id == 0 || item_id == 22) {
388 return ICON_MD_BLOCK; // Nothing
389 } else if (item_id == 1 || item_id == 7) {
390 return ICON_MD_MONETIZATION_ON; // Rupees (green/blue)
391 } else if (item_id == 4 || item_id == 6 || item_id == 11 || item_id == 19 ||
392 item_id == 21) {
393 return ICON_MD_FAVORITE; // Hearts
394 } else if (item_id == 8) {
395 return ICON_MD_KEY; // Key
396 } else if (item_id == 5 || item_id == 10) {
397 return ICON_MD_CIRCLE; // Bombs
398 } else if (item_id == 9) {
399 return ICON_MD_ARROW_UPWARD; // Arrows
400 } else if (item_id == 12 || item_id == 13) {
401 return ICON_MD_AUTO_AWESOME; // Magic
402 } else if (item_id == 14) {
403 return ICON_MD_EGG; // Cucco
404 } else if (item_id >= 15 && item_id <= 17) {
405 return ICON_MD_PERSON; // Soldiers
406 } else if (item_id == 18) {
407 return ICON_MD_WARNING; // Landmine
408 } else if (item_id == 20) {
409 return ICON_MD_FLUTTER_DASH; // Fairy
410 } else if (item_id == 23) {
411 return ICON_MD_TERRAIN; // Hole
412 } else if (item_id == 24) {
413 return ICON_MD_SWAP_HORIZ; // Warp
414 } else if (item_id == 25) {
415 return ICON_MD_STAIRS; // Staircase
416 } else if (item_id == 26) {
417 return ICON_MD_BROKEN_IMAGE; // Bombable
418 } else if (item_id == 27) {
419 return ICON_MD_TOGGLE_ON; // Switch
420 } else if (item_id == 2) {
421 return ICON_MD_LANDSCAPE; // Rock
422 } else if (item_id == 3) {
423 return ICON_MD_BUG_REPORT; // Bee
424 }
425 return ICON_MD_HELP; // Unknown
426 }
427
428 int* current_room_id_ = nullptr;
431
432 // Selection state
434 bool placement_mode_ = false;
435
436 std::function<void(const zelda3::PotItem&)> item_placed_callback_;
438};
439
440} // namespace editor
441} // namespace yaze
442
443#endif // YAZE_APP_EDITOR_DUNGEON_PANELS_ITEM_EDITOR_PANEL_H_
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)
WindowContent for placing and managing dungeon pot items.
std::string GetIcon() const override
Material Design icon for this panel.
static constexpr size_t kPotItemCount
static constexpr const char * kPotItemNames[]
int GetPriority() const override
Get display priority for menu ordering.
void DrawContent(bool show_room_items)
ImVec4 GetItemTypeColor(int item_id, const AgentUITheme &theme)
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetOpenSelectionInspectorCallback(std::function< void()> callback)
std::function< void(const zelda3::PotItem &) item_placed_callback_)
std::string GetEditorCategory() const override
Editor category this panel belongs to.
std::string GetId() const override
Unique identifier for this panel.
void DrawItemSelector(bool show_room_items)
DungeonCanvasViewer * canvas_viewer_
void Draw(bool *) override
Draw the panel content.
void SetItemPlacedCallback(std::function< void(const zelda3::PotItem &)> callback)
std::string GetDisplayName() const override
Human-readable name shown in menus and title bars.
const char * GetItemTypeIcon(int item_id)
std::function< void()> open_selection_inspector_callback_
std::string GetWorkflowGroup() const override
Optional workflow group for hack-centric actions.
ItemEditorPanel(int *current_room_id, DungeonRoomStore *rooms, DungeonCanvasViewer *canvas_viewer=nullptr)
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.
Base interface for all logical window content components.
RAII scope that enables automatic widget registration.
RAII guard for ImGui style colors.
Definition style_guard.h:27
#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()
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:392
Centralized theme colors for Agent UI components.
Definition agent_theme.h:19
Automatic widget registration helpers for ImGui Test Engine integration.