Skip to content

Commit 554db6b

Browse files
committed
MultiSelect: WIP range-select (#1861) (rebased six millions times)
1 parent c2d21ab commit 554db6b

File tree

5 files changed

+533
-59
lines changed

5 files changed

+533
-59
lines changed

imgui.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -1273,6 +1273,7 @@ ImGuiStyle::ImGuiStyle()
12731273
TableAngledHeadersTextAlign = ImVec2(0.5f,0.0f);// Alignment of angled headers within the cell
12741274
ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.
12751275
ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text.
1276+
SelectableSpacing = ImVec2(0.0f,0.0f);// Horizontal and vertical spacing between selectables (by default they are canceling out the effect of ItemSpacing).
12761277
SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line.
12771278
SeparatorTextBorderSize = 3.0f; // Thickkness of border in SeparatorText()
12781279
SeparatorTextAlign = ImVec2(0.0f,0.5f);// Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center).
@@ -1321,6 +1322,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor)
13211322
LogSliderDeadzone = ImTrunc(LogSliderDeadzone * scale_factor);
13221323
TabRounding = ImTrunc(TabRounding * scale_factor);
13231324
TabMinWidthForCloseButton = (TabMinWidthForCloseButton != FLT_MAX) ? ImTrunc(TabMinWidthForCloseButton * scale_factor) : FLT_MAX;
1325+
SelectableSpacing = ImTrunc(SelectableSpacing * scale_factor);
13241326
SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor);
13251327
DisplayWindowPadding = ImTrunc(DisplayWindowPadding * scale_factor);
13261328
DisplaySafeAreaPadding = ImTrunc(DisplaySafeAreaPadding * scale_factor);
@@ -3257,6 +3259,7 @@ static const ImGuiDataVarInfo GStyleVarInfo[] =
32573259
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)}, // ImGuiStyleVar_TableAngledHeadersAngle
32583260
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign
32593261
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign
3262+
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SelectableSpacing) }, // ImGuiStyleVar_SelectableSpacing
32603263
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign
32613264
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)}, // ImGuiStyleVar_SeparatorTextBorderSize
32623265
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) }, // ImGuiStyleVar_SeparatorTextAlign
@@ -3822,6 +3825,7 @@ void ImGui::Shutdown()
38223825
g.MenusIdSubmittedThisFrame.clear();
38233826
g.InputTextState.ClearFreeMemory();
38243827
g.InputTextDeactivatedState.ClearFreeMemory();
3828+
g.MultiSelectScopeWindow = NULL;
38253829

38263830
g.SettingsWindows.clear();
38273831
g.SettingsHandlers.clear();

imgui.h

+82
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Index of this file:
4444
// [SECTION] ImGuiIO
4545
// [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload)
4646
// [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor)
47+
// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectData)
4748
// [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData)
4849
// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont)
4950
// [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport)
@@ -174,6 +175,7 @@ struct ImGuiIO; // Main configuration and I/O between your a
174175
struct ImGuiInputTextCallbackData; // Shared state of InputText() when using custom ImGuiInputTextCallback (rare/advanced use)
175176
struct ImGuiKeyData; // Storage for ImGuiIO and IsKeyDown(), IsKeyPressed() etc functions.
176177
struct ImGuiListClipper; // Helper to manually clip large list of items
178+
struct ImGuiMultiSelectData; // State for a BeginMultiSelect() block
177179
struct ImGuiOnceUponAFrame; // Helper for running a block of code not more than once a frame
178180
struct ImGuiPayload; // User data payload for drag and drop operations
179181
struct ImGuiPlatformImeData; // Platform IME data for io.PlatformSetImeDataFn() function.
@@ -227,6 +229,7 @@ typedef int ImGuiInputTextFlags; // -> enum ImGuiInputTextFlags_ // Flags: f
227229
typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag(), shared by all items
228230
typedef int ImGuiKeyChord; // -> ImGuiKey | ImGuiMod_XXX // Flags: for IsKeyChordPressed(), Shortcut() etc. an ImGuiKey optionally OR-ed with one or more ImGuiMod_XXX values.
229231
typedef int ImGuiPopupFlags; // -> enum ImGuiPopupFlags_ // Flags: for OpenPopup*(), BeginPopupContext*(), IsPopupOpen()
232+
typedef int ImGuiMultiSelectFlags; // -> enum ImGuiMultiSelectFlags_// Flags: for BeginMultiSelect()
230233
typedef int ImGuiSelectableFlags; // -> enum ImGuiSelectableFlags_ // Flags: for Selectable()
231234
typedef int ImGuiSliderFlags; // -> enum ImGuiSliderFlags_ // Flags: for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc.
232235
typedef int ImGuiTabBarFlags; // -> enum ImGuiTabBarFlags_ // Flags: for BeginTabBar()
@@ -262,6 +265,10 @@ typedef ImWchar32 ImWchar;
262265
typedef ImWchar16 ImWchar;
263266
#endif
264267

268+
// Multi-Selection item index or identifier when using SetNextItemSelectionUserData()/BeginMultiSelect()
269+
// (Most users are likely to use this store an item INDEX but this may be used to store a POINTER as well.)
270+
typedef ImS64 ImGuiSelectionUserData;
271+
265272
// Callback and functions types
266273
typedef int (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData* data); // Callback function for ImGui::InputText()
267274
typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data); // Callback function for ImGui::SetNextWindowSizeConstraints()
@@ -661,6 +668,14 @@ namespace ImGui
661668
IMGUI_API bool Selectable(const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // "bool selected" carry the selection state (read-only). Selectable() is clicked is returns true so you can modify your selection state. size.x==0.0: use remaining width, size.x>0.0: specify width. size.y==0.0: use label height, size.y>0.0: specify height
662669
IMGUI_API bool Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // "bool* p_selected" point to the selection state (read-write), as a convenient helper.
663670

671+
// Multi-selection system for Selectable() and TreeNode() functions.
672+
// This enables standard multi-selection/range-selection idioms (CTRL+Click/Arrow, SHIFT+Click/Arrow, etc) in a way that allow items to be fully clipped (= not submitted at all) when not visible.
673+
// Read comments near ImGuiMultiSelectData for details.
674+
// When enabled, Selectable() and TreeNode() functions will return true when selection needs toggling.
675+
IMGUI_API ImGuiMultiSelectData* BeginMultiSelect(ImGuiMultiSelectFlags flags, void* range_ref, bool range_ref_is_selected);
676+
IMGUI_API ImGuiMultiSelectData* EndMultiSelect();
677+
IMGUI_API void SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data);
678+
664679
// Widgets: List Boxes
665680
// - This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.
666681
// - You can submit contents and manage your selection state however you want it, by creating e.g. Selectable() or any other items.
@@ -893,6 +908,7 @@ namespace ImGui
893908
IMGUI_API bool IsItemDeactivated(); // was the last item just made inactive (item was previously active). Useful for Undo/Redo patterns with widgets that require continuous editing.
894909
IMGUI_API bool IsItemDeactivatedAfterEdit(); // was the last item just made inactive and made a value change when it was active? (e.g. Slider/Drag moved). Useful for Undo/Redo patterns with widgets that require continuous editing. Note that you may get false positives (some widgets such as Combo()/ListBox()/Selectable() will return true even when clicking an already selected item).
895910
IMGUI_API bool IsItemToggledOpen(); // was the last item open state toggled? set by TreeNode().
911+
IMGUI_API bool IsItemToggledSelection(); // was the last item selection state toggled? (after Selectable(), TreeNode() etc. We only returns toggle _event_ in order to handle clipping correctly)
896912
IMGUI_API bool IsAnyItemHovered(); // is any item hovered?
897913
IMGUI_API bool IsAnyItemActive(); // is any item active?
898914
IMGUI_API bool IsAnyItemFocused(); // is any item focused?
@@ -1683,6 +1699,7 @@ enum ImGuiStyleVar_
16831699
ImGuiStyleVar_TableAngledHeadersAngle, // float TableAngledHeadersAngle
16841700
ImGuiStyleVar_TableAngledHeadersTextAlign,// ImVec2 TableAngledHeadersTextAlign
16851701
ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign
1702+
ImGuiStyleVar_SelectableSpacing, // ImVec2 SelectableSpacing
16861703
ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign
16871704
ImGuiStyleVar_SeparatorTextBorderSize, // float SeparatorTextBorderSize
16881705
ImGuiStyleVar_SeparatorTextAlign, // ImVec2 SeparatorTextAlign
@@ -2127,6 +2144,7 @@ struct ImGuiStyle
21272144
ImVec2 TableAngledHeadersTextAlign;// Alignment of angled headers within the cell
21282145
ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.
21292146
ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered).
2147+
ImVec2 SelectableSpacing; // Horizontal and vertical spacing between selectables (by default they are canceling out the effect of ItemSpacing).
21302148
ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line.
21312149
float SeparatorTextBorderSize; // Thickkness of border in SeparatorText()
21322150
ImVec2 SeparatorTextAlign; // Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center).
@@ -2702,6 +2720,70 @@ struct ImColor
27022720
static ImColor HSV(float h, float s, float v, float a = 1.0f) { float r, g, b; ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); return ImColor(r, g, b, a); }
27032721
};
27042722

2723+
//-----------------------------------------------------------------------------
2724+
// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectData)
2725+
//-----------------------------------------------------------------------------
2726+
2727+
// Flags for BeginMultiSelect().
2728+
// This system is designed to allow mouse/keyboard multi-selection, including support for range-selection (SHIFT + click) which is difficult to re-implement manually.
2729+
// If you disable multi-selection with ImGuiMultiSelectFlags_NoMultiSelect (which is provided for consistency and flexibility), the whole BeginMultiSelect() system
2730+
// becomes largely overkill as you can handle single-selection in a simpler manner by just calling Selectable() and reacting on clicks yourself.
2731+
enum ImGuiMultiSelectFlags_
2732+
{
2733+
ImGuiMultiSelectFlags_NoMultiSelect = 1 << 0,
2734+
ImGuiMultiSelectFlags_NoUnselect = 1 << 1, // Disable unselecting items with CTRL+Click, CTRL+Space etc.
2735+
ImGuiMultiSelectFlags_NoSelectAll = 1 << 2, // Disable CTRL+A shortcut to set RequestSelectAll
2736+
};
2737+
2738+
// Abstract:
2739+
// - This system implements standard multi-selection idioms (CTRL+Click/Arrow, SHIFT+Click/Arrow, etc) in a way that allow items to be
2740+
// fully clipped (= not submitted at all) when not visible. Clipping is typically provided by ImGuiListClipper.
2741+
// Handling all of this in a single pass imgui is a little tricky, and this is why we provide those functionalities.
2742+
// Note however that if you don't need SHIFT+Click/Arrow range-select, you can handle a simpler form of multi-selection yourself,
2743+
// by reacting to click/presses on Selectable() items and checking keyboard modifiers.
2744+
// The complexity of this system here is mostly caused by the handling of range-select while optionally allowing to clip elements.
2745+
// - The work involved to deal with multi-selection differs whether you want to only submit visible items (and clip others) or submit all items
2746+
// regardless of their visibility. Clipping items is more efficient and will allow you to deal with large lists (1k~100k items) with near zero
2747+
// performance penalty, but requires a little more work on the code. If you only have a few hundreds elements in your possible selection set,
2748+
// you may as well not bother with clipping, as the cost should be negligible (as least on imgui side).
2749+
// If you are not sure, always start without clipping and you can work your way to the more optimized version afterwards.
2750+
// - The void* Src/Dst value represent a selectable object. They are the values you pass to SetNextItemMultiSelectData().
2751+
// Storing an integer index is the easiest thing to do, as SetRange requests will give you two end points. But the code never assume that sortable integers are used.
2752+
// - In the spirit of imgui design, your code own the selection data. So this is designed to handle all kind of selection data: instructive (store a bool inside each object),
2753+
// external array (store an array aside from your objects), set (store only selected items in a hash/map/set), using intervals (store indices in an interval tree), etc.
2754+
// Usage flow:
2755+
// 1) Call BeginMultiSelect() with the last saved value of ->RangeSrc and its selection status. As a default value for the initial frame or when,
2756+
// resetting your selection state: you may use the value for your first item or a "null" value that matches the type stored in your void*.
2757+
// 2) Honor Clear/SelectAll requests by updating your selection data. [Only required if you are using a clipper in step 4]
2758+
// 3) Set RangeSrcPassedBy=true if the RangeSrc item is part of the items clipped before the first submitted/visible item. [Only required if you are using a clipper in step 4]
2759+
// This is because for range-selection we need to know if we are currently "inside" or "outside" the range.
2760+
// If you are using integer indices everywhere, this is easy to compute: if (clipper.DisplayStart > (int)data->RangeSrc) { data->RangeSrcPassedBy = true; }
2761+
// 4) Submit your items with SetNextItemMultiSelectData() + Selectable()/TreeNode() calls.
2762+
// Call IsItemSelectionToggled() to query with the selection state has been toggled, in which you need the info immediately (before EndMultiSelect()) for your display.
2763+
// When cannot reliably return a "IsItemSelected()" value because we need to consider clipped (unprocessed) item, this is why we return a toggle event instead.
2764+
// 5) Call EndMultiSelect(). Save the value of ->RangeSrc for the next frame (you may convert the value in a format that is safe for persistance)
2765+
// 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)
2766+
// If you submit all items (no clipper), Step 2 and 3 and will be handled by Selectable() on a per-item basis.
2767+
struct ImGuiMultiSelectData
2768+
{
2769+
bool RequestClear; // Begin, End // Request user to clear selection
2770+
bool RequestSelectAll; // Begin, End // Request user to select all
2771+
bool RequestSetRange; // End // Request user to set or clear selection in the [RangeSrc..RangeDst] range
2772+
bool RangeSrcPassedBy; // After Begin // Need to be set by user is RangeSrc was part of the clipped set before submitting the visible items. Ignore if not clipping.
2773+
bool RangeValue; // End // End: parameter from RequestSetRange request. True = Select Range, False = Unselect range.
2774+
void* RangeSrc; // Begin, End // End: parameter from RequestSetRange request + you need to save this value so you can pass it again next frame. / Begin: this is the value you passed to BeginMultiSelect()
2775+
void* RangeDst; // End // End: parameter from RequestSetRange request.
2776+
int RangeDirection; // End // End: parameter from RequestSetRange request. +1 if RangeSrc came before RangeDst, -1 otherwise. Available as an indicator in case you cannot infer order from the void* values.
2777+
2778+
ImGuiMultiSelectData() { Clear(); }
2779+
void Clear()
2780+
{
2781+
RequestClear = RequestSelectAll = RequestSetRange = RangeSrcPassedBy = RangeValue = false;
2782+
RangeSrc = RangeDst = NULL;
2783+
RangeDirection = 0;
2784+
}
2785+
};
2786+
27052787
//-----------------------------------------------------------------------------
27062788
// [SECTION] Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawListFlags, ImDrawList, ImDrawData)
27072789
// Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList.

0 commit comments

Comments
 (0)