From 67ee3892c7bc7b5a511424dc4e6764669a49da22 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 21 Dec 2023 15:48:42 +0100 Subject: [PATCH] Updated Multi Select (markdown) --- Multi-Select.md | 79 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/Multi-Select.md b/Multi-Select.md index d0b934c..7674c5b 100644 --- a/Multi-Select.md +++ b/Multi-Select.md @@ -8,16 +8,78 @@ - **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. external selection (set/map/hash), intrusive selection (bool inside your objects) 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). See "Usage flow" section below for details. If you are not sure, always start without clipping! You can work your way to the optimized version afterwards. -### Usage Flow +### Principal APIs -- (1) Call BeginMultiSelect() and retrieve the ImGuiMultiSelectIO* result. -- (2) [If using clipper] Honor request list (Clear/SelectAll/SetRange requests) by updating your selection data. Same code as Step 6. -- (3) [If using clipper] You need to make sure RangeSrcItem is always submitted. Calculate its index and pass to clipper.IncludeItemByIndex(). If storing indices in ImGuiSelectionUserData, a simple clipper.IncludeItemByIndex(ms_io->RangeSrcItem) call will work. -- (4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls. -- (5) Call EndMultiSelect() and retrieve the ImGuiMultiSelectIO* result. -- (6) Honor request list (Clear/SelectAll/SetRange requests) by updating your selection data. Same code as Step 2. +```cpp +// Main API +ImGuiMultiSelectIO* BeginMultiSelect(ImGuiMultiSelectFlags flags); +ImGuiMultiSelectIO* EndMultiSelect(); +void SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data); +``` +```cpp +// Main IO structure returned by BeginMultiSelect()/EndMultiSelect(). +// This mainly contains a list of selection requests. +// - Use 'Demo->Tools->Debug Log->Selection' to see requests as they happen. +// - Some fields are only useful if your list is dynamic and allows deletion (getting post-deletion focus/state right is shown in the demo) +// - Below: who reads/writes each fields? 'r'=read, 'w'=write, 'ms'=multi-select code, 'app'=application/user code. +struct ImGuiMultiSelectIO +{ + //------------------------------------------// BeginMultiSelect / EndMultiSelect + ImVector Requests; // ms:w, app:r / ms:w app:r // Requests to apply to your selection data. + ImGuiSelectionUserData RangeSrcItem; // ms:w app:r / // (If using clipper) Begin: Source item (generally the first selected item when multi-selecting, which is used as a reference point) must never be clipped! + ImGuiSelectionUserData NavIdItem; // ms:w, app:r / // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items). + bool NavIdSelected; // ms:w, app:r / app:r // (If using deletion) Last known selection state for NavId (if part of submitted items). + bool RangeSrcReset; // app:w / ms:r // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection). +}; -If you submit all items (no clipper), Step 2 and 3 are optional and will be handled by each item themselves. It is perfectly fine if you honor those steps without a clipper. +// Selection request item +struct ImGuiSelectionRequest +{ + //------------------------------------------// BeginMultiSelect / EndMultiSelect + ImGuiSelectionRequestType Type; // ms:w, app:r / ms:w, app:r // Request type. You'll most often receive 1 Clear + 1 SetRange with a single-item range. + bool RangeSelected; // / ms:w, app:r // Parameter for SetRange request (true = select range, false = unselect range) + ImGuiSelectionUserData RangeFirstItem; // / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from top to bottom) + ImGuiSelectionUserData RangeLastItem; // / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from bottom to top) +}; + +// Selection request type +enum ImGuiSelectionRequestType +{ + ImGuiSelectionRequestType_None = 0, + ImGuiSelectionRequestType_Clear, // Request app to clear selection. + ImGuiSelectionRequestType_SelectAll, // Request app to select all. + ImGuiSelectionRequestType_SetRange, // Request app to select/unselect [RangeFirstItem..RangeLastItem] items based on 'bool RangeSelected'. Only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false. +}; +``` + +### TL;DR; + +- Identify submitted items with `SetNextItemSelectionUserData()`, most likely using an index into your current data-set. +- Store and maintain actual selection data using persistent object identifiers. +- Usage Flow: + - (1) Call `BeginMultiSelect()` and retrieve the `ImGuiMultiSelectIO*` result. + - (2) Honor request list (Clear/SelectAll/SetRange requests) by updating your selection data. Same code as Step 6. + - (3) [If using clipper] You need to make sure RangeSrcItem is always submitted. + - Calculate its index and pass to `clipper.IncludeItemByIndex()`. + - If storing indices in `ImGuiSelectionUserData`, a simple `clipper.IncludeItemByIndex(ms_io->RangeSrcItem)` call will work. + - (4) Submit your items with `SetNextItemSelectionUserData()` + `Selectable()`/`TreeNode()` calls. + - (5) Call `EndMultiSelect()` and retrieve the `ImGuiMultiSelectIO*` result. + - (6) Honor request list (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 are optional and will be handled by each item themselves. It is fine to always honor those steps. + +### About ImGuiSelectionUserData + +- For each item is it submitted by your call to `SetNextItemSelectionUserData()`. +- This can store an application-defined identifier (e.g. index or pointer). +- In return we store them into RangeSrcItem/RangeFirstItem/RangeLastItem and other fields in `ImGuiMultiSelectIO`. +- Most applications will store an object INDEX, hence the chosen name and type. + Storing an integer index is the easiest thing to do, as SetRange requests will give you two end-points + and you will need to iterate/interpolate between them to update your selection. +- However it is perfectly possible to store a POINTER or another IDENTIFIER inside this value! + Our system never assume that you identify items by indices, it never attempts to interpolate between two values. +- As most users will want to store an index, for convenience and to reduce confusion we use ImS64 instead of void*, + being syntactically easier to downcast. Feel free to reinterpret_cast and store a pointer inside. +- If you need to wrap this API for another language/framework, feel free to expose this as 'int' if simpler. ### With the ImGuiSelectionBasicStorage helper @@ -25,7 +87,6 @@ If you submit all items (no clipper), Step 2 and 3 are optional and will be hand - Used by our demos and provided as a convenience to easily implement basic multi-selection. - USING THIS IS NOT MANDATORY. This is only a helper and not a required API. Advanced users are likely to implement their own. - Minimum pseudo-code example using this helper: ```cpp static vector items; // Your items