MultiSelect: WIP range-select (#1861) (rebased six millions times)

This commit is contained in:
omar 2019-02-01 12:22:57 +01:00 committed by ocornut
parent c2d21ab04f
commit 554db6bc0f
5 changed files with 533 additions and 59 deletions

View File

@ -1273,6 +1273,7 @@ ImGuiStyle::ImGuiStyle()
TableAngledHeadersTextAlign = ImVec2(0.5f,0.0f);// Alignment of angled headers within the cell
ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.
ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text.
SelectableSpacing = ImVec2(0.0f,0.0f);// Horizontal and vertical spacing between selectables (by default they are canceling out the effect of ItemSpacing).
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.
SeparatorTextBorderSize = 3.0f; // Thickkness of border in SeparatorText()
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)
LogSliderDeadzone = ImTrunc(LogSliderDeadzone * scale_factor);
TabRounding = ImTrunc(TabRounding * scale_factor);
TabMinWidthForCloseButton = (TabMinWidthForCloseButton != FLT_MAX) ? ImTrunc(TabMinWidthForCloseButton * scale_factor) : FLT_MAX;
SelectableSpacing = ImTrunc(SelectableSpacing * scale_factor);
SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor);
DisplayWindowPadding = ImTrunc(DisplayWindowPadding * scale_factor);
DisplaySafeAreaPadding = ImTrunc(DisplaySafeAreaPadding * scale_factor);
@ -3257,6 +3259,7 @@ static const ImGuiDataVarInfo GStyleVarInfo[] =
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)}, // ImGuiStyleVar_TableAngledHeadersAngle
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SelectableSpacing) }, // ImGuiStyleVar_SelectableSpacing
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)}, // ImGuiStyleVar_SeparatorTextBorderSize
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) }, // ImGuiStyleVar_SeparatorTextAlign
@ -3822,6 +3825,7 @@ void ImGui::Shutdown()
g.MenusIdSubmittedThisFrame.clear();
g.InputTextState.ClearFreeMemory();
g.InputTextDeactivatedState.ClearFreeMemory();
g.MultiSelectScopeWindow = NULL;
g.SettingsWindows.clear();
g.SettingsHandlers.clear();

82
imgui.h
View File

@ -44,6 +44,7 @@ Index of this file:
// [SECTION] ImGuiIO
// [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload)
// [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor)
// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectData)
// [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData)
// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont)
// [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport)
@ -174,6 +175,7 @@ struct ImGuiIO; // Main configuration and I/O between your a
struct ImGuiInputTextCallbackData; // Shared state of InputText() when using custom ImGuiInputTextCallback (rare/advanced use)
struct ImGuiKeyData; // Storage for ImGuiIO and IsKeyDown(), IsKeyPressed() etc functions.
struct ImGuiListClipper; // Helper to manually clip large list of items
struct ImGuiMultiSelectData; // State for a BeginMultiSelect() block
struct ImGuiOnceUponAFrame; // Helper for running a block of code not more than once a frame
struct ImGuiPayload; // User data payload for drag and drop operations
struct ImGuiPlatformImeData; // Platform IME data for io.PlatformSetImeDataFn() function.
@ -227,6 +229,7 @@ typedef int ImGuiInputTextFlags; // -> enum ImGuiInputTextFlags_ // Flags: f
typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag(), shared by all items
typedef int ImGuiKeyChord; // -> ImGuiKey | ImGuiMod_XXX // Flags: for IsKeyChordPressed(), Shortcut() etc. an ImGuiKey optionally OR-ed with one or more ImGuiMod_XXX values.
typedef int ImGuiPopupFlags; // -> enum ImGuiPopupFlags_ // Flags: for OpenPopup*(), BeginPopupContext*(), IsPopupOpen()
typedef int ImGuiMultiSelectFlags; // -> enum ImGuiMultiSelectFlags_// Flags: for BeginMultiSelect()
typedef int ImGuiSelectableFlags; // -> enum ImGuiSelectableFlags_ // Flags: for Selectable()
typedef int ImGuiSliderFlags; // -> enum ImGuiSliderFlags_ // Flags: for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc.
typedef int ImGuiTabBarFlags; // -> enum ImGuiTabBarFlags_ // Flags: for BeginTabBar()
@ -262,6 +265,10 @@ typedef ImWchar32 ImWchar;
typedef ImWchar16 ImWchar;
#endif
// Multi-Selection item index or identifier when using SetNextItemSelectionUserData()/BeginMultiSelect()
// (Most users are likely to use this store an item INDEX but this may be used to store a POINTER as well.)
typedef ImS64 ImGuiSelectionUserData;
// Callback and functions types
typedef int (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData* data); // Callback function for ImGui::InputText()
typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data); // Callback function for ImGui::SetNextWindowSizeConstraints()
@ -661,6 +668,14 @@ namespace ImGui
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
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.
// Multi-selection system for Selectable() and TreeNode() functions.
// 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.
// Read comments near ImGuiMultiSelectData for details.
// When enabled, Selectable() and TreeNode() functions will return true when selection needs toggling.
IMGUI_API ImGuiMultiSelectData* BeginMultiSelect(ImGuiMultiSelectFlags flags, void* range_ref, bool range_ref_is_selected);
IMGUI_API ImGuiMultiSelectData* EndMultiSelect();
IMGUI_API void SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data);
// Widgets: List Boxes
// - This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.
// - 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
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.
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).
IMGUI_API bool IsItemToggledOpen(); // was the last item open state toggled? set by TreeNode().
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)
IMGUI_API bool IsAnyItemHovered(); // is any item hovered?
IMGUI_API bool IsAnyItemActive(); // is any item active?
IMGUI_API bool IsAnyItemFocused(); // is any item focused?
@ -1683,6 +1699,7 @@ enum ImGuiStyleVar_
ImGuiStyleVar_TableAngledHeadersAngle, // float TableAngledHeadersAngle
ImGuiStyleVar_TableAngledHeadersTextAlign,// ImVec2 TableAngledHeadersTextAlign
ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign
ImGuiStyleVar_SelectableSpacing, // ImVec2 SelectableSpacing
ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign
ImGuiStyleVar_SeparatorTextBorderSize, // float SeparatorTextBorderSize
ImGuiStyleVar_SeparatorTextAlign, // ImVec2 SeparatorTextAlign
@ -2127,6 +2144,7 @@ struct ImGuiStyle
ImVec2 TableAngledHeadersTextAlign;// Alignment of angled headers within the cell
ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.
ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered).
ImVec2 SelectableSpacing; // Horizontal and vertical spacing between selectables (by default they are canceling out the effect of ItemSpacing).
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.
float SeparatorTextBorderSize; // Thickkness of border in SeparatorText()
ImVec2 SeparatorTextAlign; // Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center).
@ -2702,6 +2720,70 @@ struct ImColor
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); }
};
//-----------------------------------------------------------------------------
// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectData)
//-----------------------------------------------------------------------------
// Flags for BeginMultiSelect().
// This system is designed to allow mouse/keyboard multi-selection, including support for range-selection (SHIFT + click) which is difficult to re-implement manually.
// If you disable multi-selection with ImGuiMultiSelectFlags_NoMultiSelect (which is provided for consistency and flexibility), the whole BeginMultiSelect() system
// becomes largely overkill as you can handle single-selection in a simpler manner by just calling Selectable() and reacting on clicks yourself.
enum ImGuiMultiSelectFlags_
{
ImGuiMultiSelectFlags_NoMultiSelect = 1 << 0,
ImGuiMultiSelectFlags_NoUnselect = 1 << 1, // Disable unselecting items with CTRL+Click, CTRL+Space etc.
ImGuiMultiSelectFlags_NoSelectAll = 1 << 2, // Disable CTRL+A shortcut to set RequestSelectAll
};
// Abstract:
// - This system implements standard multi-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. Clipping is typically provided by ImGuiListClipper.
// Handling all of this in a single pass imgui is a little tricky, and this is why we provide those functionalities.
// Note however that if you don't need SHIFT+Click/Arrow range-select, you can handle a simpler form of multi-selection yourself,
// by reacting to click/presses on Selectable() items and checking keyboard modifiers.
// The complexity of this system here is mostly caused by the handling of range-select while optionally allowing to clip elements.
// - 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 imgui side).
// If you are not sure, always start without clipping and you can work your way to the more optimized version afterwards.
// - The void* Src/Dst value represent a selectable object. They are the values you pass to SetNextItemMultiSelectData().
// 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.
// - 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),
// 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.
// Usage flow:
// 1) Call BeginMultiSelect() with the last saved value of ->RangeSrc and its selection status. As a default value for the initial frame or when,
// 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*.
// 2) Honor Clear/SelectAll requests by updating your selection data. [Only required if you are using a clipper in step 4]
// 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]
// 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->RangeSrc) { data->RangeSrcPassedBy = true; }
// 4) Submit your items with SetNextItemMultiSelectData() + Selectable()/TreeNode() calls.
// Call IsItemSelectionToggled() to query with the selection state has been toggled, in which you need the info immediately (before EndMultiSelect()) for your display.
// When cannot reliably return a "IsItemSelected()" value because we need to consider clipped (unprocessed) item, this is why we return a toggle event instead.
// 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)
// 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)
// If you submit all items (no clipper), Step 2 and 3 and will be handled by Selectable() on a per-item basis.
struct ImGuiMultiSelectData
{
bool RequestClear; // Begin, End // Request user to clear selection
bool RequestSelectAll; // Begin, End // Request user to select all
bool RequestSetRange; // End // Request user to set or clear selection in the [RangeSrc..RangeDst] range
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.
bool RangeValue; // End // End: parameter from RequestSetRange request. True = Select Range, False = Unselect range.
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()
void* RangeDst; // End // End: parameter from RequestSetRange request.
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.
ImGuiMultiSelectData() { Clear(); }
void Clear()
{
RequestClear = RequestSelectAll = RequestSetRange = RangeSrcPassedBy = RangeValue = false;
RangeSrc = RangeDst = NULL;
RangeDirection = 0;
}
};
//-----------------------------------------------------------------------------
// [SECTION] Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawListFlags, ImDrawList, ImDrawData)
// Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList.

View File

@ -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()
@ -214,6 +215,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();
@ -251,6 +253,7 @@ void* GImGuiDemoMarkerCallbackUserData = NULL;
//-----------------------------------------------------------------------------
// - ShowDemoWindow()
// - ShowDemoWindowWidgets()
// - ShowDemoWindowMultiSelect()
// - ShowDemoWindowLayout()
// - ShowDemoWindowPopups()
// - ShowDemoWindowTables()
@ -1371,37 +1374,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"))
{
@ -1461,6 +1433,15 @@ static void ShowDemoWindowWidgets()
if (winning_state)
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f + 0.5f * cosf(time * 2.0f), 0.5f + 0.5f * sinf(time * 3.0f)));
static float spacing = 0.0f;
ImGui::PushItemWidth(100);
ImGui::SliderFloat("SelectableSpacing", &spacing, 0, 20, "%.0f");
ImGui::SameLine(); HelpMarker("Selectable cancel out the regular spacing between items by extending itself by ItemSpacing/2 in each direction.\nThis has two purposes:\n- Avoid the gap between items so the mouse is always hitting something.\n- Avoid the gap between items so range-selected item looks connected.\nBy changing SelectableSpacing we can enforce spacing between selectables.");
ImGui::PopItemWidth();
ImGui::Spacing();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 8));
ImGui::PushStyleVar(ImGuiStyleVar_SelectableSpacing, ImVec2(spacing, spacing));
for (int y = 0; y < 4; y++)
for (int x = 0; x < 4; x++)
{
@ -1479,8 +1460,10 @@ static void ShowDemoWindowWidgets()
ImGui::PopID();
}
ImGui::PopStyleVar(2);
if (winning_state)
ImGui::PopStyleVar();
ImGui::TreePop();
}
IMGUI_DEMO_MARKER("Widgets/Selectables/Alignment");
@ -1509,6 +1492,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");
@ -2785,6 +2770,113 @@ static void ShowDemoWindowWidgets()
}
}
static void ShowDemoWindowMultiSelect()
{
IMGUI_DEMO_MARKER("Widgets/Selection State");
if (ImGui::TreeNode("Selection State"))
{
HelpMarker("Selections can be built under Selectable(), TreeNode() or other widgets. Selection state is owned by application code/data.");
IMGUI_DEMO_MARKER("Widgets/Selection State/Single Selection");
if (ImGui::TreeNode("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/Selection State/Multiple Selection (Basic)");
if (ImGui::TreeNode("Multiple Selection (Basic)"))
{
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/Selection State/Multiple Selection (Full)");
if (ImGui::TreeNode("Multiple Selection (Full)"))
{
// Demonstrate holding/updating multi-selection data and using the BeginMultiSelect/EndMultiSelect API to support range-selection and clipping.
// In this demo we use ImGuiStorage (simple key->value storage) to avoid external dependencies but it's probably not optimal.
// In your real code you could use e.g std::unordered_set<> or your own data structure for storing selection.
// If you don't mind being limited to one view over your objects, the simplest way is to use an intrusive selection (e.g. store bool inside object, as used in examples above).
// Otherwise external set/hash/map/interval trees (storing indices, etc.) may be appropriate.
struct MySelection
{
ImGuiStorage Storage;
void Clear() { Storage.Clear(); }
void SelectAll(int count) { Storage.Data.reserve(count); Storage.Data.resize(0); for (int n = 0; n < count; n++) Storage.Data.push_back(ImGuiStoragePair((ImGuiID)n, 1)); }
void SetRange(int a, int b, int sel) { if (b < a) { int tmp = b; b = a; a = tmp; } for (int n = a; n <= b; n++) Storage.SetInt((ImGuiID)n, sel); }
bool GetSelected(int id) const { return Storage.GetInt((ImGuiID)id) != 0; }
void SetSelected(int id, bool v) { SetRange(id, id, v ? 1 : 0); }
};
static int selection_ref = 0; // Selection pivot (last clicked item, we need to preserve this to handle range-select)
static MySelection selection;
const char* random_names[] =
{
"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", "Celtuce", "Chayote", "Chinese Broccoli", "Corn", "Cucumber"
};
int COUNT = 1000;
HelpMarker("Hold CTRL and click to select multiple items. Hold SHIFT to select a range.");
ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", (unsigned int*)&ImGui::GetIO().ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard);
if (ImGui::BeginListBox("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20)))
{
ImGuiMultiSelectData* multi_select_data = ImGui::BeginMultiSelect(0, (void*)(intptr_t)selection_ref, selection.GetSelected((int)selection_ref));
if (multi_select_data->RequestClear) { selection.Clear(); }
if (multi_select_data->RequestSelectAll) { selection.SelectAll(COUNT); }
ImGuiListClipper clipper;
clipper.Begin(COUNT);
while (clipper.Step())
{
if (clipper.DisplayStart > (int)selection_ref)
multi_select_data->RangeSrcPassedBy = true;
for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++)
{
ImGui::PushID(n);
char label[64];
sprintf(label, "Object %05d (category: %s)", n, random_names[n % IM_ARRAYSIZE(random_names)]);
bool item_is_selected = selection.GetSelected(n);
ImGui::SetNextItemSelectionUserData(n);
if (ImGui::Selectable(label, item_is_selected))
selection.SetSelected(n, !item_is_selected);
ImGui::PopID();
}
}
multi_select_data = ImGui::EndMultiSelect();
selection_ref = (int)(intptr_t)multi_select_data->RangeSrc;
ImGui::EndListBox();
if (multi_select_data->RequestClear) { selection.Clear(); }
if (multi_select_data->RequestSelectAll) { selection.SelectAll(COUNT); }
if (multi_select_data->RequestSetRange) { selection.SetRange((int)(intptr_t)multi_select_data->RangeSrc, (int)(intptr_t)multi_select_data->RangeDst, multi_select_data->RangeValue ? 1 : 0); }
}
ImGui::TreePop();
}
ImGui::TreePop();
}
}
static void ShowDemoWindowLayout()
{
IMGUI_DEMO_MARKER("Layout");
@ -6771,6 +6863,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f");
ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f");
ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f");
ImGui::SliderFloat2("SelectableSpacing", (float*)&style.SelectableSpacing, 0.0f, 20.0f, "%.0f"); ImGui::SameLine(); HelpMarker("SelectableSpacing must be < ItemSpacing.\nSelectables display their highlight after canceling out the effect of ItemSpacing, so they can be look tightly packed. This setting allows to enforce spacing between them.");
ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f");
ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f");
ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f");

View File

@ -134,6 +134,7 @@ struct ImGuiInputTextDeactivateData;// Short term storage to backup text of a de
struct ImGuiLastItemData; // Status storage for last submitted items
struct ImGuiLocEntry; // A localization entry.
struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only
struct ImGuiMultiSelectState; // Multi-selection state
struct ImGuiNavItemData; // Result of a gamepad/keyboard directional navigation move query result
struct ImGuiMetricsConfig; // Storage for ShowMetricsWindow() and DebugNodeXXX() functions
struct ImGuiNextWindowData; // Storage for SetNextWindow** functions
@ -854,6 +855,7 @@ enum ImGuiItemFlagsPrivate_
// Controlled by widget code
ImGuiItemFlags_Inputable = 1 << 20, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature.
ImGuiItemFlags_HasSelectionUserData = 1 << 21, // false // Set by SetNextItemSelectionUserData()
ImGuiItemFlags_IsMultiSelect = 1 << 22, // false // Set by SetNextItemSelectionUserData()
ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups, // Please don't change, use PushItemFlag() instead.
@ -1201,10 +1203,6 @@ struct ImGuiNextWindowData
inline void ClearFlags() { Flags = ImGuiNextWindowDataFlags_None; }
};
// Multi-Selection item index or identifier when using SetNextItemSelectionUserData()/BeginMultiSelect()
// (Most users are likely to use this store an item INDEX but this may be used to store a POINTER as well.)
typedef ImS64 ImGuiSelectionUserData;
enum ImGuiNextItemDataFlags_
{
ImGuiNextItemDataFlags_None = 0,
@ -1711,8 +1709,20 @@ struct ImGuiOldColumns
// We always assume that -1 is an invalid value (which works for indices and pointers)
#define ImGuiSelectionUserData_Invalid ((ImGuiSelectionUserData)-1)
#define IMGUI_HAS_MULTI_SELECT 1
#ifdef IMGUI_HAS_MULTI_SELECT
// <this is filled in 'range_select' branch>
struct IMGUI_API ImGuiMultiSelectState
{
ImGuiMultiSelectData In; // The In requests are set and returned by BeginMultiSelect()
ImGuiMultiSelectData Out; // The Out requests are finalized and returned by EndMultiSelect()
bool InRangeDstPassedBy; // (Internal) set by the the item that match NavJustMovedToId when InRequestRangeSetNav is set.
bool InRequestSetRangeNav; // (Internal) set by BeginMultiSelect() when using Shift+Navigation. Because scrolling may be affected we can't afford a frame of lag with Shift+Navigation.
ImGuiMultiSelectState() { Clear(); }
void Clear() { In.Clear(); Out.Clear(); InRangeDstPassedBy = InRequestSetRangeNav = false; }
};
#endif // #ifdef IMGUI_HAS_MULTI_SELECT
//-----------------------------------------------------------------------------
@ -2107,6 +2117,12 @@ struct ImGuiContext
ImVec2 NavWindowingAccumDeltaPos;
ImVec2 NavWindowingAccumDeltaSize;
// Range-Select/Multi-Select
ImGuiID MultiSelectScopeId;
ImGuiWindow* MultiSelectScopeWindow;
ImGuiMultiSelectFlags MultiSelectFlags;
ImGuiMultiSelectState MultiSelectState;
// Render
float DimBgRatio; // 0.0..1.0 animation when fading in a dimming background (for modal window and CTRL+TAB list)
@ -2370,6 +2386,10 @@ struct ImGuiContext
NavWindowingToggleLayer = false;
NavWindowingToggleKey = ImGuiKey_None;
MultiSelectScopeId = 0;
MultiSelectScopeWindow = NULL;
MultiSelectFlags = 0;
DimBgRatio = 0.0f;
DragDropActive = DragDropWithinSource = DragDropWithinTarget = false;
@ -3144,7 +3164,6 @@ namespace ImGui
IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_w, float default_h);
IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x);
IMGUI_API void PushMultiItemsWidths(int components, float width_full);
IMGUI_API bool IsItemToggledSelection(); // Was the last item selection toggled? (after Selectable(), TreeNode() etc. We only returns toggle _event_ in order to handle clipping correctly)
IMGUI_API ImVec2 GetContentRegionMaxAbs();
IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess);
@ -3325,6 +3344,10 @@ namespace ImGui
IMGUI_API int TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx);
IMGUI_API int TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data);
// Multi-Select/Range-Select API
IMGUI_API void MultiSelectItemHeader(ImGuiID id, bool* p_selected);
IMGUI_API void MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed);
// Internal Columns API (this is not exposed because we will encourage transitioning to the Tables API)
IMGUI_API void SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect);
IMGUI_API void BeginColumns(const char* str_id, int count, ImGuiOldColumnFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns().
@ -3461,7 +3484,6 @@ namespace ImGui
IMGUI_API bool DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags);
IMGUI_API bool SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb);
IMGUI_API bool SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f, float hover_visibility_delay = 0.0f, ImU32 bg_col = 0);
IMGUI_API void SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data);
// Widgets: Tree Nodes
IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiID storage_id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL);

View File

@ -6465,6 +6465,26 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiID storage_id, ImGuiTreeNodeFlags
bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
const bool was_selected = selected;
// Multi-selection support (header)
const bool is_multi_select = (g.MultiSelectScopeWindow == window);
if (is_multi_select)
{
flags |= ImGuiTreeNodeFlags_OpenOnArrow;
MultiSelectItemHeader(id, &selected);
button_flags |= ImGuiButtonFlags_NoHoveredOnFocus;
// To handle drag and drop of multiple items we need to avoid clearing selection on click.
// Enabling this test makes actions using CTRL+SHIFT delay their effect on the mouse release which is annoying, but it allows drag and drop of multiple items.
if (!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore))
button_flags |= ImGuiButtonFlags_PressedOnClick;
else
button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
}
else
{
button_flags |= ImGuiButtonFlags_NoKeyModifiers;
}
bool hovered, held;
bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
bool toggled = false;
@ -6472,7 +6492,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiID storage_id, ImGuiTreeNodeFlags
{
if (pressed && g.DragDropHoldJustPressedId != id)
{
if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id))
if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id && !is_multi_select))
toggled = true;
if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job
@ -6507,13 +6527,23 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiID storage_id, ImGuiTreeNodeFlags
}
}
// In this branch, TreeNodeBehavior() cannot toggle the selection so this will never trigger.
if (selected != was_selected) //-V547
// Multi-selection support (footer)
if (is_multi_select)
{
bool pressed_copy = pressed && !toggled;
MultiSelectItemFooter(id, &selected, &pressed_copy);
if (pressed)
SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, interact_bb);
}
if (selected != was_selected)
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
// Render
const ImU32 text_col = GetColorU32(ImGuiCol_Text);
ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact;
if (is_multi_select)
nav_highlight_flags |= ImGuiNavHighlightFlags_AlwaysDraw; // Always show the nav rectangle
if (display_frame)
{
// Framed type
@ -6727,8 +6757,8 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
ImRect bb(min_x, pos.y, text_max.x, text_max.y);
if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
{
const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
const float spacing_y = style.ItemSpacing.y;
const float spacing_x = span_all_columns ? 0.0f : ImMax(style.ItemSpacing.x - style.SelectableSpacing.x, 0.0f);
const float spacing_y = ImMax(style.ItemSpacing.y - style.SelectableSpacing.y, 0.0f);
const float spacing_L = IM_TRUNC(spacing_x * 0.50f);
const float spacing_U = IM_TRUNC(spacing_y * 0.50f);
bb.Min.x -= spacing_L;
@ -6783,20 +6813,43 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; }
// Multi-selection support (header)
const bool is_multi_select = (g.MultiSelectScopeWindow == window);
const bool was_selected = selected;
if (is_multi_select)
{
MultiSelectItemHeader(id, &selected);
button_flags |= ImGuiButtonFlags_NoHoveredOnFocus;
// To handle drag and drop of multiple items we need to avoid clearing selection on click.
// Enabling this test makes actions using CTRL+SHIFT delay their effect on the mouse release which is annoying, but it allows drag and drop of multiple items.
if (!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore))
button_flags |= ImGuiButtonFlags_PressedOnClick;
else
button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
}
bool hovered, held;
bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
// Auto-select when moved into
// - This will be more fully fleshed in the range-select branch
// - This is not exposed as it won't nicely work with some user side handling of shift/control
// - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons
// - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope())
// - (2) usage will fail with clipped items
// The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId)
if (g.NavJustMovedToId == id)
selected = pressed = true;
// Multi-selection support (footer)
if (is_multi_select)
{
MultiSelectItemFooter(id, &selected, &pressed);
}
else
{
// Auto-select when moved into
// - This will be more fully fleshed in the range-select branch
// - This is not exposed as it won't nicely work with some user side handling of shift/control
// - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons
// - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope())
// - (2) usage will fail with clipped items
// The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId)
if (g.NavJustMovedToId == id)
selected = pressed = true;
}
// Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard
if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover)))
@ -6810,18 +6863,27 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
if (pressed)
MarkItemEdited(id);
// In this branch, Selectable() cannot toggle the selection so this will never trigger.
if (selected != was_selected) //-V547
if (selected != was_selected)
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
// Render
if (hovered || selected)
{
const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
// FIXME-MULTISELECT, FIXME-STYLE: Color for 'selected' elements? ImGuiCol_HeaderSelected
ImU32 col;
if (selected && !hovered)
col = GetColorU32(ImLerp(GetStyleColorVec4(ImGuiCol_Header), GetStyleColorVec4(ImGuiCol_HeaderHovered), 0.5f));
else
col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
}
if (g.NavId == id)
RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding);
{
ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding;
if (is_multi_select)
nav_highlight_flags |= ImGuiNavHighlightFlags_AlwaysDraw; // Always show the nav rectangle
RenderNavHighlight(bb, id, nav_highlight_flags);
}
if (span_all_columns)
{
@ -6841,7 +6903,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
EndDisabled();
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
return pressed; //-V1020
return pressed || (was_selected != selected); //-V1020
}
bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
@ -7049,16 +7111,227 @@ void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data)
//-------------------------------------------------------------------------
// [SECTION] Widgets: Multi-Select support
//-------------------------------------------------------------------------
// - BeginMultiSelect()
// - EndMultiSelect()
// - SetNextItemMultiSelectData()
// - MultiSelectItemHeader() [Internal]
// - MultiSelectItemFooter() [Internal]
//-------------------------------------------------------------------------
ImGuiMultiSelectData* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, void* range_ref, bool range_ref_is_selected)
{
ImGuiContext& g = *ImGui::GetCurrentContext();
ImGuiWindow* window = g.CurrentWindow;
IM_ASSERT(g.MultiSelectScopeId == 0); // No recursion allowed yet (we could allow it if we deem it useful)
IM_ASSERT(g.MultiSelectFlags == 0);
ImGuiMultiSelectState* ms = &g.MultiSelectState;
g.MultiSelectScopeId = window->IDStack.back();
g.MultiSelectScopeWindow = window;
g.MultiSelectFlags = flags;
ms->Clear();
if ((flags & ImGuiMultiSelectFlags_NoMultiSelect) == 0)
{
ms->In.RangeSrc = ms->Out.RangeSrc = range_ref;
ms->In.RangeValue = ms->Out.RangeValue = range_ref_is_selected;
}
// Auto clear when using Navigation to move within the selection (we compare SelectScopeId so it possible to use multiple lists inside a same window)
if (g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.MultiSelectScopeId)
{
if (g.IO.KeyShift)
ms->InRequestSetRangeNav = true;
if (!g.IO.KeyCtrl && !g.IO.KeyShift)
ms->In.RequestClear = true;
}
// Select All helper shortcut
if (!(flags & ImGuiMultiSelectFlags_NoMultiSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll))
if (IsWindowFocused() && g.IO.KeyCtrl && IsKeyPressed(GetKeyIndex(ImGuiKey_A)))
ms->In.RequestSelectAll = true;
#ifdef IMGUI_DEBUG_MULTISELECT
if (ms->In.RequestClear) printf("[%05d] BeginMultiSelect: RequestClear\n", g.FrameCount);
if (ms->In.RequestSelectAll) printf("[%05d] BeginMultiSelect: RequestSelectAll\n", g.FrameCount);
#endif
return &ms->In;
}
ImGuiMultiSelectData* ImGui::EndMultiSelect()
{
ImGuiContext& g = *ImGui::GetCurrentContext();
ImGuiMultiSelectState* ms = &g.MultiSelectState;
IM_ASSERT(g.MultiSelectScopeId != 0);
if (g.MultiSelectFlags & ImGuiMultiSelectFlags_NoUnselect)
ms->Out.RangeValue = true;
g.MultiSelectScopeId = 0;
g.MultiSelectScopeWindow = NULL;
g.MultiSelectFlags = 0;
#ifdef IMGUI_DEBUG_MULTISELECT
if (ms->Out.RequestClear) printf("[%05d] EndMultiSelect: RequestClear\n", g.FrameCount);
if (ms->Out.RequestSelectAll) printf("[%05d] EndMultiSelect: RequestSelectAll\n", g.FrameCount);
if (ms->Out.RequestSetRange) printf("[%05d] EndMultiSelect: RequestSetRange %p..%p = %d\n", g.FrameCount, ms->Out.RangeSrc, ms->Out.RangeDst, ms->Out.RangeValue);
#endif
return &ms->Out;
}
void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data)
{
// Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code!
// This designed so widgets can also cheaply set this before calling ItemAdd(), so we are not tied to MultiSelect api.
ImGuiContext& g = *GImGui;
g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData;
if (g.MultiSelectScopeId != 0)
g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect;
else
g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData;
g.NextItemData.SelectionUserData = selection_user_data;
}
void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected)
{
ImGuiContext& g = *GImGui;
ImGuiMultiSelectState* ms = &g.MultiSelectState;
IM_ASSERT((g.NextItemData.SelectionUserData != ImGuiSelectionUserData_Invalid) && "Forgot to call SetNextItemMultiSelectData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope");
void* item_data = (void*)g.NextItemData.SelectionUserData;
// Apply Clear/SelectAll requests requested by BeginMultiSelect().
// This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper.
// If you are using a clipper (aka not submitting every element of the list) you need to process the Clear/SelectAll request after calling BeginMultiSelect()
bool selected = *p_selected;
if (ms->In.RequestClear)
selected = false;
else if (ms->In.RequestSelectAll)
selected = true;
const bool is_range_src = (ms->In.RangeSrc == item_data);
if (is_range_src)
ms->In.RangeSrcPassedBy = true;
// When using SHIFT+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection)
// For this to work, IF the user is clipping items, they need to set RangeSrcPassedBy = true to notify the system.
if (ms->InRequestSetRangeNav)
{
IM_ASSERT(id != 0);
IM_ASSERT(g.IO.KeyShift);
const bool is_range_dst = !ms->InRangeDstPassedBy && g.NavJustMovedToId == id; // Assume that g.NavJustMovedToId is not clipped.
if (is_range_dst)
ms->InRangeDstPassedBy = true;
if (is_range_src || is_range_dst || ms->In.RangeSrcPassedBy != ms->InRangeDstPassedBy)
selected = ms->In.RangeValue;
else if (!g.IO.KeyCtrl)
selected = false;
}
*p_selected = selected;
}
void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiMultiSelectState* ms = &g.MultiSelectState;
void* item_data = (void*)g.NextItemData.SelectionUserData;
bool selected = *p_selected;
bool pressed = *p_pressed;
bool is_ctrl = g.IO.KeyCtrl;
bool is_shift = g.IO.KeyShift;
const bool is_multiselect = (g.MultiSelectFlags & ImGuiMultiSelectFlags_NoMultiSelect) == 0;
// Auto-select as you navigate a list
if (g.NavJustMovedToId == id)
{
if (!g.IO.KeyCtrl)
selected = pressed = true;
else if (g.IO.KeyCtrl && g.IO.KeyShift)
pressed = true;
}
// Right-click handling: this could be moved at the Selectable() level.
bool hovered = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
if (hovered && IsMouseClicked(1))
{
SetFocusID(g.LastItemData.ID, window);
if (!pressed && !selected)
{
pressed = true;
is_ctrl = is_shift = false;
}
}
if (pressed)
{
//-------------------------------------------------------------------------------------------------------------------------------------------------
// ACTION | Begin | Item Old | Item New | End
//-------------------------------------------------------------------------------------------------------------------------------------------------
// Keys Navigated, Ctrl=0, Shift=0 | In.Clear | Clear -> Sel=0 | Src=item, Pressed -> Sel=1 |
// Keys Navigated, Ctrl=0, Shift=1 | n/a | n/a | Dst=item, Pressed -> Sel=1, Out.Clear, Out.SetRange=1 | Clear + SetRange
// Keys Navigated, Ctrl=1, Shift=1 | n/a | n/a | Dst=item, Pressed -> Sel=Src, Out.Clear, Out.SetRange=Src | Clear + SetRange
// Mouse Pressed, Ctrl=0, Shift=0 | n/a | n/a (Sel=1) | Src=item, Pressed -> Sel=1, Out.Clear, Out.SetRange=1 | Clear + SetRange
// Mouse Pressed, Ctrl=0, Shift=1 | n/a | n/a | Dst=item, Pressed -> Sel=1, Out.Clear, Out.SetRange=1 | Clear + SetRange
//-------------------------------------------------------------------------------------------------------------------------------------------------
ImGuiInputSource input_source = (g.NavJustMovedToId != 0 && g.NavWindow == window && g.NavJustMovedToId == g.LastItemData.ID) ? g.NavInputSource : ImGuiInputSource_Mouse;
if (is_shift && is_multiselect)
{
ms->Out.RequestSetRange = true;
ms->Out.RangeDst = item_data;
if (!is_ctrl)
ms->Out.RangeValue = true;
ms->Out.RangeDirection = ms->In.RangeSrcPassedBy ? +1 : -1;
}
else
{
selected = (!is_ctrl || (g.MultiSelectFlags & ImGuiMultiSelectFlags_NoUnselect)) ? true : !selected;
ms->Out.RangeSrc = ms->Out.RangeDst = item_data;
ms->Out.RangeValue = selected;
}
if (input_source == ImGuiInputSource_Mouse)
{
// Mouse click without CTRL clears the selection, unless the clicked item is already selected
bool preserve_existing_selection = g.DragDropActive;
if (is_multiselect && !is_ctrl && !preserve_existing_selection)
ms->Out.RequestClear = true;
if (is_multiselect && !is_shift && !preserve_existing_selection && ms->Out.RequestClear)
{
// For toggle selection unless there is a Clear request, we can handle it completely locally without sending a RangeSet request.
IM_ASSERT(ms->Out.RangeSrc == ms->Out.RangeDst); // Setup by block above
ms->Out.RequestSetRange = true;
ms->Out.RangeValue = selected;
ms->Out.RangeDirection = +1;
}
if (!is_multiselect)
{
// Clear selection, set single item range
IM_ASSERT(ms->Out.RangeSrc == item_data && ms->Out.RangeDst == item_data); // Setup by block above
ms->Out.RequestClear = true;
ms->Out.RequestSetRange = true;
}
}
else if (input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Gamepad)
{
if (!is_multiselect)
ms->Out.RequestClear = true;
else if (is_shift && !is_ctrl && is_multiselect)
ms->Out.RequestClear = true;
}
}
// Update/store the selection state of the Source item (used by CTRL+SHIFT, when Source is unselected we perform a range unselect)
if (ms->Out.RangeSrc == item_data && is_ctrl && is_shift && is_multiselect && !(g.MultiSelectFlags & ImGuiMultiSelectFlags_NoUnselect))
ms->Out.RangeValue = selected;
*p_selected = selected;
*p_pressed = pressed;
}
//-------------------------------------------------------------------------
// [SECTION] Widgets: ListBox