yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
asset_browser.cc
Go to the documentation of this file.
1#include "asset_browser.h"
2#include "util/i18n/tr.h"
3
4#include "absl/strings/str_format.h"
5
6namespace yaze {
7namespace gui {
8
9using namespace ImGui;
10
11const ImGuiTableSortSpecs* AssetObject::s_current_sort_specs = NULL;
12
14 const std::array<gfx::Bitmap, zelda3::kNumGfxSheets>& bmp_manager) {
15 PushItemWidth(GetFontSize() * 10);
16 SeparatorText(tr("Contents"));
17 Checkbox(tr("Show Type Overlay"), &ShowTypeOverlay);
18 SameLine();
19 Checkbox(tr("Allow Sorting"), &AllowSorting);
20 SameLine();
21 Checkbox(tr("Stretch Spacing"), &StretchSpacing);
22 SameLine();
23 Checkbox(tr("Allow dragging unselected item"), &AllowDragUnselected);
24 SameLine();
25 Checkbox(tr("Allow box-selection"), &AllowBoxSelect);
26 SameLine();
27 SliderFloat(tr("Icon Size"), &IconSize, 16.0f, 128.0f, "%.0f");
28 SameLine();
29 SliderInt(tr("Icon Spacing"), &IconSpacing, 0, 32);
30 SameLine();
31 SliderInt(tr("Icon Hit Spacing"), &IconHitSpacing, 0, 32);
32 PopItemWidth();
33
34 // Filter by types
35 static bool filter_type[4] = {true, true, true, true};
36 Text(tr("Filter by type:"));
37 SameLine();
38 Checkbox(tr("Unsorted"), &filter_type[0]);
39 SameLine();
40 Checkbox(tr("Dungeon"), &filter_type[1]);
41 SameLine();
42 Checkbox(tr("Overworld"), &filter_type[2]);
43 SameLine();
44 Checkbox(tr("Sprite"), &filter_type[3]);
45
46 // Show a table with ONLY one header row to showcase the idea/possibility of
47 // using this to provide a sorting UI
48 if (AllowSorting) {
49 PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
50 ImGuiTableFlags table_flags_for_sort_specs =
51 ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti |
52 ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Borders;
53 if (BeginTable("for_sort_specs_only", 2, table_flags_for_sort_specs,
54 ImVec2(0.0f, GetFrameHeight()))) {
55 TableSetupColumn("Index");
56 TableSetupColumn("Type");
57 TableHeadersRow();
58 if (ImGuiTableSortSpecs* sort_specs = TableGetSortSpecs())
59 if (sort_specs->SpecsDirty || RequestSort) {
60 AssetObject::SortWithSortSpecs(sort_specs, Items.Data, Items.Size);
61 sort_specs->SpecsDirty = RequestSort = false;
62 }
63 EndTable();
64 }
65 PopStyleVar();
66 }
67
68 ImGuiIO& io = GetIO();
69 SetNextWindowContentSize(ImVec2(
70 0.0f, LayoutOuterPadding +
72 if (BeginChild("Assets", ImVec2(0.0f, -GetTextLineHeightWithSpacing()),
73 ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove)) {
74 ImDrawList* draw_list = GetWindowDrawList();
75
76 const float avail_width = GetContentRegionAvail().x;
77 UpdateLayoutSizes(avail_width);
78
79 // Calculate and store start position.
80 ImVec2 start_pos = GetCursorScreenPos();
81 start_pos = ImVec2(start_pos.x + LayoutOuterPadding,
82 start_pos.y + LayoutOuterPadding);
83 SetCursorScreenPos(start_pos);
84
85 // Multi-select
86 ImGuiMultiSelectFlags ms_flags = ImGuiMultiSelectFlags_ClearOnEscape |
87 ImGuiMultiSelectFlags_ClearOnClickVoid;
88
89 // - Enable box-select (in 2D mode, so that changing box-select rectangle
90 // X1/X2 boundaries will affect clipped items)
92 ms_flags |= ImGuiMultiSelectFlags_BoxSelect2d;
93
94 // - This feature allows dragging an unselected item without selecting it
95 // (rarely used)
97 ms_flags |= ImGuiMultiSelectFlags_SelectOnClickRelease;
98
99 // - Enable keyboard wrapping on X axis
100 // (FIXME-MULTISELECT: We haven't designed/exposed a general nav wrapping
101 // api yet, so this flag is provided as a courtesy to avoid doing:
102 // NavMoveRequestTryWrapping(GetCurrentWindow(),
103 // ImGuiNavMoveFlags_WrapX);
104 // When we finish implementing a more general API for this, we will
105 // obsolete this flag in favor of the new system)
106 ms_flags |= ImGuiMultiSelectFlags_NavWrapX;
107
108 ImGuiMultiSelectIO* ms_io =
109 BeginMultiSelect(ms_flags, Selection.Size, Items.Size);
110
111 // Use custom selection adapter: store ID in selection (recommended)
112 Selection.UserData = this;
113 Selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self_,
114 int idx) {
115 GfxSheetAssetBrowser* self = (GfxSheetAssetBrowser*)self_->UserData;
116 return self->Items[idx].ID;
117 };
118 Selection.ApplyRequests(ms_io);
119
120 const bool want_delete =
121 (Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) &&
122 (Selection.Size > 0)) ||
124 const int item_curr_idx_to_focus =
125 want_delete ? Selection.ApplyDeletionPreLoop(ms_io, Items.Size) : -1;
126 RequestDelete = false;
127
128 // Push LayoutSelectableSpacing (which is LayoutItemSpacing minus
129 // hit-spacing, if we decide to have hit gaps between items) Altering
130 // style ItemSpacing may seem unnecessary as we position every items using
131 // SetCursorScreenPos()... But it is necessary for two reasons:
132 // - Selectables uses it by default to visually fill the space between two
133 // items.
134 // - The vertical spacing would be measured by Clipper to calculate line
135 // height if we didn't provide it explicitly (here we do).
136 PushStyleVar(ImGuiStyleVar_ItemSpacing,
138
139 // Rendering parameters
140 const ImU32 icon_type_overlay_colors[5] = {
141 0, IM_COL32(200, 70, 70, 255), IM_COL32(70, 170, 70, 255),
142 IM_COL32(70, 70, 200, 255), IM_COL32(200, 200, 200, 255)};
143 const ImU32 icon_bg_color = GetColorU32(ImGuiCol_MenuBarBg);
144 const ImVec2 icon_type_overlay_size = ImVec2(5.0f, 5.0f);
145 const bool display_label = (LayoutItemSize.x >= CalcTextSize("999").x);
146
147 const int column_count = LayoutColumnCount;
148 ImGuiListClipper clipper;
149 clipper.Begin(LayoutLineCount, LayoutItemStep.y);
150
151 // Ensure focused item line is not clipped.
152 if (item_curr_idx_to_focus != -1)
153 clipper.IncludeItemByIndex(item_curr_idx_to_focus / column_count);
154
155 // Ensure RangeSrc item line is not clipped.
156 if (ms_io->RangeSrcItem != -1)
157 clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem / column_count);
158
159 while (clipper.Step()) {
160 for (int line_idx = clipper.DisplayStart; line_idx < clipper.DisplayEnd;
161 line_idx++) {
162 const int item_min_idx_for_current_line = line_idx * column_count;
163 const int item_max_idx_for_current_line =
164 IM_MIN((line_idx + 1) * column_count, Items.Size);
165 for (int item_idx = item_min_idx_for_current_line;
166 item_idx < item_max_idx_for_current_line; ++item_idx) {
167 AssetObject* item_data = &Items[item_idx];
168 PushID((int)item_data->ID);
169
170 // Position item
171 ImVec2 pos =
172 ImVec2(start_pos.x + (item_idx % column_count) * LayoutItemStep.x,
173 start_pos.y + line_idx * LayoutItemStep.y);
174 SetCursorScreenPos(pos);
175
176 SetNextItemSelectionUserData(item_idx);
177 bool item_is_selected = Selection.Contains((ImGuiID)item_data->ID);
178 bool item_is_visible = IsRectVisible(LayoutItemSize);
179 Selectable("", item_is_selected, ImGuiSelectableFlags_None,
181
182 // Update our selection state immediately (without waiting for
183 // EndMultiSelect() requests) because we use this to alter the color
184 // of our text/icon.
185 if (IsItemToggledSelection())
186 item_is_selected = !item_is_selected;
187
188 // Focus (for after deletion)
189 if (item_curr_idx_to_focus == item_idx)
190 SetKeyboardFocusHere(-1);
191
192 // Drag and drop
193 if (BeginDragDropSource()) {
194 // Create payload with full selection OR single unselected item.
195 // (the later is only possible when using
196 // ImGuiMultiSelectFlags_SelectOnClickRelease)
197 if (GetDragDropPayload() == NULL) {
198 ImVector<ImGuiID> payload_items;
199 void* it = NULL;
200 ImGuiID id = 0;
201 if (!item_is_selected)
202 payload_items.push_back(item_data->ID);
203 else
204 while (Selection.GetNextSelectedItem(&it, &id))
205 payload_items.push_back(id);
206 SetDragDropPayload("ASSETS_BROWSER_ITEMS", payload_items.Data,
207 (size_t)payload_items.size_in_bytes());
208 }
209
210 // Display payload content in tooltip, by extracting it from the
211 // payload data (we could read from selection, but it is more
212 // correct and reusable to read from payload)
213 const ImGuiPayload* payload = GetDragDropPayload();
214 const int payload_count =
215 (int)payload->DataSize / (int)sizeof(ImGuiID);
216 Text(tr("%d assets"), payload_count);
217
218 EndDragDropSource();
219 }
220
221 // Render icon (a real app would likely display an image/thumbnail
222 // here) Because we use ImGuiMultiSelectFlags_BoxSelect2d, clipping
223 // vertical may occasionally be larger, so we coarse-clip our
224 // rendering as well.
225 if (item_is_visible) {
226 ImVec2 box_min(pos.x - 1, pos.y - 1);
227 ImVec2 box_max(box_min.x + LayoutItemSize.x + 2,
228 box_min.y + LayoutItemSize.y + 2); // Dubious
229 draw_list->AddRectFilled(box_min, box_max,
230 icon_bg_color); // Background color
231
232 if (display_label) {
233 ImU32 label_col = GetColorU32(
234 item_is_selected ? ImGuiCol_Text : ImGuiCol_TextDisabled);
235 draw_list->AddImage(
236 (ImTextureID)(intptr_t)bmp_manager[item_data->ID].texture(),
237 box_min, box_max, ImVec2(0, 0), ImVec2(1, 1),
238 GetColorU32(ImVec4(1, 1, 1, 1)));
239 draw_list->AddText(ImVec2(box_min.x, box_max.y - GetFontSize()),
240 label_col,
241 absl::StrFormat("%X", item_data->ID).c_str());
242 }
243 if (ShowTypeOverlay && item_data->Type != 0) {
244 ImU32 type_col = icon_type_overlay_colors
245 [item_data->Type % IM_ARRAYSIZE(icon_type_overlay_colors)];
246 draw_list->AddRectFilled(
247 ImVec2(box_max.x - 2 - icon_type_overlay_size.x,
248 box_min.y + 2),
249 ImVec2(box_max.x - 2,
250 box_min.y + 2 + icon_type_overlay_size.y),
251 type_col);
252 }
253 }
254
255 PopID();
256 }
257 }
258 }
259 clipper.End();
260 PopStyleVar(); // ImGuiStyleVar_ItemSpacing
261
262 // Context menu
263 if (BeginPopupContextWindow()) {
264 Text(tr("Selection: %d items"), Selection.Size);
265 Separator();
266 if (BeginMenu(tr("Set Type"))) {
267 if (MenuItem(tr("Unsorted"))) {
268 void* it = NULL;
269 ImGuiID id = 0;
270 while (Selection.GetNextSelectedItem(&it, &id))
271 Items[id].Type = 0;
272 }
273 if (MenuItem(tr("Dungeon"))) {
274 void* it = NULL;
275 ImGuiID id = 0;
276 while (Selection.GetNextSelectedItem(&it, &id))
277 Items[id].Type = 1;
278 }
279 if (MenuItem(tr("Overworld"))) {
280 void* it = NULL;
281 ImGuiID id = 0;
282 while (Selection.GetNextSelectedItem(&it, &id))
283 Items[id].Type = 2;
284 }
285 if (MenuItem(tr("Sprite"))) {
286 void* it = NULL;
287 ImGuiID id = 0;
288 while (Selection.GetNextSelectedItem(&it, &id))
289 Items[id].Type = 3;
290 }
291 EndMenu();
292 }
293 Separator();
294 if (MenuItem(tr("Delete"), "Del", false, Selection.Size > 0))
295 RequestDelete = true;
296 EndPopup();
297 }
298
299 ms_io = EndMultiSelect();
300 Selection.ApplyRequests(ms_io);
301 if (want_delete)
302 Selection.ApplyDeletionPostLoop(ms_io, Items, item_curr_idx_to_focus);
303
304 // Zooming with CTRL+Wheel
305 if (IsWindowAppearing())
306 ZoomWheelAccum = 0.0f;
307 if (IsWindowHovered() && io.MouseWheel != 0.0f &&
308 IsKeyDown(ImGuiMod_Ctrl) && IsAnyItemActive() == false) {
309 ZoomWheelAccum += io.MouseWheel;
310 if (fabsf(ZoomWheelAccum) >= 1.0f) {
311 // Calculate hovered item index from mouse location
312 // FIXME: Locking aiming on 'hovered_item_idx' (with a cool-down
313 // timer) would ensure zoom keeps on it.
314 const float hovered_item_nx =
315 (io.MousePos.x - start_pos.x + LayoutItemSpacing * 0.5f) /
317 const float hovered_item_ny =
318 (io.MousePos.y - start_pos.y + LayoutItemSpacing * 0.5f) /
320 const int hovered_item_idx =
321 ((int)hovered_item_ny * LayoutColumnCount) + (int)hovered_item_nx;
322 // SetTooltip("%f,%f -> item %d", hovered_item_nx,
323 // hovered_item_ny, hovered_item_idx); // Move those 4 lines in block
324 // above for easy debugging
325
326 // Zoom
327 IconSize *= powf(1.1f, (float)(int)ZoomWheelAccum);
328 IconSize = IM_CLAMP(IconSize, 16.0f, 128.0f);
330 UpdateLayoutSizes(avail_width);
331
332 // Manipulate scroll to that we will land at the same Y location of
333 // currently hovered item.
334 // - Calculate next frame position of item under mouse
335 // - Set new scroll position to be used in next BeginChild()
336 // call.
337 float hovered_item_rel_pos_y =
338 ((float)(hovered_item_idx / LayoutColumnCount) +
339 fmodf(hovered_item_ny, 1.0f)) *
341 hovered_item_rel_pos_y += GetStyle().WindowPadding.y;
342 float mouse_local_y = io.MousePos.y - GetWindowPos().y;
343 SetScrollY(hovered_item_rel_pos_y - mouse_local_y);
344 }
345 }
346 }
347 EndChild();
348
349 Text(tr("Selected: %d/%d items"), Selection.Size, Items.Size);
350}
351
352} // namespace gui
353
354} // namespace yaze
#define IM_CLAMP(V, MN, MX)
#define IM_MIN(A, B)
Definition input.cc:23
void SeparatorText(const char *label)
static const ImGuiTableSortSpecs * s_current_sort_specs
static void SortWithSortSpecs(ImGuiTableSortSpecs *sort_specs, AssetObject *items, int items_count)
int ApplyDeletionPreLoop(ImGuiMultiSelectIO *ms_io, int items_count)
void ApplyDeletionPostLoop(ImGuiMultiSelectIO *ms_io, ImVector< ITEM_TYPE > &items, int item_curr_idx_to_select)
void UpdateLayoutSizes(float avail_width)
ExampleSelectionWithDeletion Selection
ImVector< AssetObject > Items
void Draw(const std::array< gfx::Bitmap, zelda3::kNumGfxSheets > &bmp_manager)
Represents a keyboard shortcut with its associated action.