1
0
mirror of https://github.com/ocornut/imgui.git synced 2024-11-30 18:34:34 +01:00

MultiSelect: Comments, tweaks.

+ Alignment to reduce noise on next commit.
This commit is contained in:
ocornut 2023-08-07 12:34:20 +02:00
parent 847b1dde8c
commit 140a2f0565
3 changed files with 44 additions and 39 deletions

75
imgui.h
View File

@ -2737,21 +2737,23 @@ enum ImGuiMultiSelectFlags_
}; };
// Multi-selection system // Multi-selection system
// - This system implements standard multi-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc) in a way that // - Refer to 'Demo->Widgets->Selection State' for references using this.
// allow a clipper to be used (so most non-visible items won't be submitted). Handling this correctly is tricky, this is why // - This system implements standard multi-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc)
// we provide the functionality. Note however that if you don't need SHIFT+Mouse/Keyboard range-select + clipping, you could use // and supports a clipper being used. Handling this manually may be tricky, this is why we provide the functionality.
// a simpler form of multi-selection yourself, by reacting to click/presses on Selectable() items and checking keyboard modifiers. // If you don't need SHIFT+Mouse/Keyboard range-select + clipping, you can use a simpler form of multi-selection yourself,
// The unusual complexity of this system is mostly caused by supporting SHIFT+Click/Arrow range-select with clipped elements. // by reacting to click/presses on Selectable() items and checking keyboard modifiers.
// - In the spirit of Dear ImGui design, your code owns the selection data. // The complexity of this system is mostly caused by supporting SHIFT+Click/Arrow range-select with clipped elements.
// So this is designed to handle all kind of selection data: e.g. instructive selection (store a bool inside each object), // - TreeNode() and Selectable() are supported but custom widgets may use it as well.
// external array (store an array aside from your objects), hash/map/set (store only selected items in a hash/map/set), // - In the spirit of Dear ImGui design, your code owns actual selection data.
// or other structures (store indices in an interval tree), etc. // This is designed to allow all kinds of selection storage you may use in your application:
// - TreeNode() and Selectable() are supported. // e.g. instructive selection (store a bool inside each object), external array (store an array in your view data, next
// - The work involved to deal with multi-selection differs whether you want to only submit visible items (and clip others) or submit all items // to your objects), set/map/hash (store only selected items), or other structures (store indices in an interval tree), etc.
// regardless of their visibility. Clipping items is more efficient and will allow you to deal with large lists (1k~100k items) with near zero // - The work involved to deal with multi-selection differs whether you want to only submit visible items and clip others,
// performance penalty, but requires a little more work on the code. If you only have a few hundreds elements in your possible selection set, // or submit all items regardless of their visibility. Clipping items is more efficient and will allow you to deal with
// you may as well not bother with clipping, as the cost should be negligible (as least on Dear ImGui side). // large lists (1k~100k items) with no performance penalty, but requires a little more work on the code.
// If you are not sure, always start without clipping and you can work your way to the more optimized version afterwards. // For small selection set (<100 items), you might want to not bother with using the clipper, as the cost you should
// be negligible (as least on Dear ImGui side).
// If you are not sure, always start without clipping and you can work your way to the optimized version afterwards.
// - The void* RangeSrcItem/RangeDstItem value represent a selectable object. They are the value you pass to SetNextItemSelectionUserData(). // - The void* RangeSrcItem/RangeDstItem value represent a selectable object. They are the value you pass to SetNextItemSelectionUserData().
// Most likely you will want to store an index here. // Most likely you will want to store an index here.
// Storing an integer index is the easiest thing to do, as SetRange requests will give you two end points and you will need to interpolate // Storing an integer index is the easiest thing to do, as SetRange requests will give you two end points and you will need to interpolate
@ -2759,34 +2761,35 @@ enum ImGuiMultiSelectFlags_
// and then from the pointer have your own way of iterating from RangeSrcItem to RangeDstItem). // and then from the pointer have your own way of iterating from RangeSrcItem to RangeDstItem).
// Usage flow: // Usage flow:
// BEGIN - (1) Call BeginMultiSelect() and retrieve the ImGuiMultiSelectIO* result. // BEGIN - (1) Call BeginMultiSelect() and retrieve the ImGuiMultiSelectIO* result.
// - (2) [If using a clipper] Honor Clear/SelectAll/SetRange requests by updating your selection data. Can use same code as Step 6. // - (2) [If using clipper] Honor Clear/SelectAll/SetRange requests by updating your selection data. Same code as Step 6.
// LOOP - (3) [If using a clipper] Set RangeSrcPassedBy=true if the RangeSrcItem item is part of the items clipped before the first submitted/visible item. // LOOP - (3) [If using clipper] Set RangeSrcPassedBy=true if the RangeSrcItem item is part of the items clipped before the first submitted/visible item.
// This is because for range-selection we need to know if we are currently "inside" or "outside" the range. // This is because for range-selection we need to know if we are currently "inside" or "outside" the range.
// If you are using integer indices everywhere, this is easy to compute: if (clipper.DisplayStart > (int)data->RangeSrcItem) { data->RangeSrcPassedBy = true; } // - If you are using integer indices, this is easy to compute: if (clipper.DisplayStart > data->RangeSrcItem) { data->RangeSrcPassedBy = true; }
// - (4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls. // - If you are using pointers, you may need additional processing in each clipper step to tell if current DisplayStart comes after RangeSrcItem..
// (optionally call IsItemToggledSelection() to query if the selection state has been toggled for a given visible item, if you need that info immediately for your display, before EndMultiSelect()) // - (4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls. (optionally call IsItemToggledSelection() if you need that info immediately for displaying your item, before EndMultiSelect())
// END - (5) Call EndMultiSelect() and retrieve the ImGuiMultiSelectIO* result. // END - (5) Call EndMultiSelect() and retrieve the ImGuiMultiSelectIO* result.
// - (6) Honor Clear/SelectAll/SetRange requests by updating your selection data. Always process them in this order (as you will receive Clear+SetRange request simultaneously). Can use same code as Step 2. // - (6) Honor Clear/SelectAll/SetRange requests by updating your selection data. Same code as Step 2.
// If you submit all items (no clipper), Step 2 and 3 and will be handled by Selectable()/TreeNode on a per-item basis. // If you submit all items (no clipper), Step 2 and 3 and will be handled by Selectable()/TreeNode on a per-item basis.
// However it is perfectly fine to honor all steps even if you don't use a clipper.
struct ImGuiMultiSelectIO struct ImGuiMultiSelectIO
{ {
// - Always process requests in this order: Clear, SelectAll, SetRange. // - Always process requests in this order: Clear, SelectAll, SetRange. Use 'Debug Log->Selection' to see requests as they happen.
// - Some fields are only necessary if your list is dynamic and allows deletion (getting "post-deletion" state right is exhibited in the demo) // - Some fields are only necessary if your list is dynamic and allows deletion (getting "post-deletion" state right is exhibited in the demo)
// - Below: who reads/writes each fields? 'r'=read, 'w'=write, 'ms'=multi-select code, 'app'=application/user code, 'BEGIN'=BeginMultiSelect() and after, 'END'=EndMultiSelect() and after. // - Below: who reads/writes each fields? 'r'=read, 'w'=write, 'ms'=multi-select code, 'app'=application/user code, 'BEGIN'=BeginMultiSelect() and after, 'END'=EndMultiSelect() and after.
// REQUESTS ----------------// BEGIN / LOOP / END // REQUESTS --------------------------------// BEGIN / LOOP / END
bool RequestClear; // ms:w, app:r / / ms:w, app:r // 1. Request app/user to clear selection. bool RequestClear; // ms:w, app:r / / ms:w, app:r // 1. Request app/user to clear selection.
bool RequestSelectAll; // ms:w, app:r / / ms:w, app:r // 2. Request app/user to select all. bool RequestSelectAll; // ms:w, app:r / / ms:w, app:r // 2. Request app/user to select all.
bool RequestSetRange; // / / ms:w, app:r // 3. Request app/user to select/unselect [RangeSrcItem..RangeDstItem] items, based on RangeSelected. In practice, only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false. bool RequestSetRange; // / / ms:w, app:r // 3. Request app/user to select/unselect [RangeSrcItem..RangeDstItem] items, based on RangeSelected. In practice, only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false.
void* RequestFocusItem; // app:w / app:r / app:r // (If using deletion) 4. Request user to focus item. This is actually only manipulated in user-space, but we provide storage to facilitate implementing a deletion idiom (see demo). void* RequestFocusItem; // app:w / app:r / app:r // (If using deletion) 4. Request user to focus item. This is actually only manipulated in user-space, but we provide storage to facilitate implementing a deletion idiom (see demo).
// STATE/ARGUMENTS ---------// BEGIN / LOOP / END // STATE/ARGUMENTS -------------------------// BEGIN / LOOP / END
void* RangeSrcItem; // ms:w / app:r / ms:w, app:r // Begin: Last known SetNextItemSelectionUserData() value for RangeSrcItem. End: parameter from RequestSetRange request. void* RangeSrcItem; // ms:w / app:r / ms:w, app:r // Begin: Last known SetNextItemSelectionUserData() value for RangeSrcItem. End: parameter from RequestSetRange request.
void* RangeDstItem; // / / ms:w, app:r // End: parameter from RequestSetRange request. void* RangeDstItem; // / / ms:w, app:r // End: parameter from RequestSetRange request.
ImS8 RangeDirection; // / / ms:w, app:r // End: parameter from RequestSetRange request. +1 if RangeSrcItem came before RangeDstItem, -1 otherwise. Available as an indicator in case you cannot infer order from the void* values. If your void* values are storing indices you will never need this. ImS8 RangeDirection; // / / ms:w, app:r // End: parameter from RequestSetRange request. +1 if RangeSrcItem came before RangeDstItem, -1 otherwise. Available as an indicator in case you cannot infer order from the void* values. If your void* values are storing indices you will never need this.
bool RangeSelected; // / / ms:w, app:r // End: parameter from RequestSetRange request. true = Select Range, false = Unselect Range. bool RangeSelected; // / / ms:w, app:r // End: parameter from RequestSetRange request. true = Select Range, false = Unselect Range.
bool RangeSrcPassedBy; // / ms:rw app:w / ms:r // (If using clipper) Need to be set by app/user if RangeSrcItem was part of the clipped set before submitting the visible items. Ignore if not clipping. bool RangeSrcPassedBy; // / ms:rw app:w / ms:r // (If using clipper) Need to be set by app/user if RangeSrcItem was part of the clipped set before submitting the visible items. Ignore if not clipping.
bool RangeSrcReset; // app:w / app:w / ms:r // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection). bool RangeSrcReset; // app:w / app:w / ms:r // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection).
bool NavIdSelected; // ms:w, app:r / / // (If using deletion) Last known selection state for NavId (if part of submitted items). bool NavIdSelected; // ms:w, app:r / / // (If using deletion) Last known selection state for NavId (if part of submitted items).
void* NavIdItem; // ms:w, app:r / / // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items). void* NavIdItem; // ms:w, app:r / / // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items).
ImGuiMultiSelectIO() { Clear(); } ImGuiMultiSelectIO() { Clear(); }
void Clear() { memset(this, 0, sizeof(*this)); RequestFocusItem = NavIdItem = RangeSrcItem = RangeDstItem = (void*)-1; } void Clear() { memset(this, 0, sizeof(*this)); RequestFocusItem = NavIdItem = RangeSrcItem = RangeDstItem = (void*)-1; }

View File

@ -2796,7 +2796,8 @@ struct ExampleSelection
void SetRange(int a, int b, bool v) { if (b < a) { int tmp = b; b = a; a = tmp; } for (int n = a; n <= b; n++) SetSelected(n, v); } void SetRange(int a, int b, bool v) { if (b < a) { int tmp = b; b = a; a = tmp; } for (int n = a; n <= b; n++) SetSelected(n, v); }
void SelectAll(int count) { Storage.Data.resize(count); for (int idx = 0; idx < count; idx++) Storage.Data[idx] = ImGuiStoragePair((ImGuiID)idx, 1); SelectionSize = count; } // This could be using SetRange(), but it this way is faster. void SelectAll(int count) { Storage.Data.resize(count); for (int idx = 0; idx < count; idx++) Storage.Data[idx] = ImGuiStoragePair((ImGuiID)idx, 1); SelectionSize = count; } // This could be using SetRange(), but it this way is faster.
// Apply requests coming from BeginMultiSelect() and EndMultiSelect(). Must be done in this order! Order->SelectAll->SetRange. // Apply requests coming from BeginMultiSelect() and EndMultiSelect(). Must be done in this order! Clear->SelectAll->SetRange.
// Enable 'Debug Log->Selection' to see selection requests as they happen.
void ApplyRequests(ImGuiMultiSelectIO* ms_io, int items_count) void ApplyRequests(ImGuiMultiSelectIO* ms_io, int items_count)
{ {
if (ms_io->RequestClear) { Clear(); } if (ms_io->RequestClear) { Clear(); }
@ -2934,6 +2935,8 @@ static void ShowDemoWindowMultiSelect()
{ {
static ExampleSelection selection; static ExampleSelection selection;
ImGui::Text("Tips: Use 'Debug Log->Selection' to see selection requests as they happen.");
ImGui::Text("Supported features:"); ImGui::Text("Supported features:");
ImGui::BulletText("Keyboard navigation (arrows, page up/down, home/end, space)."); ImGui::BulletText("Keyboard navigation (arrows, page up/down, home/end, space).");
ImGui::BulletText("Ctrl modifier to preserve and toggle selection."); ImGui::BulletText("Ctrl modifier to preserve and toggle selection.");

View File

@ -7455,11 +7455,10 @@ void ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage)
#ifndef IMGUI_DISABLE_DEBUG_TOOLS #ifndef IMGUI_DISABLE_DEBUG_TOOLS
const bool is_active = (storage->LastFrameActive >= GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here. const bool is_active = (storage->LastFrameActive >= GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here.
if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); } if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }
bool open = TreeNode((void*)(intptr_t)storage->ID, "MultiSelect 0x%08X%s", storage->ID, is_active ? "" : " *Inactive*"); bool open = TreeNode((void*)(intptr_t)storage->ID, "MultiSelect 0x%08X in '%s'%s", storage->ID, storage->Window ? storage->Window->Name : "N/A", is_active ? "" : " *Inactive*");
if (!is_active) { PopStyleColor(); } if (!is_active) { PopStyleColor(); }
if (!open) if (!open)
return; return;
Text("ID = 0x%08X", storage->ID);
Text("RangeSrcItem = %p, RangeSelected = %d", storage->RangeSrcItem, storage->RangeSelected); Text("RangeSrcItem = %p, RangeSelected = %d", storage->RangeSrcItem, storage->RangeSelected);
Text("NavIdData = %p, NavIdSelected = %d", storage->NavIdItem, storage->NavIdSelected); Text("NavIdData = %p, NavIdSelected = %d", storage->NavIdItem, storage->NavIdSelected);
TreePop(); TreePop();