yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_item_list_panel.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <cctype>
6#include <string>
7#include <vector>
8
9#include "absl/strings/str_format.h"
12#include "imgui/imgui.h"
14
15namespace yaze::editor {
16namespace {
17
19 const zelda3::OverworldItem& rhs) {
20 return lhs.id_ == rhs.id_ && lhs.room_map_id_ == rhs.room_map_id_ &&
21 lhs.x_ == rhs.x_ && lhs.y_ == rhs.y_ && lhs.game_x_ == rhs.game_x_ &&
22 lhs.game_y_ == rhs.game_y_ && lhs.bg2_ == rhs.bg2_;
23}
24
25std::string ToLower(std::string value) {
26 std::transform(
27 value.begin(), value.end(), value.begin(),
28 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
29 return value;
30}
31
32std::string ItemName(const zelda3::OverworldItem& item) {
33 if (item.id_ < zelda3::kSecretItemNames.size()) {
34 return zelda3::kSecretItemNames[item.id_];
35 }
36 return absl::StrFormat("Item 0x%02X", static_cast<int>(item.id_));
37}
38
40 const std::string& lowered_filter) {
41 if (lowered_filter.empty()) {
42 return true;
43 }
44
45 const std::string name = ToLower(ItemName(item));
46 const std::string id_hex =
47 absl::StrFormat("%02x", static_cast<int>(item.id_));
48 const std::string map_hex =
49 absl::StrFormat("%02x", static_cast<int>(item.room_map_id_));
50 const std::string pos = absl::StrFormat("%d,%d", item.x_, item.y_);
51
52 return name.find(lowered_filter) != std::string::npos ||
53 id_hex.find(lowered_filter) != std::string::npos ||
54 map_hex.find(lowered_filter) != std::string::npos ||
55 pos.find(lowered_filter) != std::string::npos;
56}
57
58} // namespace
59
60void OverworldItemListPanel::Draw(bool* p_open) {
61 (void)p_open;
62 auto* ow_editor = CurrentOverworldEditor();
63 if (!ow_editor) {
64 return;
65 }
66 if (ow_editor->is_lyra_preview()) {
67 ImGui::TextDisabled("Lyra preview: item and sprite lists unavailable");
68 return;
69 }
70 // Selecting this right-hand window starts item placement. Insert Item uses
71 // the catalog popup instead.
72 static bool was_focused = false;
73 const bool focused =
74 ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
75 if (focused && !was_focused) {
76 ow_editor->SetEntityEditMode(EntityEditMode::ITEMS);
77 }
78 was_focused = focused;
79
80 auto* items = ow_editor->overworld().mutable_all_items();
81 if (!items) {
82 ImGui::TextDisabled(tr("No item list available."));
83 return;
84 }
85
86 static char filter_buffer[64] = "";
87 static bool current_world_only = true;
88 static bool current_map_only = false;
89 static int sort_mode = 0; // 0=Map+Pos, 1=ID, 2=Name
90
91 ImGui::InputTextWithHint("##ItemFilter", ICON_MD_FILTER_ALT " Filter items",
92 filter_buffer, sizeof(filter_buffer));
93 ImGui::SameLine();
94 ImGui::Checkbox(tr("World"), &current_world_only);
95 ImGui::SameLine();
96 ImGui::Checkbox(tr("Map"), &current_map_only);
97
98 const char* sort_modes[] = {"Map + Position", "Item ID", "Name"};
99 ImGui::SetNextItemWidth(180.0f);
100 ImGui::Combo(tr("Sort"), &sort_mode, sort_modes, IM_ARRAYSIZE(sort_modes));
101
102 const std::string lowered_filter = ToLower(std::string(filter_buffer));
103 std::vector<size_t> filtered_indices;
104 filtered_indices.reserve(items->size());
105
106 const int current_map = ow_editor->current_map_id();
107 const int current_world = ow_editor->current_world_id();
108
109 for (size_t i = 0; i < items->size(); ++i) {
110 const auto& item = items->at(i);
111 if (item.deleted) {
112 continue;
113 }
114 if (current_world_only && (item.room_map_id_ / 0x40) != current_world) {
115 continue;
116 }
117 if (current_map_only && item.room_map_id_ != current_map) {
118 continue;
119 }
120 if (!MatchesItemFilter(item, lowered_filter)) {
121 continue;
122 }
123 filtered_indices.push_back(i);
124 }
125
126 auto sort_by_map_pos = [&](size_t lhs, size_t rhs) {
127 const auto& a = items->at(lhs);
128 const auto& b = items->at(rhs);
129 if (a.room_map_id_ != b.room_map_id_) {
130 return a.room_map_id_ < b.room_map_id_;
131 }
132 if (a.y_ != b.y_) {
133 return a.y_ < b.y_;
134 }
135 if (a.x_ != b.x_) {
136 return a.x_ < b.x_;
137 }
138 return a.id_ < b.id_;
139 };
140 auto sort_by_id = [&](size_t lhs, size_t rhs) {
141 const auto& a = items->at(lhs);
142 const auto& b = items->at(rhs);
143 if (a.id_ != b.id_) {
144 return a.id_ < b.id_;
145 }
146 return sort_by_map_pos(lhs, rhs);
147 };
148 auto sort_by_name = [&](size_t lhs, size_t rhs) {
149 const auto& a = items->at(lhs);
150 const auto& b = items->at(rhs);
151 const std::string name_a = ItemName(a);
152 const std::string name_b = ItemName(b);
153 if (name_a != name_b) {
154 return name_a < name_b;
155 }
156 return sort_by_map_pos(lhs, rhs);
157 };
158
159 switch (sort_mode) {
160 case 1:
161 std::sort(filtered_indices.begin(), filtered_indices.end(), sort_by_id);
162 break;
163 case 2:
164 std::sort(filtered_indices.begin(), filtered_indices.end(), sort_by_name);
165 break;
166 case 0:
167 default:
168 std::sort(filtered_indices.begin(), filtered_indices.end(),
169 sort_by_map_pos);
170 break;
171 }
172
173 const zelda3::OverworldItem* selected_item = ow_editor->GetSelectedItem();
174 const bool has_selection = selected_item != nullptr;
175
176 if (!has_selection) {
177 ImGui::BeginDisabled();
178 }
179 if (ImGui::Button(ICON_MD_CONTENT_COPY " Duplicate", ImVec2(120.0f, 0.0f))) {
180 ow_editor->DuplicateSelectedItem();
181 selected_item = ow_editor->GetSelectedItem();
182 }
183 ImGui::SameLine();
184 if (ImGui::Button(ICON_MD_DELETE " Delete", ImVec2(96.0f, 0.0f))) {
185 ow_editor->DeleteSelectedItem();
186 selected_item = ow_editor->GetSelectedItem();
187 }
188 if (!has_selection) {
189 ImGui::EndDisabled();
190 }
191
192 ImGui::SameLine();
193 if (selected_item) {
194 ImGui::TextDisabled(tr("Selected: 0x%02X @ (%d,%d)"),
195 static_cast<int>(selected_item->id_), selected_item->x_,
196 selected_item->y_);
197 } else {
198 ImGui::TextDisabled(tr("No item selected"));
199 }
200
201 ImGui::TextDisabled(tr(
202 "Shortcuts: Ctrl+D duplicate, arrows nudge, Shift+arrows nudge by 16px"));
203 ImGui::TextDisabled(tr("Total: %zu | Visible: %zu"), items->size(),
204 filtered_indices.size());
205
206 if (ImGui::BeginTable("##OverworldItemListTable", 6,
207 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
208 ImGuiTableFlags_Resizable |
209 ImGuiTableFlags_SizingStretchProp |
210 ImGuiTableFlags_ScrollY,
211 ImVec2(0.0f, 0.0f))) {
212 ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 30.0f);
213 ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 42.0f);
214 ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 1.0f);
215 ImGui::TableSetupColumn("Map", ImGuiTableColumnFlags_WidthFixed, 54.0f);
216 ImGui::TableSetupColumn("World XY", ImGuiTableColumnFlags_WidthFixed,
217 118.0f);
218 ImGui::TableSetupColumn("Game XY", ImGuiTableColumnFlags_WidthFixed, 86.0f);
219 ImGui::TableHeadersRow();
220
221 for (size_t visible_row = 0; visible_row < filtered_indices.size();
222 ++visible_row) {
223 const size_t index = filtered_indices[visible_row];
224 const auto& item = items->at(index);
225 const bool is_selected =
226 selected_item && ItemIdentityMatches(item, *selected_item);
227
228 ImGui::TableNextRow();
229
230 ImGui::TableNextColumn();
231 if (ImGui::SmallButton(
232 absl::StrFormat("%s##select_item_%zu",
233 is_selected ? ICON_MD_RADIO_BUTTON_CHECKED
235 index)
236 .c_str())) {
237 ow_editor->SelectItemByIdentity(item);
238 selected_item = ow_editor->GetSelectedItem();
239 }
240
241 ImGui::TableNextColumn();
242 ImGui::Text(tr("0x%02X"), static_cast<int>(item.id_));
243
244 ImGui::TableNextColumn();
245 ImGui::TextUnformatted(ItemName(item).c_str());
246
247 ImGui::TableNextColumn();
248 if (item.room_map_id_ == current_map) {
249 ImGui::TextColored(ImVec4(0.45f, 0.85f, 0.50f, 1.0f), "%02X",
250 static_cast<int>(item.room_map_id_));
251 } else {
252 ImGui::Text("%02X", static_cast<int>(item.room_map_id_));
253 }
254
255 ImGui::TableNextColumn();
256 ImGui::Text("%4d, %4d", item.x_, item.y_);
257
258 ImGui::TableNextColumn();
259 ImGui::Text("%2d, %2d", item.game_x_, item.game_y_);
260 }
261
262 ImGui::EndTable();
263 }
264}
265
267
268} // namespace yaze::editor
Filterable list view for overworld items with quick selection/actions.
void Draw(bool *p_open) override
Draw the panel content.
#define ICON_MD_RADIO_BUTTON_CHECKED
Definition icons.h:1548
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
#define ICON_MD_RADIO_BUTTON_UNCHECKED
Definition icons.h:1551
#define ICON_MD_FILTER_ALT
Definition icons.h:761
bool ItemIdentityMatches(const zelda3::OverworldItem &lhs, const zelda3::OverworldItem &rhs)
bool MatchesItemFilter(const zelda3::OverworldItem &item, const std::string &lowered_filter)
Editors are the view controllers for the application.
OverworldEditor * CurrentOverworldEditor()
const std::vector< std::string > kSecretItemNames
#define REGISTER_PANEL(PanelClass)
Auto-registration macro for panels with default constructors.