|
@@ -72,6 +72,7 @@ Index of this file:
|
|
|
// [SECTION] Demo Window / ShowDemoWindow()
|
|
|
// - ShowDemoWindow()
|
|
|
// - sub section: ShowDemoWindowWidgets()
|
|
|
+// - sub section: ShowDemoWindowMultiSelect()
|
|
|
// - sub section: ShowDemoWindowLayout()
|
|
|
// - sub section: ShowDemoWindowPopups()
|
|
|
// - sub section: ShowDemoWindowTables()
|
|
@@ -94,6 +95,7 @@ Index of this file:
|
|
|
// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering()
|
|
|
// [SECTION] Example App: Docking, DockSpace / ShowExampleAppDockSpace()
|
|
|
// [SECTION] Example App: Documents Handling / ShowExampleAppDocuments()
|
|
|
+// [SECTION] Example App: Assets Browser / ShowExampleAppAssetsBrowser()
|
|
|
|
|
|
*/
|
|
|
|
|
@@ -198,6 +200,7 @@ Index of this file:
|
|
|
|
|
|
// Forward Declarations
|
|
|
static void ShowExampleAppMainMenuBar();
|
|
|
+static void ShowExampleAppAssetsBrowser(bool* p_open);
|
|
|
static void ShowExampleAppConsole(bool* p_open);
|
|
|
static void ShowExampleAppCustomRendering(bool* p_open);
|
|
|
static void ShowExampleAppDockSpace(bool* p_open);
|
|
@@ -216,6 +219,7 @@ static void ShowExampleMenuFile();
|
|
|
// We split the contents of the big ShowDemoWindow() function into smaller functions
|
|
|
// (because the link time of very large functions grow non-linearly)
|
|
|
static void ShowDemoWindowWidgets();
|
|
|
+static void ShowDemoWindowMultiSelect();
|
|
|
static void ShowDemoWindowLayout();
|
|
|
static void ShowDemoWindowPopups();
|
|
|
static void ShowDemoWindowTables();
|
|
@@ -263,6 +267,7 @@ void* GImGuiDemoMarkerCallbackUserData = NULL;
|
|
|
//-----------------------------------------------------------------------------
|
|
|
// - ShowDemoWindow()
|
|
|
// - ShowDemoWindowWidgets()
|
|
|
+// - ShowDemoWindowMultiSelect()
|
|
|
// - ShowDemoWindowLayout()
|
|
|
// - ShowDemoWindowPopups()
|
|
|
// - ShowDemoWindowTables()
|
|
@@ -284,6 +289,7 @@ void ImGui::ShowDemoWindow(bool* p_open)
|
|
|
|
|
|
// Examples Apps (accessible from the "Examples" menu)
|
|
|
static bool show_app_main_menu_bar = false;
|
|
|
+ static bool show_app_assets_browser = false;
|
|
|
static bool show_app_console = false;
|
|
|
static bool show_app_custom_rendering = false;
|
|
|
static bool show_app_dockspace = false;
|
|
@@ -301,6 +307,7 @@ void ImGui::ShowDemoWindow(bool* p_open)
|
|
|
if (show_app_main_menu_bar) ShowExampleAppMainMenuBar();
|
|
|
if (show_app_dockspace) ShowExampleAppDockSpace(&show_app_dockspace); // Process the Docking app first, as explicit DockSpace() nodes needs to be submitted early (read comments near the DockSpace function)
|
|
|
if (show_app_documents) ShowExampleAppDocuments(&show_app_documents); // Process the Document app next, as it may also use a DockSpace()
|
|
|
+ if (show_app_assets_browser) ShowExampleAppAssetsBrowser(&show_app_assets_browser);
|
|
|
if (show_app_console) ShowExampleAppConsole(&show_app_console);
|
|
|
if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering);
|
|
|
if (show_app_log) ShowExampleAppLog(&show_app_log);
|
|
@@ -398,6 +405,7 @@ void ImGui::ShowDemoWindow(bool* p_open)
|
|
|
ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar);
|
|
|
|
|
|
ImGui::SeparatorText("Mini apps");
|
|
|
+ ImGui::MenuItem("Assets Browser", NULL, &show_app_assets_browser);
|
|
|
ImGui::MenuItem("Console", NULL, &show_app_console);
|
|
|
ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering);
|
|
|
ImGui::MenuItem("Dockspace", NULL, &show_app_dockspace);
|
|
@@ -665,6 +673,7 @@ void ImGui::ShowDemoWindow(bool* p_open)
|
|
|
static void ShowDemoWindowWidgets()
|
|
|
{
|
|
|
IMGUI_DEMO_MARKER("Widgets");
|
|
|
+ //ImGui::SetNextItemOpen(true, ImGuiCond_Once);
|
|
|
if (!ImGui::CollapsingHeader("Widgets"))
|
|
|
return;
|
|
|
|
|
@@ -1410,6 +1419,7 @@ static void ShowDemoWindowWidgets()
|
|
|
}
|
|
|
|
|
|
IMGUI_DEMO_MARKER("Widgets/Selectables");
|
|
|
+ //ImGui::SetNextItemOpen(true, ImGuiCond_Once);
|
|
|
if (ImGui::TreeNode("Selectables"))
|
|
|
{
|
|
|
// Selectable() has 2 overloads:
|
|
@@ -1431,37 +1441,6 @@ static void ShowDemoWindowWidgets()
|
|
|
ImGui::TreePop();
|
|
|
}
|
|
|
|
|
|
- IMGUI_DEMO_MARKER("Widgets/Selectables/Single Selection");
|
|
|
- if (ImGui::TreeNode("Selection State: Single Selection"))
|
|
|
- {
|
|
|
- static int selected = -1;
|
|
|
- for (int n = 0; n < 5; n++)
|
|
|
- {
|
|
|
- char buf[32];
|
|
|
- sprintf(buf, "Object %d", n);
|
|
|
- if (ImGui::Selectable(buf, selected == n))
|
|
|
- selected = n;
|
|
|
- }
|
|
|
- ImGui::TreePop();
|
|
|
- }
|
|
|
- IMGUI_DEMO_MARKER("Widgets/Selectables/Multiple Selection");
|
|
|
- if (ImGui::TreeNode("Selection State: Multiple Selection"))
|
|
|
- {
|
|
|
- HelpMarker("Hold CTRL and click to select multiple items.");
|
|
|
- static bool selection[5] = { false, false, false, false, false };
|
|
|
- for (int n = 0; n < 5; n++)
|
|
|
- {
|
|
|
- char buf[32];
|
|
|
- sprintf(buf, "Object %d", n);
|
|
|
- if (ImGui::Selectable(buf, selection[n]))
|
|
|
- {
|
|
|
- if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held
|
|
|
- memset(selection, 0, sizeof(selection));
|
|
|
- selection[n] ^= 1;
|
|
|
- }
|
|
|
- }
|
|
|
- ImGui::TreePop();
|
|
|
- }
|
|
|
IMGUI_DEMO_MARKER("Widgets/Selectables/Rendering more items on the same line");
|
|
|
if (ImGui::TreeNode("Rendering more items on the same line"))
|
|
|
{
|
|
@@ -1569,6 +1548,8 @@ static void ShowDemoWindowWidgets()
|
|
|
ImGui::TreePop();
|
|
|
}
|
|
|
|
|
|
+ ShowDemoWindowMultiSelect();
|
|
|
+
|
|
|
// To wire InputText() with std::string or any other custom string type,
|
|
|
// see the "Text Input > Resize Callback" section of this demo, and the misc/cpp/imgui_stdlib.h file.
|
|
|
IMGUI_DEMO_MARKER("Widgets/Text Input");
|
|
@@ -2860,6 +2841,765 @@ static void ShowDemoWindowWidgets()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+static const char* ExampleNames[] =
|
|
|
+{
|
|
|
+ "Artichoke", "Arugula", "Asparagus", "Avocado", "Bamboo Shoots", "Bean Sprouts", "Beans", "Beet", "Belgian Endive", "Bell Pepper",
|
|
|
+ "Bitter Gourd", "Bok Choy", "Broccoli", "Brussels Sprouts", "Burdock Root", "Cabbage", "Calabash", "Capers", "Carrot", "Cassava",
|
|
|
+ "Cauliflower", "Celery", "Celery Root", "Celcuce", "Chayote", "Chinese Broccoli", "Corn", "Cucumber"
|
|
|
+};
|
|
|
+
|
|
|
+// Extra functions to add deletion support to ImGuiSelectionBasicStorage
|
|
|
+struct ExampleSelectionWithDeletion : ImGuiSelectionBasicStorage
|
|
|
+{
|
|
|
+ // Find which item should be Focused after deletion.
|
|
|
+ // Call _before_ item submission. Retunr an index in the before-deletion item list, your item loop should call SetKeyboardFocusHere() on it.
|
|
|
+ // The subsequent ApplyDeletionPostLoop() code will use it to apply Selection.
|
|
|
+ // - We cannot provide this logic in core Dear ImGui because we don't have access to selection data.
|
|
|
+ // - We don't actually manipulate the ImVector<> here, only in ApplyDeletionPostLoop(), but using similar API for consistency and flexibility.
|
|
|
+ // - Important: Deletion only works if the underlying ImGuiID for your items are stable: aka not depend on their index, but on e.g. item id/ptr.
|
|
|
+ // FIXME-MULTISELECT: Doesn't take account of the possibility focus target will be moved during deletion. Need refocus or scroll offset.
|
|
|
+ int ApplyDeletionPreLoop(ImGuiMultiSelectIO* ms_io, int items_count)
|
|
|
+ {
|
|
|
+ if (Size == 0)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ // If focused item is not selected...
|
|
|
+ const int focused_idx = (int)ms_io->NavIdItem; // Index of currently focused item
|
|
|
+ if (ms_io->NavIdSelected == false) // This is merely a shortcut, == Contains(adapter->IndexToStorage(items, focused_idx))
|
|
|
+ {
|
|
|
+ ms_io->RangeSrcReset = true; // Request to recover RangeSrc from NavId next frame. Would be ok to reset even when NavIdSelected==true, but it would take an extra frame to recover RangeSrc when deleting a selected item.
|
|
|
+ return focused_idx; // Request to focus same item after deletion.
|
|
|
+ }
|
|
|
+
|
|
|
+ // If focused item is selected: land on first unselected item after focused item.
|
|
|
+ for (int idx = focused_idx + 1; idx < items_count; idx++)
|
|
|
+ if (!Contains(GetStorageIdFromIndex(idx)))
|
|
|
+ return idx;
|
|
|
+
|
|
|
+ // If focused item is selected: otherwise return last unselected item before focused item.
|
|
|
+ for (int idx = IM_MIN(focused_idx, items_count) - 1; idx >= 0; idx--)
|
|
|
+ if (!Contains(GetStorageIdFromIndex(idx)))
|
|
|
+ return idx;
|
|
|
+
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Rewrite item list (delete items) + update selection.
|
|
|
+ // - Call after EndMultiSelect()
|
|
|
+ // - We cannot provide this logic in core Dear ImGui because we don't have access to your items, nor to selection data.
|
|
|
+ template<typename ITEM_TYPE>
|
|
|
+ void ApplyDeletionPostLoop(ImGuiMultiSelectIO* ms_io, ImVector<ITEM_TYPE>& items, int item_curr_idx_to_select)
|
|
|
+ {
|
|
|
+ // Rewrite item list (delete items) + convert old selection index (before deletion) to new selection index (after selection).
|
|
|
+ // If NavId was not part of selection, we will stay on same item.
|
|
|
+ ImVector<ITEM_TYPE> new_items;
|
|
|
+ new_items.reserve(items.Size - Size);
|
|
|
+ int item_next_idx_to_select = -1;
|
|
|
+ for (int idx = 0; idx < items.Size; idx++)
|
|
|
+ {
|
|
|
+ if (!Contains(GetStorageIdFromIndex(idx)))
|
|
|
+ new_items.push_back(items[idx]);
|
|
|
+ if (item_curr_idx_to_select == idx)
|
|
|
+ item_next_idx_to_select = new_items.Size - 1;
|
|
|
+ }
|
|
|
+ items.swap(new_items);
|
|
|
+
|
|
|
+ // Update selection
|
|
|
+ Clear();
|
|
|
+ if (item_next_idx_to_select != -1 && ms_io->NavIdSelected)
|
|
|
+ SetItemSelected(GetStorageIdFromIndex(item_next_idx_to_select), true);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// Example: Implement dual list box storage and interface
|
|
|
+struct ExampleDualListBox
|
|
|
+{
|
|
|
+ ImVector<ImGuiID> Items[2]; // ID is index into ExampleName[]
|
|
|
+ ImGuiSelectionBasicStorage Selections[2]; // Store ExampleItemId into selection
|
|
|
+ bool OptKeepSorted = true;
|
|
|
+
|
|
|
+ void MoveAll(int src, int dst)
|
|
|
+ {
|
|
|
+ IM_ASSERT((src == 0 && dst == 1) || (src == 1 && dst == 0));
|
|
|
+ for (ImGuiID item_id : Items[src])
|
|
|
+ Items[dst].push_back(item_id);
|
|
|
+ Items[src].clear();
|
|
|
+ SortItems(dst);
|
|
|
+ Selections[src].Swap(Selections[dst]);
|
|
|
+ Selections[src].Clear();
|
|
|
+ }
|
|
|
+ void MoveSelected(int src, int dst)
|
|
|
+ {
|
|
|
+ for (int src_n = 0; src_n < Items[src].Size; src_n++)
|
|
|
+ {
|
|
|
+ ImGuiID item_id = Items[src][src_n];
|
|
|
+ if (!Selections[src].Contains(item_id))
|
|
|
+ continue;
|
|
|
+ Items[src].erase(&Items[src][src_n]); // FIXME-OPT: Could be implemented more optimally (rebuild src items and swap)
|
|
|
+ Items[dst].push_back(item_id);
|
|
|
+ src_n--;
|
|
|
+ }
|
|
|
+ if (OptKeepSorted)
|
|
|
+ SortItems(dst);
|
|
|
+ Selections[src].Swap(Selections[dst]);
|
|
|
+ Selections[src].Clear();
|
|
|
+ }
|
|
|
+ void ApplySelectionRequests(ImGuiMultiSelectIO* ms_io, int side)
|
|
|
+ {
|
|
|
+ // In this example we store item id in selection (instead of item index)
|
|
|
+ Selections[side].UserData = Items[side].Data;
|
|
|
+ Selections[side].AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { ImGuiID* items = (ImGuiID*)self->UserData; return items[idx]; };
|
|
|
+ Selections[side].ApplyRequests(ms_io);
|
|
|
+ }
|
|
|
+ static int IMGUI_CDECL CompareItemsByValue(const void* lhs, const void* rhs)
|
|
|
+ {
|
|
|
+ const int* a = (const int*)lhs;
|
|
|
+ const int* b = (const int*)rhs;
|
|
|
+ return (*a - *b) > 0 ? +1 : -1;
|
|
|
+ }
|
|
|
+ void SortItems(int n)
|
|
|
+ {
|
|
|
+ qsort(Items[n].Data, (size_t)Items[n].Size, sizeof(Items[n][0]), CompareItemsByValue);
|
|
|
+ }
|
|
|
+ void Show()
|
|
|
+ {
|
|
|
+ //ImGui::Checkbox("Sorted", &OptKeepSorted);
|
|
|
+ if (ImGui::BeginTable("split", 3, ImGuiTableFlags_None))
|
|
|
+ {
|
|
|
+ ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); // Left side
|
|
|
+ ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); // Buttons
|
|
|
+ ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); // Right side
|
|
|
+ ImGui::TableNextRow();
|
|
|
+
|
|
|
+ int request_move_selected = -1;
|
|
|
+ int request_move_all = -1;
|
|
|
+ float child_height_0 = 0.0f;
|
|
|
+ for (int side = 0; side < 2; side++)
|
|
|
+ {
|
|
|
+ // FIXME-MULTISELECT: Dual List Box: Add context menus
|
|
|
+ // FIXME-NAV: Using ImGuiWindowFlags_NavFlattened exhibit many issues.
|
|
|
+ ImVector<ImGuiID>& items = Items[side];
|
|
|
+ ImGuiSelectionBasicStorage& selection = Selections[side];
|
|
|
+
|
|
|
+ ImGui::TableSetColumnIndex((side == 0) ? 0 : 2);
|
|
|
+ ImGui::Text("%s (%d)", (side == 0) ? "Available" : "Basket", items.Size);
|
|
|
+
|
|
|
+ // Submit scrolling range to avoid glitches on moving/deletion
|
|
|
+ const float items_height = ImGui::GetTextLineHeightWithSpacing();
|
|
|
+ ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height));
|
|
|
+
|
|
|
+ bool child_visible;
|
|
|
+ if (side == 0)
|
|
|
+ {
|
|
|
+ // Left child is resizable
|
|
|
+ ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, ImGui::GetFrameHeightWithSpacing() * 4), ImVec2(FLT_MAX, FLT_MAX));
|
|
|
+ child_visible = ImGui::BeginChild("0", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY);
|
|
|
+ child_height_0 = ImGui::GetWindowSize().y;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Right child use same height as left one
|
|
|
+ child_visible = ImGui::BeginChild("1", ImVec2(-FLT_MIN, child_height_0), ImGuiChildFlags_FrameStyle);
|
|
|
+ }
|
|
|
+ if (child_visible)
|
|
|
+ {
|
|
|
+ ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_None;
|
|
|
+ ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size);
|
|
|
+ ApplySelectionRequests(ms_io, side);
|
|
|
+
|
|
|
+ for (int item_n = 0; item_n < items.Size; item_n++)
|
|
|
+ {
|
|
|
+ ImGuiID item_id = items[item_n];
|
|
|
+ bool item_is_selected = selection.Contains(item_id);
|
|
|
+ ImGui::SetNextItemSelectionUserData(item_n);
|
|
|
+ ImGui::Selectable(ExampleNames[item_id], item_is_selected, ImGuiSelectableFlags_AllowDoubleClick);
|
|
|
+ if (ImGui::IsItemFocused())
|
|
|
+ {
|
|
|
+ // FIXME-MULTISELECT: Dual List Box: Transfer focus
|
|
|
+ if (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))
|
|
|
+ request_move_selected = side;
|
|
|
+ if (ImGui::IsMouseDoubleClicked(0)) // FIXME-MULTISELECT: Double-click on multi-selection?
|
|
|
+ request_move_selected = side;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ms_io = ImGui::EndMultiSelect();
|
|
|
+ ApplySelectionRequests(ms_io, side);
|
|
|
+ }
|
|
|
+ ImGui::EndChild();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Buttons columns
|
|
|
+ ImGui::TableSetColumnIndex(1);
|
|
|
+ ImGui::NewLine();
|
|
|
+ //ImVec2 button_sz = { ImGui::CalcTextSize(">>").x + ImGui::GetStyle().FramePadding.x * 2.0f, ImGui::GetFrameHeight() + padding.y * 2.0f };
|
|
|
+ ImVec2 button_sz = { ImGui::GetFrameHeight(), ImGui::GetFrameHeight() };
|
|
|
+
|
|
|
+ // (Using BeginDisabled()/EndDisabled() works but feels distracting given how it is currently visualized)
|
|
|
+ if (ImGui::Button(">>", button_sz))
|
|
|
+ request_move_all = 0;
|
|
|
+ if (ImGui::Button(">", button_sz))
|
|
|
+ request_move_selected = 0;
|
|
|
+ if (ImGui::Button("<", button_sz))
|
|
|
+ request_move_selected = 1;
|
|
|
+ if (ImGui::Button("<<", button_sz))
|
|
|
+ request_move_all = 1;
|
|
|
+
|
|
|
+ // Process requests
|
|
|
+ if (request_move_all != -1)
|
|
|
+ MoveAll(request_move_all, request_move_all ^ 1);
|
|
|
+ if (request_move_selected != -1)
|
|
|
+ MoveSelected(request_move_selected, request_move_selected ^ 1);
|
|
|
+
|
|
|
+ // FIXME-MULTISELECT: Support action from outside
|
|
|
+ /*
|
|
|
+ if (OptKeepSorted == false)
|
|
|
+ {
|
|
|
+ ImGui::NewLine();
|
|
|
+ if (ImGui::ArrowButton("MoveUp", ImGuiDir_Up)) {}
|
|
|
+ if (ImGui::ArrowButton("MoveDown", ImGuiDir_Down)) {}
|
|
|
+ }
|
|
|
+ */
|
|
|
+
|
|
|
+ ImGui::EndTable();
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// Multi-selection demos
|
|
|
+// Also read: https://github.com/ocornut/imgui/wiki/Multi-Select
|
|
|
+static void ShowDemoWindowMultiSelect()
|
|
|
+{
|
|
|
+ IMGUI_DEMO_MARKER("Widgets/Selection State & Multi-Select");
|
|
|
+ if (ImGui::TreeNode("Selection State & Multi-Select"))
|
|
|
+ {
|
|
|
+ HelpMarker("Selections can be built using Selectable(), TreeNode() or other widgets. Selection state is owned by application code/data.");
|
|
|
+
|
|
|
+ // Without any fancy API: manage single-selection yourself.
|
|
|
+ IMGUI_DEMO_MARKER("Widgets/Selection State/Single-Select");
|
|
|
+ if (ImGui::TreeNode("Single-Select"))
|
|
|
+ {
|
|
|
+ static int selected = -1;
|
|
|
+ for (int n = 0; n < 5; n++)
|
|
|
+ {
|
|
|
+ char buf[32];
|
|
|
+ sprintf(buf, "Object %d", n);
|
|
|
+ if (ImGui::Selectable(buf, selected == n))
|
|
|
+ selected = n;
|
|
|
+ }
|
|
|
+ ImGui::TreePop();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Demonstrate implementation a most-basic form of multi-selection manually
|
|
|
+ // This doesn't support the SHIFT modifier which requires BeginMultiSelect()!
|
|
|
+ IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (manual/simplified, without BeginMultiSelect)");
|
|
|
+ if (ImGui::TreeNode("Multi-Select (manual/simplified, without BeginMultiSelect)"))
|
|
|
+ {
|
|
|
+ HelpMarker("Hold CTRL and click to select multiple items.");
|
|
|
+ static bool selection[5] = { false, false, false, false, false };
|
|
|
+ for (int n = 0; n < 5; n++)
|
|
|
+ {
|
|
|
+ char buf[32];
|
|
|
+ sprintf(buf, "Object %d", n);
|
|
|
+ if (ImGui::Selectable(buf, selection[n]))
|
|
|
+ {
|
|
|
+ if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held
|
|
|
+ memset(selection, 0, sizeof(selection));
|
|
|
+ selection[n] ^= 1; // Toggle current item
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ImGui::TreePop();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Demonstrate handling proper multi-selection using the BeginMultiSelect/EndMultiSelect API.
|
|
|
+ // SHIFT+Click w/ CTRL and other standard features are supported.
|
|
|
+ // We use the ImGuiSelectionBasicStorage helper which you may freely reimplement.
|
|
|
+ IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select");
|
|
|
+ if (ImGui::TreeNode("Multi-Select"))
|
|
|
+ {
|
|
|
+ ImGui::Text("Supported features:");
|
|
|
+ ImGui::BulletText("Keyboard navigation (arrows, page up/down, home/end, space).");
|
|
|
+ ImGui::BulletText("Ctrl modifier to preserve and toggle selection.");
|
|
|
+ ImGui::BulletText("Shift modifier for range selection.");
|
|
|
+ ImGui::BulletText("CTRL+A to select all.");
|
|
|
+ ImGui::BulletText("Escape to clear selection.");
|
|
|
+ ImGui::BulletText("Click and drag to box-select.");
|
|
|
+ ImGui::Text("Tip: Use 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen.");
|
|
|
+
|
|
|
+ // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection
|
|
|
+ const int ITEMS_COUNT = 50;
|
|
|
+ static ImGuiSelectionBasicStorage selection;
|
|
|
+ ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT);
|
|
|
+
|
|
|
+ // The BeginChild() has no purpose for selection logic, other that offering a scrolling region.
|
|
|
+ if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY))
|
|
|
+ {
|
|
|
+ ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;
|
|
|
+ ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT);
|
|
|
+ selection.ApplyRequests(ms_io);
|
|
|
+
|
|
|
+ for (int n = 0; n < ITEMS_COUNT; n++)
|
|
|
+ {
|
|
|
+ char label[64];
|
|
|
+ sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]);
|
|
|
+ bool item_is_selected = selection.Contains((ImGuiID)n);
|
|
|
+ ImGui::SetNextItemSelectionUserData(n);
|
|
|
+ ImGui::Selectable(label, item_is_selected);
|
|
|
+ }
|
|
|
+
|
|
|
+ ms_io = ImGui::EndMultiSelect();
|
|
|
+ selection.ApplyRequests(ms_io);
|
|
|
+ }
|
|
|
+ ImGui::EndChild();
|
|
|
+ ImGui::TreePop();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Demonstrate using the clipper with BeginMultiSelect()/EndMultiSelect()
|
|
|
+ IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (with clipper)");
|
|
|
+ if (ImGui::TreeNode("Multi-Select (with clipper)"))
|
|
|
+ {
|
|
|
+ // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection
|
|
|
+ static ImGuiSelectionBasicStorage selection;
|
|
|
+
|
|
|
+ ImGui::Text("Added features:");
|
|
|
+ ImGui::BulletText("Using ImGuiListClipper.");
|
|
|
+
|
|
|
+ const int ITEMS_COUNT = 10000;
|
|
|
+ ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT);
|
|
|
+ if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY))
|
|
|
+ {
|
|
|
+ ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;
|
|
|
+ ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT);
|
|
|
+ selection.ApplyRequests(ms_io);
|
|
|
+
|
|
|
+ ImGuiListClipper clipper;
|
|
|
+ clipper.Begin(ITEMS_COUNT);
|
|
|
+ if (ms_io->RangeSrcItem != -1)
|
|
|
+ clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped.
|
|
|
+ while (clipper.Step())
|
|
|
+ {
|
|
|
+ for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++)
|
|
|
+ {
|
|
|
+ char label[64];
|
|
|
+ sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]);
|
|
|
+ bool item_is_selected = selection.Contains((ImGuiID)n);
|
|
|
+ ImGui::SetNextItemSelectionUserData(n);
|
|
|
+ ImGui::Selectable(label, item_is_selected);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ms_io = ImGui::EndMultiSelect();
|
|
|
+ selection.ApplyRequests(ms_io);
|
|
|
+ }
|
|
|
+ ImGui::EndChild();
|
|
|
+ ImGui::TreePop();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Demonstrate dynamic item list + deletion support using the BeginMultiSelect/EndMultiSelect API.
|
|
|
+ // In order to support Deletion without any glitches you need to:
|
|
|
+ // - (1) If items are submitted in their own scrolling area, submit contents size SetNextWindowContentSize() ahead of time to prevent one-frame readjustment of scrolling.
|
|
|
+ // - (2) Items needs to have persistent ID Stack identifier = ID needs to not depends on their index. PushID(index) = KO. PushID(item_id) = OK. This is in order to focus items reliably after a selection.
|
|
|
+ // - (3) BeginXXXX process
|
|
|
+ // - (4) Focus process
|
|
|
+ // - (5) EndXXXX process
|
|
|
+ IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (with deletion)");
|
|
|
+ if (ImGui::TreeNode("Multi-Select (with deletion)"))
|
|
|
+ {
|
|
|
+ // Storing items data separately from selection data.
|
|
|
+ // (you may decide to store selection data inside your item (aka intrusive storage) if you don't need multiple views over same items)
|
|
|
+ // Use a custom selection.Adapter: store item identifier in Selection (instead of index)
|
|
|
+ static ImVector<ImGuiID> items;
|
|
|
+ static ExampleSelectionWithDeletion selection;
|
|
|
+ selection.UserData = (void*)&items;
|
|
|
+ selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { ImVector<ImGuiID>* p_items = (ImVector<ImGuiID>*)self->UserData; return (*p_items)[idx]; }; // Index -> ID
|
|
|
+
|
|
|
+ ImGui::Text("Added features:");
|
|
|
+ ImGui::BulletText("Dynamic list with Delete key support.");
|
|
|
+ ImGui::Text("Selection size: %d/%d", selection.Size, items.Size);
|
|
|
+
|
|
|
+ // Initialize default list with 50 items + button to add/remove items.
|
|
|
+ static ImGuiID items_next_id = 0;
|
|
|
+ if (items_next_id == 0)
|
|
|
+ for (ImGuiID n = 0; n < 50; n++)
|
|
|
+ items.push_back(items_next_id++);
|
|
|
+ if (ImGui::SmallButton("Add 20 items")) { for (int n = 0; n < 20; n++) { items.push_back(items_next_id++); } }
|
|
|
+ ImGui::SameLine();
|
|
|
+ if (ImGui::SmallButton("Remove 20 items")) { for (int n = IM_MIN(20, items.Size); n > 0; n--) { selection.SetItemSelected(items.back(), false); items.pop_back(); } }
|
|
|
+
|
|
|
+ // (1) Extra to support deletion: Submit scrolling range to avoid glitches on deletion
|
|
|
+ const float items_height = ImGui::GetTextLineHeightWithSpacing();
|
|
|
+ ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height));
|
|
|
+
|
|
|
+ if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY))
|
|
|
+ {
|
|
|
+ ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;
|
|
|
+ ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size);
|
|
|
+ selection.ApplyRequests(ms_io);
|
|
|
+
|
|
|
+ const bool want_delete = ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (selection.Size > 0);
|
|
|
+ const int item_curr_idx_to_focus = want_delete ? selection.ApplyDeletionPreLoop(ms_io, items.Size) : -1;
|
|
|
+
|
|
|
+ for (int n = 0; n < items.Size; n++)
|
|
|
+ {
|
|
|
+ const ImGuiID item_id = items[n];
|
|
|
+ char label[64];
|
|
|
+ sprintf(label, "Object %05u: %s", item_id, ExampleNames[item_id % IM_ARRAYSIZE(ExampleNames)]);
|
|
|
+
|
|
|
+ bool item_is_selected = selection.Contains(item_id);
|
|
|
+ ImGui::SetNextItemSelectionUserData(n);
|
|
|
+ ImGui::Selectable(label, item_is_selected);
|
|
|
+ if (item_curr_idx_to_focus == n)
|
|
|
+ ImGui::SetKeyboardFocusHere(-1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Apply multi-select requests
|
|
|
+ ms_io = ImGui::EndMultiSelect();
|
|
|
+ selection.ApplyRequests(ms_io);
|
|
|
+ if (want_delete)
|
|
|
+ selection.ApplyDeletionPostLoop(ms_io, items, item_curr_idx_to_focus);
|
|
|
+ }
|
|
|
+ ImGui::EndChild();
|
|
|
+ ImGui::TreePop();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Implement a Dual List Box (#6648)
|
|
|
+ IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (dual list box)");
|
|
|
+ if (ImGui::TreeNode("Multi-Select (dual list box)"))
|
|
|
+ {
|
|
|
+ // Init default state
|
|
|
+ static ExampleDualListBox dlb;
|
|
|
+ if (dlb.Items[0].Size == 0 && dlb.Items[1].Size == 0)
|
|
|
+ for (int item_id = 0; item_id < IM_ARRAYSIZE(ExampleNames); item_id++)
|
|
|
+ dlb.Items[0].push_back((ImGuiID)item_id);
|
|
|
+
|
|
|
+ // Show
|
|
|
+ dlb.Show();
|
|
|
+
|
|
|
+ ImGui::TreePop();
|
|
|
+ }
|
|
|
+
|
|
|
+ IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (checkboxes)");
|
|
|
+ if (ImGui::TreeNode("Multi-Select (checkboxes)"))
|
|
|
+ {
|
|
|
+ ImGui::Text("In a list of checkboxes (not selectable):");
|
|
|
+ ImGui::BulletText("Using _NoAutoSelect + _NoAutoClear flags.");
|
|
|
+ ImGui::BulletText("Shift+Click to check multiple boxes.");
|
|
|
+ ImGui::BulletText("Shift+Keyboard to copy current value to other boxes.");
|
|
|
+
|
|
|
+ // If you have an array of checkboxes, you may want to use NoAutoSelect + NoAutoClear and the ImGuiSelectionExternalStorage helper.
|
|
|
+ static bool items[20] = {};
|
|
|
+ static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_NoAutoSelect | ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_ClearOnEscape;
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoSelect", &flags, ImGuiMultiSelectFlags_NoAutoSelect);
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoClear", &flags, ImGuiMultiSelectFlags_NoAutoClear);
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect2d", &flags, ImGuiMultiSelectFlags_BoxSelect2d); // Cannot use ImGuiMultiSelectFlags_BoxSelect1d as checkboxes are varying width.
|
|
|
+
|
|
|
+ if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeY))
|
|
|
+ {
|
|
|
+ ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, -1, IM_ARRAYSIZE(items));
|
|
|
+ ImGuiSelectionExternalStorage storage_wrapper;
|
|
|
+ storage_wrapper.UserData = (void*)items;
|
|
|
+ storage_wrapper.AdapterSetItemSelected = [](ImGuiSelectionExternalStorage* self, int n, bool selected) { bool* array = (bool*)self->UserData; array[n] = selected; };
|
|
|
+ storage_wrapper.ApplyRequests(ms_io);
|
|
|
+ for (int n = 0; n < 20; n++)
|
|
|
+ {
|
|
|
+ char label[32];
|
|
|
+ sprintf(label, "Item %d", n);
|
|
|
+ ImGui::SetNextItemSelectionUserData(n);
|
|
|
+ ImGui::Checkbox(label, &items[n]);
|
|
|
+ }
|
|
|
+ ms_io = ImGui::EndMultiSelect();
|
|
|
+ storage_wrapper.ApplyRequests(ms_io);
|
|
|
+ }
|
|
|
+ ImGui::EndChild();
|
|
|
+
|
|
|
+ ImGui::TreePop();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Demonstrate individual selection scopes in same window
|
|
|
+ IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (multiple scopes)");
|
|
|
+ if (ImGui::TreeNode("Multi-Select (multiple scopes)"))
|
|
|
+ {
|
|
|
+ // Use default select: Pass index to SetNextItemSelectionUserData(), store index in Selection
|
|
|
+ const int SCOPES_COUNT = 3;
|
|
|
+ const int ITEMS_COUNT = 8; // Per scope
|
|
|
+ static ImGuiSelectionBasicStorage selections_data[SCOPES_COUNT];
|
|
|
+
|
|
|
+ // Use ImGuiMultiSelectFlags_ScopeRect to not affect other selections in same window.
|
|
|
+ static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ScopeRect | ImGuiMultiSelectFlags_ClearOnEscape;// | ImGuiMultiSelectFlags_ClearOnClickVoid;
|
|
|
+ if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeWindow", &flags, ImGuiMultiSelectFlags_ScopeWindow) && (flags & ImGuiMultiSelectFlags_ScopeWindow))
|
|
|
+ flags &= ~ImGuiMultiSelectFlags_ScopeRect;
|
|
|
+ if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeRect", &flags, ImGuiMultiSelectFlags_ScopeRect) && (flags & ImGuiMultiSelectFlags_ScopeRect))
|
|
|
+ flags &= ~ImGuiMultiSelectFlags_ScopeWindow;
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnClickVoid", &flags, ImGuiMultiSelectFlags_ClearOnClickVoid);
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect1d", &flags, ImGuiMultiSelectFlags_BoxSelect1d);
|
|
|
+
|
|
|
+ for (int selection_scope_n = 0; selection_scope_n < SCOPES_COUNT; selection_scope_n++)
|
|
|
+ {
|
|
|
+ ImGui::PushID(selection_scope_n);
|
|
|
+ ImGuiSelectionBasicStorage* selection = &selections_data[selection_scope_n];
|
|
|
+ ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection->Size, ITEMS_COUNT);
|
|
|
+ selection->ApplyRequests(ms_io);
|
|
|
+
|
|
|
+ ImGui::SeparatorText("Selection scope");
|
|
|
+ ImGui::Text("Selection size: %d/%d", selection->Size, ITEMS_COUNT);
|
|
|
+
|
|
|
+ for (int n = 0; n < ITEMS_COUNT; n++)
|
|
|
+ {
|
|
|
+ char label[64];
|
|
|
+ sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]);
|
|
|
+ bool item_is_selected = selection->Contains((ImGuiID)n);
|
|
|
+ ImGui::SetNextItemSelectionUserData(n);
|
|
|
+ ImGui::Selectable(label, item_is_selected);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Apply multi-select requests
|
|
|
+ ms_io = ImGui::EndMultiSelect();
|
|
|
+ selection->ApplyRequests(ms_io);
|
|
|
+ ImGui::PopID();
|
|
|
+ }
|
|
|
+ ImGui::TreePop();
|
|
|
+ }
|
|
|
+
|
|
|
+ // See ShowExampleAppAssetsBrowser()
|
|
|
+ if (ImGui::TreeNode("Multi-Select (tiled assets browser)"))
|
|
|
+ {
|
|
|
+ ImGui::BulletText("See 'Examples->Assets Browser' in menu");
|
|
|
+ ImGui::TreePop();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Advanced demonstration of BeginMultiSelect()
|
|
|
+ // - Showcase clipping.
|
|
|
+ // - Showcase deletion.
|
|
|
+ // - Showcase basic drag and drop.
|
|
|
+ // - Showcase TreeNode variant (note that tree node don't expand in the demo: supporting expanding tree nodes + clipping a separate thing).
|
|
|
+ // - Showcase using inside a table.
|
|
|
+ IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (advanced)");
|
|
|
+ //ImGui::SetNextItemOpen(true, ImGuiCond_Once);
|
|
|
+ if (ImGui::TreeNode("Multi-Select (advanced)"))
|
|
|
+ {
|
|
|
+ // Options
|
|
|
+ enum WidgetType { WidgetType_Selectable, WidgetType_TreeNode };
|
|
|
+ static bool use_clipper = true;
|
|
|
+ static bool use_deletion = true;
|
|
|
+ static bool use_drag_drop = true;
|
|
|
+ static bool show_in_table = false;
|
|
|
+ static bool show_color_button = false;
|
|
|
+ static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;
|
|
|
+ static WidgetType widget_type = WidgetType_Selectable;
|
|
|
+
|
|
|
+ if (ImGui::TreeNode("Options"))
|
|
|
+ {
|
|
|
+ if (ImGui::RadioButton("Selectables", widget_type == WidgetType_Selectable)) { widget_type = WidgetType_Selectable; }
|
|
|
+ ImGui::SameLine();
|
|
|
+ if (ImGui::RadioButton("Tree nodes", widget_type == WidgetType_TreeNode)) { widget_type = WidgetType_TreeNode; }
|
|
|
+ ImGui::SameLine();
|
|
|
+ HelpMarker("TreeNode() is technically supported but... using this correctly is more complicated (you need some sort of linear/random access to your tree, which is suited to advanced trees setups already implementing filters and clipper. We will work toward simplifying and demoing this.\n\nFor now the tree demo is actually a little bit meaningless because it is an empty tree with only root nodes.");
|
|
|
+ ImGui::Checkbox("Enable clipper", &use_clipper);
|
|
|
+ ImGui::Checkbox("Enable deletion", &use_deletion);
|
|
|
+ ImGui::Checkbox("Enable drag & drop", &use_drag_drop);
|
|
|
+ ImGui::Checkbox("Show in a table", &show_in_table);
|
|
|
+ ImGui::Checkbox("Show color button", &show_color_button);
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SingleSelect", &flags, ImGuiMultiSelectFlags_SingleSelect);
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoSelectAll", &flags, ImGuiMultiSelectFlags_NoSelectAll);
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoRangeSelect", &flags, ImGuiMultiSelectFlags_NoRangeSelect);
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoSelect", &flags, ImGuiMultiSelectFlags_NoAutoSelect);
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoClear", &flags, ImGuiMultiSelectFlags_NoAutoClear);
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoClearOnReselect", &flags, ImGuiMultiSelectFlags_NoAutoClearOnReselect);
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect1d", &flags, ImGuiMultiSelectFlags_BoxSelect1d);
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect2d", &flags, ImGuiMultiSelectFlags_BoxSelect2d);
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelectNoScroll", &flags, ImGuiMultiSelectFlags_BoxSelectNoScroll);
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnEscape", &flags, ImGuiMultiSelectFlags_ClearOnEscape);
|
|
|
+ ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnClickVoid", &flags, ImGuiMultiSelectFlags_ClearOnClickVoid);
|
|
|
+ if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeWindow", &flags, ImGuiMultiSelectFlags_ScopeWindow) && (flags & ImGuiMultiSelectFlags_ScopeWindow))
|
|
|
+ flags &= ~ImGuiMultiSelectFlags_ScopeRect;
|
|
|
+ if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeRect", &flags, ImGuiMultiSelectFlags_ScopeRect) && (flags & ImGuiMultiSelectFlags_ScopeRect))
|
|
|
+ flags &= ~ImGuiMultiSelectFlags_ScopeWindow;
|
|
|
+ if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClick", &flags, ImGuiMultiSelectFlags_SelectOnClick) && (flags & ImGuiMultiSelectFlags_SelectOnClick))
|
|
|
+ flags &= ~ImGuiMultiSelectFlags_SelectOnClickRelease;
|
|
|
+ if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClickRelease", &flags, ImGuiMultiSelectFlags_SelectOnClickRelease) && (flags & ImGuiMultiSelectFlags_SelectOnClickRelease))
|
|
|
+ flags &= ~ImGuiMultiSelectFlags_SelectOnClick;
|
|
|
+ ImGui::SameLine(); HelpMarker("Allow dragging an unselected item without altering selection.");
|
|
|
+ ImGui::TreePop();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Initialize default list with 1000 items.
|
|
|
+ // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection
|
|
|
+ static ImVector<int> items;
|
|
|
+ static int items_next_id = 0;
|
|
|
+ if (items_next_id == 0) { for (int n = 0; n < 1000; n++) { items.push_back(items_next_id++); } }
|
|
|
+ static ExampleSelectionWithDeletion selection;
|
|
|
+ static bool request_deletion_from_menu = false; // Queue deletion triggered from context menu
|
|
|
+
|
|
|
+ ImGui::Text("Selection size: %d/%d", selection.Size, items.Size);
|
|
|
+
|
|
|
+ const float items_height = (widget_type == WidgetType_TreeNode) ? ImGui::GetTextLineHeight() : ImGui::GetTextLineHeightWithSpacing();
|
|
|
+ ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height));
|
|
|
+ if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY))
|
|
|
+ {
|
|
|
+ ImVec2 color_button_sz(ImGui::GetFontSize(), ImGui::GetFontSize());
|
|
|
+ if (widget_type == WidgetType_TreeNode)
|
|
|
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f));
|
|
|
+
|
|
|
+ ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size);
|
|
|
+ selection.ApplyRequests(ms_io);
|
|
|
+
|
|
|
+ const bool want_delete = (ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (selection.Size > 0)) || request_deletion_from_menu;
|
|
|
+ const int item_curr_idx_to_focus = want_delete ? selection.ApplyDeletionPreLoop(ms_io, items.Size) : -1;
|
|
|
+ request_deletion_from_menu = false;
|
|
|
+
|
|
|
+ if (show_in_table)
|
|
|
+ {
|
|
|
+ if (widget_type == WidgetType_TreeNode)
|
|
|
+ ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0.0f, 0.0f));
|
|
|
+ ImGui::BeginTable("##Split", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_NoPadOuterX);
|
|
|
+ ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.70f);
|
|
|
+ ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.30f);
|
|
|
+ //ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f));
|
|
|
+ }
|
|
|
+
|
|
|
+ ImGuiListClipper clipper;
|
|
|
+ if (use_clipper)
|
|
|
+ {
|
|
|
+ clipper.Begin(items.Size);
|
|
|
+ if (item_curr_idx_to_focus != -1)
|
|
|
+ clipper.IncludeItemByIndex(item_curr_idx_to_focus); // Ensure focused item is not clipped.
|
|
|
+ if (ms_io->RangeSrcItem != -1)
|
|
|
+ clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped.
|
|
|
+ }
|
|
|
+
|
|
|
+ while (!use_clipper || clipper.Step())
|
|
|
+ {
|
|
|
+ const int item_begin = use_clipper ? clipper.DisplayStart : 0;
|
|
|
+ const int item_end = use_clipper ? clipper.DisplayEnd : items.Size;
|
|
|
+ for (int n = item_begin; n < item_end; n++)
|
|
|
+ {
|
|
|
+ if (show_in_table)
|
|
|
+ ImGui::TableNextColumn();
|
|
|
+
|
|
|
+ const int item_id = items[n];
|
|
|
+ const char* item_category = ExampleNames[item_id % IM_ARRAYSIZE(ExampleNames)];
|
|
|
+ char label[64];
|
|
|
+ sprintf(label, "Object %05d: %s", item_id, item_category);
|
|
|
+
|
|
|
+ // IMPORTANT: for deletion refocus to work we need object ID to be stable,
|
|
|
+ // aka not depend on their index in the list. Here we use our persistent item_id
|
|
|
+ // instead of index to build a unique ID that will persist.
|
|
|
+ // (If we used PushID(index) instead, focus wouldn't be restored correctly after deletion).
|
|
|
+ ImGui::PushID(item_id);
|
|
|
+
|
|
|
+ // Emit a color button, to test that Shift+LeftArrow landing on an item that is not part
|
|
|
+ // of the selection scope doesn't erroneously alter our selection.
|
|
|
+ if (show_color_button)
|
|
|
+ {
|
|
|
+ ImU32 dummy_col = (ImU32)((unsigned int)n * 0xC250B74B) | IM_COL32_A_MASK;
|
|
|
+ ImGui::ColorButton("##", ImColor(dummy_col), ImGuiColorEditFlags_NoTooltip, color_button_sz);
|
|
|
+ ImGui::SameLine();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Submit item
|
|
|
+ bool item_is_selected = selection.Contains((ImGuiID)n);
|
|
|
+ bool item_is_open = false;
|
|
|
+ ImGui::SetNextItemSelectionUserData(n);
|
|
|
+ if (widget_type == WidgetType_Selectable)
|
|
|
+ {
|
|
|
+ ImGui::Selectable(label, item_is_selected, ImGuiSelectableFlags_None);
|
|
|
+ }
|
|
|
+ else if (widget_type == WidgetType_TreeNode)
|
|
|
+ {
|
|
|
+ ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
|
|
|
+ if (item_is_selected)
|
|
|
+ tree_node_flags |= ImGuiTreeNodeFlags_Selected;
|
|
|
+ item_is_open = ImGui::TreeNodeEx(label, tree_node_flags);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Focus (for after deletion)
|
|
|
+ if (item_curr_idx_to_focus == n)
|
|
|
+ ImGui::SetKeyboardFocusHere(-1);
|
|
|
+
|
|
|
+ // Drag and Drop
|
|
|
+ if (use_drag_drop && ImGui::BeginDragDropSource())
|
|
|
+ {
|
|
|
+ // Create payload with full selection OR single unselected item.
|
|
|
+ // (the later is only possible when using ImGuiMultiSelectFlags_SelectOnClickRelease)
|
|
|
+ if (ImGui::GetDragDropPayload() == NULL)
|
|
|
+ {
|
|
|
+ ImVector<int> payload_items;
|
|
|
+ void* it = NULL;
|
|
|
+ ImGuiID id = 0;
|
|
|
+ if (!item_is_selected)
|
|
|
+ payload_items.push_back(item_id);
|
|
|
+ else
|
|
|
+ while (selection.GetNextSelectedItem(&it, &id))
|
|
|
+ payload_items.push_back((int)id);
|
|
|
+ ImGui::SetDragDropPayload("MULTISELECT_DEMO_ITEMS", payload_items.Data, (size_t)payload_items.size_in_bytes());
|
|
|
+ }
|
|
|
+
|
|
|
+ // Display payload content in tooltip
|
|
|
+ const ImGuiPayload* payload = ImGui::GetDragDropPayload();
|
|
|
+ const int* payload_items = (int*)payload->Data;
|
|
|
+ const int payload_count = (int)payload->DataSize / (int)sizeof(int);
|
|
|
+ if (payload_count == 1)
|
|
|
+ ImGui::Text("Object %05d: %s", payload_items[0], ExampleNames[payload_items[0] % IM_ARRAYSIZE(ExampleNames)]);
|
|
|
+ else
|
|
|
+ ImGui::Text("Dragging %d objects", payload_count);
|
|
|
+
|
|
|
+ ImGui::EndDragDropSource();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (widget_type == WidgetType_TreeNode && item_is_open)
|
|
|
+ ImGui::TreePop();
|
|
|
+
|
|
|
+ // Right-click: context menu
|
|
|
+ if (ImGui::BeginPopupContextItem())
|
|
|
+ {
|
|
|
+ ImGui::BeginDisabled(!use_deletion || selection.Size == 0);
|
|
|
+ sprintf(label, "Delete %d item(s)###DeleteSelected", selection.Size);
|
|
|
+ if (ImGui::Selectable(label))
|
|
|
+ request_deletion_from_menu = true;
|
|
|
+ ImGui::EndDisabled();
|
|
|
+ ImGui::Selectable("Close");
|
|
|
+ ImGui::EndPopup();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Demo content within a table
|
|
|
+ if (show_in_table)
|
|
|
+ {
|
|
|
+ ImGui::TableNextColumn();
|
|
|
+ ImGui::SetNextItemWidth(-FLT_MIN);
|
|
|
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
|
|
+ ImGui::InputText("###NoLabel", (char*)(void*)item_category, strlen(item_category), ImGuiInputTextFlags_ReadOnly);
|
|
|
+ ImGui::PopStyleVar();
|
|
|
+ }
|
|
|
+
|
|
|
+ ImGui::PopID();
|
|
|
+ }
|
|
|
+ if (!use_clipper)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (show_in_table)
|
|
|
+ {
|
|
|
+ ImGui::EndTable();
|
|
|
+ if (widget_type == WidgetType_TreeNode)
|
|
|
+ ImGui::PopStyleVar();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Apply multi-select requests
|
|
|
+ ms_io = ImGui::EndMultiSelect();
|
|
|
+ selection.ApplyRequests(ms_io);
|
|
|
+ if (want_delete)
|
|
|
+ selection.ApplyDeletionPostLoop(ms_io, items, item_curr_idx_to_focus);
|
|
|
+
|
|
|
+ if (widget_type == WidgetType_TreeNode)
|
|
|
+ ImGui::PopStyleVar();
|
|
|
+ }
|
|
|
+ ImGui::EndChild();
|
|
|
+ ImGui::TreePop();
|
|
|
+ }
|
|
|
+ ImGui::TreePop();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
static void ShowDemoWindowLayout()
|
|
|
{
|
|
|
IMGUI_DEMO_MARKER("Layout");
|
|
@@ -9188,6 +9928,404 @@ void ShowExampleAppDocuments(bool* p_open)
|
|
|
ImGui::End();
|
|
|
}
|
|
|
|
|
|
+//-----------------------------------------------------------------------------
|
|
|
+// [SECTION] Example App: Assets Browser / ShowExampleAppAssetsBrowser()
|
|
|
+//-----------------------------------------------------------------------------
|
|
|
+
|
|
|
+//#include "imgui_internal.h" // NavMoveRequestTryWrapping()
|
|
|
+
|
|
|
+struct ExampleAsset
|
|
|
+{
|
|
|
+ ImGuiID ID;
|
|
|
+ int Type;
|
|
|
+
|
|
|
+ ExampleAsset(ImGuiID id, int type) { ID = id; Type = type; }
|
|
|
+
|
|
|
+ static const ImGuiTableSortSpecs* s_current_sort_specs;
|
|
|
+
|
|
|
+ static void SortWithSortSpecs(ImGuiTableSortSpecs* sort_specs, ExampleAsset* items, int items_count)
|
|
|
+ {
|
|
|
+ s_current_sort_specs = sort_specs; // Store in variable accessible by the sort function.
|
|
|
+ if (items_count > 1)
|
|
|
+ qsort(items, (size_t)items_count, sizeof(items[0]), ExampleAsset::CompareWithSortSpecs);
|
|
|
+ s_current_sort_specs = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Compare function to be used by qsort()
|
|
|
+ static int IMGUI_CDECL CompareWithSortSpecs(const void* lhs, const void* rhs)
|
|
|
+ {
|
|
|
+ const ExampleAsset* a = (const ExampleAsset*)lhs;
|
|
|
+ const ExampleAsset* b = (const ExampleAsset*)rhs;
|
|
|
+ for (int n = 0; n < s_current_sort_specs->SpecsCount; n++)
|
|
|
+ {
|
|
|
+ const ImGuiTableColumnSortSpecs* sort_spec = &s_current_sort_specs->Specs[n];
|
|
|
+ int delta = 0;
|
|
|
+ if (sort_spec->ColumnIndex == 0)
|
|
|
+ delta = ((int)a->ID - (int)b->ID);
|
|
|
+ else if (sort_spec->ColumnIndex == 1)
|
|
|
+ delta = (a->Type - b->Type);
|
|
|
+ if (delta > 0)
|
|
|
+ return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? +1 : -1;
|
|
|
+ if (delta < 0)
|
|
|
+ return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? -1 : +1;
|
|
|
+ }
|
|
|
+ return ((int)a->ID - (int)b->ID);
|
|
|
+ }
|
|
|
+};
|
|
|
+const ImGuiTableSortSpecs* ExampleAsset::s_current_sort_specs = NULL;
|
|
|
+
|
|
|
+struct ExampleAssetsBrowser
|
|
|
+{
|
|
|
+ // Options
|
|
|
+ bool ShowTypeOverlay = true;
|
|
|
+ bool AllowSorting = true;
|
|
|
+ bool AllowDragUnselected = false;
|
|
|
+ bool AllowBoxSelect = true;
|
|
|
+ float IconSize = 32.0f;
|
|
|
+ int IconSpacing = 10;
|
|
|
+ int IconHitSpacing = 4; // Increase hit-spacing if you want to make it possible to clear or box-select from gaps. Some spacing is required to able to amend with Shift+box-select. Value is small in Explorer.
|
|
|
+ bool StretchSpacing = true;
|
|
|
+
|
|
|
+ // State
|
|
|
+ ImVector<ExampleAsset> Items; // Our items
|
|
|
+ ExampleSelectionWithDeletion Selection; // Our selection (ImGuiSelectionBasicStorage + helper funcs to handle deletion)
|
|
|
+ ImGuiID NextItemId = 0; // Unique identifier when creating new items
|
|
|
+ bool RequestDelete = false; // Deferred deletion request
|
|
|
+ bool RequestSort = false; // Deferred sort request
|
|
|
+ float ZoomWheelAccum = 0.0f; // Mouse wheel accumulator to handle smooth wheels better
|
|
|
+
|
|
|
+ // Calculated sizes for layout, output of UpdateLayoutSizes(). Could be locals but our code is simpler this way.
|
|
|
+ ImVec2 LayoutItemSize;
|
|
|
+ ImVec2 LayoutItemStep; // == LayoutItemSize + LayoutItemSpacing
|
|
|
+ float LayoutItemSpacing = 0.0f;
|
|
|
+ float LayoutSelectableSpacing = 0.0f;
|
|
|
+ float LayoutOuterPadding = 0.0f;
|
|
|
+ int LayoutColumnCount = 0;
|
|
|
+ int LayoutLineCount = 0;
|
|
|
+
|
|
|
+ // Functions
|
|
|
+ ExampleAssetsBrowser()
|
|
|
+ {
|
|
|
+ AddItems(10000);
|
|
|
+ }
|
|
|
+ void AddItems(int count)
|
|
|
+ {
|
|
|
+ if (Items.Size == 0)
|
|
|
+ NextItemId = 0;
|
|
|
+ Items.reserve(Items.Size + count);
|
|
|
+ for (int n = 0; n < count; n++, NextItemId++)
|
|
|
+ Items.push_back(ExampleAsset(NextItemId, (NextItemId % 20) < 15 ? 0 : (NextItemId % 20) < 18 ? 1 : 2));
|
|
|
+ RequestSort = true;
|
|
|
+ }
|
|
|
+ void ClearItems()
|
|
|
+ {
|
|
|
+ Items.clear();
|
|
|
+ Selection.Clear();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Logic would be written in the main code BeginChild() and outputing to local variables.
|
|
|
+ // We extracted it into a function so we can call it easily from multiple places.
|
|
|
+ void UpdateLayoutSizes(float avail_width)
|
|
|
+ {
|
|
|
+ // Layout: when not stretching: allow extending into right-most spacing.
|
|
|
+ LayoutItemSpacing = (float)IconSpacing;
|
|
|
+ if (StretchSpacing == false)
|
|
|
+ avail_width += floorf(LayoutItemSpacing * 0.5f);
|
|
|
+
|
|
|
+ // Layout: calculate number of icon per line and number of lines
|
|
|
+ LayoutItemSize = ImVec2(floorf(IconSize), floorf(IconSize));
|
|
|
+ LayoutColumnCount = IM_MAX((int)(avail_width / (LayoutItemSize.x + LayoutItemSpacing)), 1);
|
|
|
+ LayoutLineCount = (Items.Size + LayoutColumnCount - 1) / LayoutColumnCount;
|
|
|
+
|
|
|
+ // Layout: when stretching: allocate remaining space to more spacing. Round before division, so item_spacing may be non-integer.
|
|
|
+ if (StretchSpacing && LayoutColumnCount > 1)
|
|
|
+ LayoutItemSpacing = floorf(avail_width - LayoutItemSize.x * LayoutColumnCount) / LayoutColumnCount;
|
|
|
+
|
|
|
+ LayoutItemStep = ImVec2(LayoutItemSize.x + LayoutItemSpacing, LayoutItemSize.y + LayoutItemSpacing);
|
|
|
+ LayoutSelectableSpacing = IM_MAX(floorf(LayoutItemSpacing) - IconHitSpacing, 0.0f);
|
|
|
+ LayoutOuterPadding = floorf(LayoutItemSpacing * 0.5f);
|
|
|
+ }
|
|
|
+
|
|
|
+ void Draw(const char* title, bool* p_open)
|
|
|
+ {
|
|
|
+ ImGui::SetNextWindowSize(ImVec2(IconSize * 25, IconSize * 15), ImGuiCond_FirstUseEver);
|
|
|
+ if (!ImGui::Begin(title, p_open, ImGuiWindowFlags_MenuBar))
|
|
|
+ {
|
|
|
+ ImGui::End();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Menu bar
|
|
|
+ if (ImGui::BeginMenuBar())
|
|
|
+ {
|
|
|
+ if (ImGui::BeginMenu("File"))
|
|
|
+ {
|
|
|
+ if (ImGui::MenuItem("Add 10000 items"))
|
|
|
+ AddItems(10000);
|
|
|
+ if (ImGui::MenuItem("Clear items"))
|
|
|
+ ClearItems();
|
|
|
+ ImGui::Separator();
|
|
|
+ if (ImGui::MenuItem("Close", NULL, false, p_open != NULL))
|
|
|
+ *p_open = false;
|
|
|
+ ImGui::EndMenu();
|
|
|
+ }
|
|
|
+ if (ImGui::BeginMenu("Edit"))
|
|
|
+ {
|
|
|
+ if (ImGui::MenuItem("Delete", "Del", false, Selection.Size > 0))
|
|
|
+ RequestDelete = true;
|
|
|
+ ImGui::EndMenu();
|
|
|
+ }
|
|
|
+ if (ImGui::BeginMenu("Options"))
|
|
|
+ {
|
|
|
+ ImGui::PushItemWidth(ImGui::GetFontSize() * 10);
|
|
|
+
|
|
|
+ ImGui::SeparatorText("Contents");
|
|
|
+ ImGui::Checkbox("Show Type Overlay", &ShowTypeOverlay);
|
|
|
+ ImGui::Checkbox("Allow Sorting", &AllowSorting);
|
|
|
+
|
|
|
+ ImGui::SeparatorText("Selection Behavior");
|
|
|
+ ImGui::Checkbox("Allow dragging unselected item", &AllowDragUnselected);
|
|
|
+ ImGui::Checkbox("Allow box-selection", &AllowBoxSelect);
|
|
|
+
|
|
|
+ ImGui::SeparatorText("Layout");
|
|
|
+ ImGui::SliderFloat("Icon Size", &IconSize, 16.0f, 128.0f, "%.0f");
|
|
|
+ ImGui::SameLine(); HelpMarker("Use CTRL+Wheel to zoom");
|
|
|
+ ImGui::SliderInt("Icon Spacing", &IconSpacing, 0, 32);
|
|
|
+ ImGui::SliderInt("Icon Hit Spacing", &IconHitSpacing, 0, 32);
|
|
|
+ ImGui::Checkbox("Stretch Spacing", &StretchSpacing);
|
|
|
+ ImGui::PopItemWidth();
|
|
|
+ ImGui::EndMenu();
|
|
|
+ }
|
|
|
+ ImGui::EndMenuBar();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Show a table with ONLY one header row to showcase the idea/possibility of using this to provide a sorting UI
|
|
|
+ if (AllowSorting)
|
|
|
+ {
|
|
|
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
|
|
+ ImGuiTableFlags table_flags_for_sort_specs = ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Borders;
|
|
|
+ if (ImGui::BeginTable("for_sort_specs_only", 2, table_flags_for_sort_specs, ImVec2(0.0f, ImGui::GetFrameHeight())))
|
|
|
+ {
|
|
|
+ ImGui::TableSetupColumn("Index");
|
|
|
+ ImGui::TableSetupColumn("Type");
|
|
|
+ ImGui::TableHeadersRow();
|
|
|
+ if (ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs())
|
|
|
+ if (sort_specs->SpecsDirty || RequestSort)
|
|
|
+ {
|
|
|
+ ExampleAsset::SortWithSortSpecs(sort_specs, Items.Data, Items.Size);
|
|
|
+ sort_specs->SpecsDirty = RequestSort = false;
|
|
|
+ }
|
|
|
+ ImGui::EndTable();
|
|
|
+ }
|
|
|
+ ImGui::PopStyleVar();
|
|
|
+ }
|
|
|
+
|
|
|
+ ImGuiIO& io = ImGui::GetIO();
|
|
|
+ ImGui::SetNextWindowContentSize(ImVec2(0.0f, LayoutOuterPadding + LayoutLineCount * (LayoutItemSize.x + LayoutItemSpacing)));
|
|
|
+ if (ImGui::BeginChild("Assets", ImVec2(0.0f, -ImGui::GetTextLineHeightWithSpacing()), ImGuiChildFlags_Border, ImGuiWindowFlags_NoMove))
|
|
|
+ {
|
|
|
+ ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
|
|
+
|
|
|
+ const float avail_width = ImGui::GetContentRegionAvail().x;
|
|
|
+ UpdateLayoutSizes(avail_width);
|
|
|
+
|
|
|
+ // Calculate and store start position.
|
|
|
+ ImVec2 start_pos = ImGui::GetCursorScreenPos();
|
|
|
+ start_pos = ImVec2(start_pos.x + LayoutOuterPadding, start_pos.y + LayoutOuterPadding);
|
|
|
+ ImGui::SetCursorScreenPos(start_pos);
|
|
|
+
|
|
|
+ // Multi-select
|
|
|
+ ImGuiMultiSelectFlags ms_flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_ClearOnClickVoid;
|
|
|
+
|
|
|
+ // - Enable box-select (in 2D mode, so that changing box-select rectangle X1/X2 boundaries will affect clipped items)
|
|
|
+ if (AllowBoxSelect)
|
|
|
+ ms_flags |= ImGuiMultiSelectFlags_BoxSelect2d;
|
|
|
+
|
|
|
+ // - This feature allows dragging an unselected item without selecting it (rarely used)
|
|
|
+ if (AllowDragUnselected)
|
|
|
+ ms_flags |= ImGuiMultiSelectFlags_SelectOnClickRelease;
|
|
|
+
|
|
|
+ // - Enable keyboard wrapping on X axis
|
|
|
+ // (FIXME-MULTISELECT: We haven't designed/exposed a general nav wrapping api yet, so this flag is provided as a courtesy to avoid doing:
|
|
|
+ // ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX);
|
|
|
+ // When we finish implementing a more general API for this, we will obsolete this flag in favor of the new system)
|
|
|
+ ms_flags |= ImGuiMultiSelectFlags_NavWrapX;
|
|
|
+
|
|
|
+ ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ms_flags, Selection.Size, Items.Size);
|
|
|
+
|
|
|
+ // Use custom selection adapter: store ID in selection (recommended)
|
|
|
+ Selection.UserData = this;
|
|
|
+ Selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self_, int idx) { ExampleAssetsBrowser* self = (ExampleAssetsBrowser*)self_->UserData; return self->Items[idx].ID; };
|
|
|
+ Selection.ApplyRequests(ms_io);
|
|
|
+
|
|
|
+ const bool want_delete = (ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (Selection.Size > 0)) || RequestDelete;
|
|
|
+ const int item_curr_idx_to_focus = want_delete ? Selection.ApplyDeletionPreLoop(ms_io, Items.Size) : -1;
|
|
|
+ RequestDelete = false;
|
|
|
+
|
|
|
+ // Push LayoutSelectableSpacing (which is LayoutItemSpacing minus hit-spacing, if we decide to have hit gaps between items)
|
|
|
+ // Altering style ItemSpacing may seem unnecessary as we position every items using SetCursorScreenPos()...
|
|
|
+ // But it is necessary for two reasons:
|
|
|
+ // - Selectables uses it by default to visually fill the space between two items.
|
|
|
+ // - The vertical spacing would be measured by Clipper to calculate line height if we didn't provide it explicitly (here we do).
|
|
|
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(LayoutSelectableSpacing, LayoutSelectableSpacing));
|
|
|
+
|
|
|
+ // Rendering parameters
|
|
|
+ const ImU32 icon_type_overlay_colors[3] = { 0, IM_COL32(200, 70, 70, 255), IM_COL32(70, 170, 70, 255) };
|
|
|
+ const ImU32 icon_bg_color = ImGui::GetColorU32(ImGuiCol_MenuBarBg);
|
|
|
+ const ImVec2 icon_type_overlay_size = ImVec2(4.0f, 4.0f);
|
|
|
+ const bool display_label = (LayoutItemSize.x >= ImGui::CalcTextSize("999").x);
|
|
|
+
|
|
|
+ const int column_count = LayoutColumnCount;
|
|
|
+ ImGuiListClipper clipper;
|
|
|
+ clipper.Begin(LayoutLineCount, LayoutItemStep.y);
|
|
|
+ if (item_curr_idx_to_focus != -1)
|
|
|
+ clipper.IncludeItemByIndex(item_curr_idx_to_focus / column_count); // Ensure focused item line is not clipped.
|
|
|
+ if (ms_io->RangeSrcItem != -1)
|
|
|
+ clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem / column_count); // Ensure RangeSrc item line is not clipped.
|
|
|
+ while (clipper.Step())
|
|
|
+ {
|
|
|
+ for (int line_idx = clipper.DisplayStart; line_idx < clipper.DisplayEnd; line_idx++)
|
|
|
+ {
|
|
|
+ const int item_min_idx_for_current_line = line_idx * column_count;
|
|
|
+ const int item_max_idx_for_current_line = IM_MIN((line_idx + 1) * column_count, Items.Size);
|
|
|
+ for (int item_idx = item_min_idx_for_current_line; item_idx < item_max_idx_for_current_line; ++item_idx)
|
|
|
+ {
|
|
|
+ ExampleAsset* item_data = &Items[item_idx];
|
|
|
+ ImGui::PushID((int)item_data->ID);
|
|
|
+
|
|
|
+ // Position item
|
|
|
+ ImVec2 pos = ImVec2(start_pos.x + (item_idx % column_count) * LayoutItemStep.x, start_pos.y + line_idx * LayoutItemStep.y);
|
|
|
+ ImGui::SetCursorScreenPos(pos);
|
|
|
+
|
|
|
+ ImGui::SetNextItemSelectionUserData(item_idx);
|
|
|
+ bool item_is_selected = Selection.Contains((ImGuiID)item_data->ID);
|
|
|
+ bool item_is_visible = ImGui::IsRectVisible(LayoutItemSize);
|
|
|
+ ImGui::Selectable("", item_is_selected, ImGuiSelectableFlags_None, LayoutItemSize);
|
|
|
+
|
|
|
+ // Update our selection state immediately (without waiting for EndMultiSelect() requests)
|
|
|
+ // because we use this to alter the color of our text/icon.
|
|
|
+ if (ImGui::IsItemToggledSelection())
|
|
|
+ item_is_selected = !item_is_selected;
|
|
|
+
|
|
|
+ // Focus (for after deletion)
|
|
|
+ if (item_curr_idx_to_focus == item_idx)
|
|
|
+ ImGui::SetKeyboardFocusHere(-1);
|
|
|
+
|
|
|
+ // Drag and drop
|
|
|
+ if (ImGui::BeginDragDropSource())
|
|
|
+ {
|
|
|
+ // Create payload with full selection OR single unselected item.
|
|
|
+ // (the later is only possible when using ImGuiMultiSelectFlags_SelectOnClickRelease)
|
|
|
+ if (ImGui::GetDragDropPayload() == NULL)
|
|
|
+ {
|
|
|
+ ImVector<ImGuiID> payload_items;
|
|
|
+ void* it = NULL;
|
|
|
+ ImGuiID id = 0;
|
|
|
+ if (!item_is_selected)
|
|
|
+ payload_items.push_back(item_data->ID);
|
|
|
+ else
|
|
|
+ while (Selection.GetNextSelectedItem(&it, &id))
|
|
|
+ payload_items.push_back(id);
|
|
|
+ ImGui::SetDragDropPayload("ASSETS_BROWSER_ITEMS", payload_items.Data, (size_t)payload_items.size_in_bytes());
|
|
|
+ }
|
|
|
+
|
|
|
+ // Display payload content in tooltip, by extracting it from the payload data
|
|
|
+ // (we could read from selection, but it is more correct and reusable to read from payload)
|
|
|
+ const ImGuiPayload* payload = ImGui::GetDragDropPayload();
|
|
|
+ const int payload_count = (int)payload->DataSize / (int)sizeof(ImGuiID);
|
|
|
+ ImGui::Text("%d assets", payload_count);
|
|
|
+
|
|
|
+ ImGui::EndDragDropSource();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Render icon (a real app would likely display an image/thumbnail here)
|
|
|
+ // Because we use ImGuiMultiSelectFlags_BoxSelect2d, clipping vertical may occasionally be larger, so we coarse-clip our rendering as well.
|
|
|
+ if (item_is_visible)
|
|
|
+ {
|
|
|
+ ImVec2 box_min(pos.x - 1, pos.y - 1);
|
|
|
+ ImVec2 box_max(box_min.x + LayoutItemSize.x + 2, box_min.y + LayoutItemSize.y + 2); // Dubious
|
|
|
+ draw_list->AddRectFilled(box_min, box_max, icon_bg_color); // Background color
|
|
|
+ if (ShowTypeOverlay && item_data->Type != 0)
|
|
|
+ {
|
|
|
+ ImU32 type_col = icon_type_overlay_colors[item_data->Type % IM_ARRAYSIZE(icon_type_overlay_colors)];
|
|
|
+ draw_list->AddRectFilled(ImVec2(box_max.x - 2 - icon_type_overlay_size.x, box_min.y + 2), ImVec2(box_max.x - 2, box_min.y + 2 + icon_type_overlay_size.y), type_col);
|
|
|
+ }
|
|
|
+ if (display_label)
|
|
|
+ {
|
|
|
+ ImU32 label_col = ImGui::GetColorU32(item_is_selected ? ImGuiCol_Text : ImGuiCol_TextDisabled);
|
|
|
+ char label[32];
|
|
|
+ sprintf(label, "%d", item_data->ID);
|
|
|
+ draw_list->AddText(ImVec2(box_min.x, box_max.y - ImGui::GetFontSize()), label_col, label);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ImGui::PopID();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ clipper.End();
|
|
|
+ ImGui::PopStyleVar(); // ImGuiStyleVar_ItemSpacing
|
|
|
+
|
|
|
+ // Context menu
|
|
|
+ if (ImGui::BeginPopupContextWindow())
|
|
|
+ {
|
|
|
+ ImGui::Text("Selection: %d items", Selection.Size);
|
|
|
+ ImGui::Separator();
|
|
|
+ if (ImGui::MenuItem("Delete", "Del", false, Selection.Size > 0))
|
|
|
+ RequestDelete = true;
|
|
|
+ ImGui::EndPopup();
|
|
|
+ }
|
|
|
+
|
|
|
+ ms_io = ImGui::EndMultiSelect();
|
|
|
+ Selection.ApplyRequests(ms_io);
|
|
|
+ if (want_delete)
|
|
|
+ Selection.ApplyDeletionPostLoop(ms_io, Items, item_curr_idx_to_focus);
|
|
|
+
|
|
|
+ // Zooming with CTRL+Wheel
|
|
|
+ if (ImGui::IsWindowAppearing())
|
|
|
+ ZoomWheelAccum = 0.0f;
|
|
|
+ if (ImGui::IsWindowHovered() && io.MouseWheel != 0.0f && ImGui::IsKeyDown(ImGuiMod_Ctrl) && ImGui::IsAnyItemActive() == false)
|
|
|
+ {
|
|
|
+ ZoomWheelAccum += io.MouseWheel;
|
|
|
+ if (fabsf(ZoomWheelAccum) >= 1.0f)
|
|
|
+ {
|
|
|
+ // Calculate hovered item index from mouse location
|
|
|
+ // FIXME: Locking aiming on 'hovered_item_idx' (with a cool-down timer) would ensure zoom keeps on it.
|
|
|
+ const float hovered_item_nx = (io.MousePos.x - start_pos.x + LayoutItemSpacing * 0.5f) / LayoutItemStep.x;
|
|
|
+ const float hovered_item_ny = (io.MousePos.y - start_pos.y + LayoutItemSpacing * 0.5f) / LayoutItemStep.y;
|
|
|
+ const int hovered_item_idx = ((int)hovered_item_ny * LayoutColumnCount) + (int)hovered_item_nx;
|
|
|
+ //ImGui::SetTooltip("%f,%f -> item %d", hovered_item_nx, hovered_item_ny, hovered_item_idx); // Move those 4 lines in block above for easy debugging
|
|
|
+
|
|
|
+ // Zoom
|
|
|
+ IconSize *= powf(1.1f, (float)(int)ZoomWheelAccum);
|
|
|
+ IconSize = IM_CLAMP(IconSize, 16.0f, 128.0f);
|
|
|
+ ZoomWheelAccum -= (int)ZoomWheelAccum;
|
|
|
+ UpdateLayoutSizes(avail_width);
|
|
|
+
|
|
|
+ // Manipulate scroll to that we will land at the same Y location of currently hovered item.
|
|
|
+ // - Calculate next frame position of item under mouse
|
|
|
+ // - Set new scroll position to be used in next ImGui::BeginChild() call.
|
|
|
+ float hovered_item_rel_pos_y = ((float)(hovered_item_idx / LayoutColumnCount) + fmodf(hovered_item_ny, 1.0f)) * LayoutItemStep.y;
|
|
|
+ hovered_item_rel_pos_y += ImGui::GetStyle().WindowPadding.y;
|
|
|
+ float mouse_local_y = io.MousePos.y - ImGui::GetWindowPos().y;
|
|
|
+ ImGui::SetScrollY(hovered_item_rel_pos_y - mouse_local_y);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ImGui::EndChild();
|
|
|
+
|
|
|
+ ImGui::Text("Selected: %d/%d items", Selection.Size, Items.Size);
|
|
|
+ ImGui::End();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+void ShowExampleAppAssetsBrowser(bool* p_open)
|
|
|
+{
|
|
|
+ IMGUI_DEMO_MARKER("Examples/Assets Browser");
|
|
|
+ static ExampleAssetsBrowser assets_browser;
|
|
|
+ assets_browser.Draw("Example: Assets Browser", p_open);
|
|
|
+}
|
|
|
+
|
|
|
// End of Demo code
|
|
|
#else
|
|
|
|