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
// - This system implements standard multi-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc) in a way that
// allow a clipper to be used (so most non-visible items won't be submitted). Handling this correctly is tricky, this is why
// we provide the functionality. Note however that if you don't need SHIFT+Mouse/Keyboard range-select + clipping, you could use
// a simpler form of multi-selection yourself, by reacting to click/presses on Selectable() items and checking keyboard modifiers.
// The unusual complexity of this system is mostly caused by supporting SHIFT+Click/Arrow range-select with clipped elements.
// - In the spirit of Dear ImGui design, your code owns the selection data.
// So this is designed to handle all kind of selection data: e.g. instructive selection (store a bool inside each object),
// external array (store an array aside from your objects), hash/map/set (store only selected items in a hash/map/set),
// or other structures (store indices in an interval tree), etc.
// - TreeNode() and Selectable() are supported.
// - The work involved to deal with multi-selection differs whether you want to only submit visible items (and clip others) or submit all items
// regardless of their visibility. Clipping items is more efficient and will allow you to deal with large lists (1k~100k items) with near zero
// performance penalty, but requires a little more work on the code. If you only have a few hundreds elements in your possible selection set,
// you may as well not bother with clipping, as the cost 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 more optimized version afterwards.
// - Refer to 'Demo->Widgets->Selection State' for references using this.
// - This system implements standard multi-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc)
// and supports a clipper being used. Handling this manually may be tricky, this is why we provide the functionality.
// If you don't need SHIFT+Mouse/Keyboard range-select + clipping, you can use a simpler form of multi-selection yourself,
// by reacting to click/presses on Selectable() items and checking keyboard modifiers.
// The complexity of this system is mostly caused by supporting SHIFT+Click/Arrow range-select with clipped elements.
// - TreeNode() and Selectable() are supported but custom widgets may use it as well.
// - In the spirit of Dear ImGui design, your code owns actual selection data.
// This is designed to allow all kinds of selection storage you may use in your application:
// e.g. instructive selection (store a bool inside each object), external array (store an array in your view data, next
// to your objects), set/map/hash (store only selected items), or other structures (store indices in an interval tree), etc.
// - The work involved to deal with multi-selection differs whether you want to only submit visible items and clip others,
// or submit all items regardless of their visibility. Clipping items is more efficient and will allow you to deal with
// large lists (1k~100k items) with no performance penalty, but requires a little more work on the code.
// 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().
// 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
@ -2759,34 +2761,35 @@ enum ImGuiMultiSelectFlags_
// and then from the pointer have your own way of iterating from RangeSrcItem to RangeDstItem).
// Usage flow:
// 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.
// 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.
// - (2) [If using clipper] Honor Clear/SelectAll/SetRange requests by updating your selection data. Same code as Step 6.
// 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.
// If you are using integer indices everywhere, this is easy to compute: if (clipper.DisplayStart > (int)data->RangeSrcItem) { data->RangeSrcPassedBy = true; }
// - (4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls.
// (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())
// - If you are using integer indices, this is easy to compute: if (clipper.DisplayStart > data->RangeSrcItem) { data->RangeSrcPassedBy = true; }
// - If you are using pointers, you may need additional processing in each clipper step to tell if current DisplayStart comes after RangeSrcItem..
// - (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.
// - (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.
// However it is perfectly fine to honor all steps even if you don't use a clipper.
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)
// - 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
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 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).
// 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* 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.
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 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).
void* NavIdItem; // ms:w, app:r / / // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items).
// REQUESTS --------------------------------// BEGIN / LOOP / END
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 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).
// 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* 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.
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 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).
void* NavIdItem; // ms:w, app:r / / // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items).
ImGuiMultiSelectIO() { Clear(); }
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 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)
{
if (ms_io->RequestClear) { Clear(); }
@ -2934,6 +2935,8 @@ static void ShowDemoWindowMultiSelect()
{
static ExampleSelection selection;
ImGui::Text("Tips: Use 'Debug Log->Selection' to see selection requests as they happen.");
ImGui::Text("Supported features:");
ImGui::BulletText("Keyboard navigation (arrows, page up/down, home/end, space).");
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
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)); }
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 (!open)
return;
Text("ID = 0x%08X", storage->ID);
Text("RangeSrcItem = %p, RangeSelected = %d", storage->RangeSrcItem, storage->RangeSelected);
Text("NavIdData = %p, NavIdSelected = %d", storage->NavIdItem, storage->NavIdSelected);
TreePop();