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
67 auto* items = ow_editor->overworld().mutable_all_items();
68 if (!items) {
69 ImGui::TextDisabled(tr("No item list available."));
70 return;
71 }
72
73 static char filter_buffer[64] = "";
74 static bool current_world_only = true;
75 static bool current_map_only = false;
76 static int sort_mode = 0; // 0=Map+Pos, 1=ID, 2=Name
77
78 ImGui::InputTextWithHint("##ItemFilter", ICON_MD_FILTER_ALT " Filter items",
79 filter_buffer, sizeof(filter_buffer));
80 ImGui::SameLine();
81 ImGui::Checkbox(tr("World"), &current_world_only);
82 ImGui::SameLine();
83 ImGui::Checkbox(tr("Map"), &current_map_only);
84
85 const char* sort_modes[] = {"Map + Position", "Item ID", "Name"};
86 ImGui::SetNextItemWidth(180.0f);
87 ImGui::Combo(tr("Sort"), &sort_mode, sort_modes, IM_ARRAYSIZE(sort_modes));
88
89 const std::string lowered_filter = ToLower(std::string(filter_buffer));
90 std::vector<size_t> filtered_indices;
91 filtered_indices.reserve(items->size());
92
93 const int current_map = ow_editor->current_map_id();
94 const int current_world = ow_editor->current_world_id();
95
96 for (size_t i = 0; i < items->size(); ++i) {
97 const auto& item = items->at(i);
98 if (item.deleted) {
99 continue;
100 }
101 if (current_world_only && (item.room_map_id_ / 0x40) != current_world) {
102 continue;
103 }
104 if (current_map_only && item.room_map_id_ != current_map) {
105 continue;
106 }
107 if (!MatchesItemFilter(item, lowered_filter)) {
108 continue;
109 }
110 filtered_indices.push_back(i);
111 }
112
113 auto sort_by_map_pos = [&](size_t lhs, size_t rhs) {
114 const auto& a = items->at(lhs);
115 const auto& b = items->at(rhs);
116 if (a.room_map_id_ != b.room_map_id_) {
117 return a.room_map_id_ < b.room_map_id_;
118 }
119 if (a.y_ != b.y_) {
120 return a.y_ < b.y_;
121 }
122 if (a.x_ != b.x_) {
123 return a.x_ < b.x_;
124 }
125 return a.id_ < b.id_;
126 };
127 auto sort_by_id = [&](size_t lhs, size_t rhs) {
128 const auto& a = items->at(lhs);
129 const auto& b = items->at(rhs);
130 if (a.id_ != b.id_) {
131 return a.id_ < b.id_;
132 }
133 return sort_by_map_pos(lhs, rhs);
134 };
135 auto sort_by_name = [&](size_t lhs, size_t rhs) {
136 const auto& a = items->at(lhs);
137 const auto& b = items->at(rhs);
138 const std::string name_a = ItemName(a);
139 const std::string name_b = ItemName(b);
140 if (name_a != name_b) {
141 return name_a < name_b;
142 }
143 return sort_by_map_pos(lhs, rhs);
144 };
145
146 switch (sort_mode) {
147 case 1:
148 std::sort(filtered_indices.begin(), filtered_indices.end(), sort_by_id);
149 break;
150 case 2:
151 std::sort(filtered_indices.begin(), filtered_indices.end(), sort_by_name);
152 break;
153 case 0:
154 default:
155 std::sort(filtered_indices.begin(), filtered_indices.end(),
156 sort_by_map_pos);
157 break;
158 }
159
160 const zelda3::OverworldItem* selected_item = ow_editor->GetSelectedItem();
161 const bool has_selection = selected_item != nullptr;
162
163 if (!has_selection) {
164 ImGui::BeginDisabled();
165 }
166 if (ImGui::Button(ICON_MD_CONTENT_COPY " Duplicate", ImVec2(120.0f, 0.0f))) {
167 ow_editor->DuplicateSelectedItem();
168 selected_item = ow_editor->GetSelectedItem();
169 }
170 ImGui::SameLine();
171 if (ImGui::Button(ICON_MD_DELETE " Delete", ImVec2(96.0f, 0.0f))) {
172 ow_editor->DeleteSelectedItem();
173 selected_item = ow_editor->GetSelectedItem();
174 }
175 if (!has_selection) {
176 ImGui::EndDisabled();
177 }
178
179 ImGui::SameLine();
180 if (selected_item) {
181 ImGui::TextDisabled(tr("Selected: 0x%02X @ (%d,%d)"),
182 static_cast<int>(selected_item->id_), selected_item->x_,
183 selected_item->y_);
184 } else {
185 ImGui::TextDisabled(tr("No item selected"));
186 }
187
188 ImGui::TextDisabled(tr(
189 "Shortcuts: Ctrl+D duplicate, arrows nudge, Shift+arrows nudge by 16px"));
190 ImGui::TextDisabled(tr("Total: %zu | Visible: %zu"), items->size(),
191 filtered_indices.size());
192
193 if (ImGui::BeginTable("##OverworldItemListTable", 6,
194 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
195 ImGuiTableFlags_Resizable |
196 ImGuiTableFlags_SizingStretchProp |
197 ImGuiTableFlags_ScrollY,
198 ImVec2(0.0f, 0.0f))) {
199 ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 30.0f);
200 ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 42.0f);
201 ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 1.0f);
202 ImGui::TableSetupColumn("Map", ImGuiTableColumnFlags_WidthFixed, 54.0f);
203 ImGui::TableSetupColumn("World XY", ImGuiTableColumnFlags_WidthFixed,
204 118.0f);
205 ImGui::TableSetupColumn("Game XY", ImGuiTableColumnFlags_WidthFixed, 86.0f);
206 ImGui::TableHeadersRow();
207
208 for (size_t visible_row = 0; visible_row < filtered_indices.size();
209 ++visible_row) {
210 const size_t index = filtered_indices[visible_row];
211 const auto& item = items->at(index);
212 const bool is_selected =
213 selected_item && ItemIdentityMatches(item, *selected_item);
214
215 ImGui::TableNextRow();
216
217 ImGui::TableNextColumn();
218 if (ImGui::SmallButton(
219 absl::StrFormat("%s##select_item_%zu",
220 is_selected ? ICON_MD_RADIO_BUTTON_CHECKED
222 index)
223 .c_str())) {
224 ow_editor->SelectItemByIdentity(item);
225 selected_item = ow_editor->GetSelectedItem();
226 }
227
228 ImGui::TableNextColumn();
229 ImGui::Text(tr("0x%02X"), static_cast<int>(item.id_));
230
231 ImGui::TableNextColumn();
232 ImGui::TextUnformatted(ItemName(item).c_str());
233
234 ImGui::TableNextColumn();
235 if (item.room_map_id_ == current_map) {
236 ImGui::TextColored(ImVec4(0.45f, 0.85f, 0.50f, 1.0f), "%02X",
237 static_cast<int>(item.room_map_id_));
238 } else {
239 ImGui::Text("%02X", static_cast<int>(item.room_map_id_));
240 }
241
242 ImGui::TableNextColumn();
243 ImGui::Text("%4d, %4d", item.x_, item.y_);
244
245 ImGui::TableNextColumn();
246 ImGui::Text("%2d, %2d", item.game_x_, item.game_y_);
247 }
248
249 ImGui::EndTable();
250 }
251}
252
254
255} // 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.