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