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