Compare commits

...

13 Commits

Author SHA1 Message Date
a02d500990 RangeSelect/MultiSelect: Temporary fix/work-around for child/popup to not inherit MultiSelectEnabled flag, until we make mulit-select data stackable. 2022-03-15 11:52:28 +07:00
a7977d1e8b RangeSelect/MultiSelect: Fix testing key mods from after the nav request (remove need to hold the mod longer) 2022-03-15 11:52:28 +07:00
910d83951d RangeSelect/MultiSelect: Fix Selectable() ambiguous return value, clarify need to use IsItemToggledSelection(). 2022-03-15 11:52:28 +07:00
f68e03d92e RangeSelect/MultiSelect: Comments. Tweak demo. 2022-03-15 11:52:28 +07:00
4c376cb3e5 RangeSelect/MultiSelect: Fixed CTRL+A not testing focus scope id. Fixed CTRL+A not testing active id. Added demo code.
Comments.
2022-03-15 11:52:27 +07:00
304270a99a RangeSelect/MultiSelect: Fix for TreeNode following merge of 011d4755. Demo: basic test for tree nodes. 2022-03-15 11:52:27 +07:00
dd52a2854c RangeSelect/MultiSelect: Transition to use FocusScope bits merged in master.
Preserve ability to shift+arrow into an item that is part of FocusScope but doesn't carry a selection without breaking selection.
2022-03-15 11:52:27 +07:00
abfa8487eb RangeSelect/MultiSelect: Renamed SetNextItemMultiSelectData() to SetNextItemSelectionData() 2022-03-15 11:52:27 +07:00
72da877c4c RangeSelect/MultiSelect: Demo sharing selection helper code. Fixed static analyzer warnings. 2022-03-15 11:52:27 +07:00
0d73bf3755 RangeSelect/MultiSelect: Added IMGUI_HAS_MULTI_SELECT define. Fixed right-click toggling selection without clearing active id, could lead to MarkItemEdited() asserting. Fixed demo. 2022-03-15 11:52:26 +07:00
9b925d51bb RangeSelect/MultiSelect: Fix so that shift+arrow when landing on an item that doesn't hold multi-select data doesn't trigger an unbounded range-select that ends up selecting everything until the end. 2022-03-15 11:52:26 +07:00
1b069b5d32 RangeSelect/MultiSelect: Removed SelectableSpacing as I'm not sure it is of use for now (history insert) 2022-03-15 11:52:26 +07:00
0d535afb41 RangeSelect/MultiSelect: WIP range-select (ref 1861) [rebased]
Internals: Rename ImGuiSelectableFlags_PressedOnXXX to ImGuiSelectableFlags_SelectOnXXX, ImGuiButtonFlags_NoHoveredOnNav to ImGuiButtonFlags_NoHoveredOnFocus.
2022-03-15 11:52:26 +07:00
5 changed files with 612 additions and 28 deletions

View File

@ -9734,6 +9734,7 @@ static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result)
result->FocusScopeId = window->DC.NavFocusScopeIdCurrent;
result->InFlags = g.LastItemData.InFlags;
result->RectRel = WindowRectAbsToRel(window, g.LastItemData.NavRect);
result->HasSelectionData = (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasSelectionData) != 0; // FIXME: Bizarre but valid we are calling NavApplyItemToResult() before clering the NextItemData
}
// We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above)
@ -10490,6 +10491,7 @@ void ImGui::NavMoveRequestApplyResult()
g.NavJustMovedToId = result->ID;
g.NavJustMovedToFocusScopeId = result->FocusScopeId;
g.NavJustMovedToKeyMods = g.NavMoveKeyMods;
g.NavJustMovedToHasSelectionData = result->HasSelectionData;
}
// Focus

79
imgui.h
View File

@ -150,6 +150,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.SetPlatformImeDataFn() function.
@ -191,6 +192,7 @@ typedef int ImGuiHoveredFlags; // -> enum ImGuiHoveredFlags_ // Flags: f
typedef int ImGuiInputTextFlags; // -> enum ImGuiInputTextFlags_ // Flags: for InputText(), InputTextMultiline()
typedef int ImGuiKeyModFlags; // -> enum ImGuiKeyModFlags_ // Flags: for io.KeyMods (Ctrl/Shift/Alt/Super)
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()
@ -612,6 +614,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 SetNextItemSelectionData(void* item_data);
// Widgets: List Boxes
// - This is essentially a thin wrapper to using BeginChild/EndChild with some stylistic changes.
// - The BeginListBox()/EndListBox() api allows you to manage your contents and selection state however you want it, by creating e.g. Selectable() or any items.
@ -835,6 +845,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 requires 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 requires 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?
@ -1720,6 +1731,19 @@ enum ImGuiMouseCursor_
ImGuiMouseCursor_COUNT
};
// 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_None = 0,
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
};
// Enumeration for ImGui::SetWindow***(), SetNextWindow***(), SetNextItem***() functions
// Represent a condition.
// Important: Treat as a regular enum! Do NOT combine multiple values using binary operators! All the functions above treat 0 as a shortcut to ImGuiCond_Always.
@ -2317,6 +2341,61 @@ struct ImGuiListClipper
#endif
};
// Abstract:
// - This system helps you implements standard multi-selection idioms (CTRL+Click/Arrow, SHIFT+Click/Arrow, etc) in a way that allow
// selectable 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 + clipping, you can handle 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.
// - 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.
// - The void* RangeSrc/RangeDst value represent a selectable object. They are the values you pass to SetNextItemSelectionData().
// 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
// between them to honor range selection. But the code never assume that sortable integers are used (you may store pointers to your object,
// and then from the pointer have your own way of iterating from RangeSrc to RangeDst).
// - In the spirit of Dear ImGui design, your code own 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.
// Usage flow:
// 1) Call BeginMultiSelect() with the last saved value of ->RangeSrc and its selection state.
// It is because you need to pass its selection state (and you own selection) that we don't store this value in Dear ImGui.
// (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 SetNextItemSelectionData() + Selectable()/TreeNode() calls.
// Call IsItemToggledSelection() to query if the selection state has been toggled, if you need the info immediately for your display (before EndMultiSelect()).
// When cannot return a "IsItemSelected()" value because we need to consider clipped/unprocessed items, 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.
#define IMGUI_HAS_MULTI_SELECT // Multi-Select/Range-Select WIP branch // <-- This is currently _not_ in the top of imgui.h to prevent merge conflicts.
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; // In loop // (If clipping) Need to be set by user if 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;
}
};
// Helpers macros to generate 32-bit encoded colors
#ifdef IMGUI_USE_BGRA_PACKED_COLOR
#define IM_COL32_R_SHIFT 16

View File

@ -554,6 +554,43 @@ void ImGui::ShowDemoWindow(bool* p_open)
ImGui::End();
}
// [Advanced] Helper class to simulate storage of a multi-selection state, used by the advanced multi-selection demos.
// We use ImGuiStorage (simple key->value storage) to avoid external dependencies but it's probably not optimal.
// To store a single-selection:
// - You only need a single variable and don't need any of this!
// To store a multi-selection, in your real application you could:
// - Use intrusively stored selection (e.g. 'bool IsSelected' inside your object). This is by far the simplest
// way to store your selection data, but it means you cannot have multiple simultaneous views over your objects.
// This is what may of the simpler demos in this file are using (so they are not using this class).
// - Otherwise, any externally stored unordered_set/set/hash/map/interval trees (storing indices, objects id, etc.)
// are generally appropriate. Even a large array of bool might work for you...
// - If you need to handle extremely large selections, it might be advantageous to support a "negative" mode in
// your storage, so "Select All" becomes "Negative=1, Clear" and then sparse unselect can add to the storage.
// About RefItem:
// - The MultiSelect API requires you to store information about the reference/pivot item (generally the last clicked item).
struct ExampleSelection
{
ImGuiStorage Storage;
int SelectionSize; // Number of selected items (== number of 1 in the Storage, maintained by this class)
int RangeRef; // Reference/pivot item (generally last clicked item)
ExampleSelection() { RangeRef = 0; Clear(); }
void Clear() { Storage.Clear(); SelectionSize = 0; }
bool GetSelected(int n) const { return Storage.GetInt((ImGuiID)n, 0) != 0; }
void SetSelected(int n, bool v) { int* p_int = Storage.GetIntRef((ImGuiID)n, 0); if (*p_int == (int)v) return; if (v) SelectionSize++; else SelectionSize--; *p_int = (bool)v; }
int GetSelectionSize() const { return SelectionSize; }
// When using SelectAll() / SetRange() we assume that our objects ID are indices.
// In this demo we always store selection using indices and never in another manner (e.g. object ID or pointers).
// If your selection system is storing selection using object ID and you want to support Shift+Click range-selection,
// you will need a way to iterate from one object to another given the ID you use.
// You are likely to need some kind of data structure to convert 'view index' <> 'object ID'.
// FIXME-MULTISELECT: Would be worth providing a demo of doing this.
// FIXME-MULTISELECT: SetRange() is currently very inefficient since it doesn't take advantage of the fact that ImGuiStorage stores sorted key.
void SetRange(int n1, int n2, bool v) { if (n2 < n1) { int tmp = n2; n2 = n1; n1 = tmp; } for (int n = n1; n <= n2; n++) SetSelected(n, v); }
void SelectAll(int count) { Storage.Data.resize(count); for (int idx = 0; idx < count; idx++) Storage.Data[idx] = ImGuiStorage::ImGuiStoragePair((ImGuiID)idx, 1); SelectionSize = count; } // This could be using SetRange(), but it this way is faster.
};
static void ShowDemoWindowWidgets()
{
IMGUI_DEMO_MARKER("Widgets");
@ -1198,7 +1235,7 @@ static void ShowDemoWindowWidgets()
ImGui::TreePop();
}
IMGUI_DEMO_MARKER("Widgets/Selectables/Multiple Selection");
if (ImGui::TreeNode("Selection State: Multiple Selection"))
if (ImGui::TreeNode("Selection State: Multiple Selection (Simplified)"))
{
HelpMarker("Hold CTRL and click to select multiple items.");
static bool selection[5] = { false, false, false, false, false };
@ -1215,6 +1252,126 @@ static void ShowDemoWindowWidgets()
}
ImGui::TreePop();
}
if (ImGui::TreeNode("Selection State: Multiple Selection (Full)"))
{
// Demonstrate holding/updating multi-selection data and using the BeginMultiSelect/EndMultiSelect API to support range-selection and clipping.
static ExampleSelection 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"
};
// Test both Selectable() and TreeNode() widgets
enum WidgetType { WidgetType_Selectable, WidgetType_TreeNode };
static bool use_columns = false;
static WidgetType widget_type = WidgetType_TreeNode;
if (ImGui::RadioButton("Selectables", widget_type == WidgetType_Selectable)) { widget_type = WidgetType_Selectable; }
ImGui::SameLine();
if (ImGui::RadioButton("Tree nodes", widget_type == WidgetType_TreeNode)) { widget_type = WidgetType_TreeNode; }
ImGui::SameLine();
ImGui::Checkbox("Use 2 columns", &use_columns);
ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", &ImGui::GetIO().ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard);
ImGui::SameLine(); HelpMarker("Hold CTRL and click to select multiple items. Hold SHIFT to select a range. Keyboard is also supported.");
// Open a scrolling region
const int ITEMS_COUNT = 1000;
if (ImGui::BeginListBox("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20)))
{
ImVec2 color_button_sz(ImGui::GetFontSize(), ImGui::GetFontSize());
if (widget_type == WidgetType_TreeNode)
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f));
ImGuiMultiSelectData* multi_select_data = ImGui::BeginMultiSelect(ImGuiMultiSelectFlags_None, (void*)(intptr_t)selection.RangeRef, selection.GetSelected(selection.RangeRef));
if (multi_select_data->RequestClear) { selection.Clear(); }
if (multi_select_data->RequestSelectAll) { selection.SelectAll(ITEMS_COUNT); }
if (use_columns)
{
ImGui::Columns(2);
//ImGui::PushStyleVar(ImGuiStyleVar_FrtemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f));
}
ImGuiListClipper clipper;
clipper.Begin(ITEMS_COUNT);
while (clipper.Step())
{
if (clipper.DisplayStart > selection.RangeRef)
multi_select_data->RangeSrcPassedBy = true;
for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++)
{
ImGui::PushID(n);
const char* category = random_names[n % IM_ARRAYSIZE(random_names)];
char label[64];
sprintf(label, "Object %05d (category: %s)", n, category);
bool item_is_selected = selection.GetSelected(n);
// Emit a color button, to test that Shift+LeftArrow landing on an item that is not part
// of the selection scope doesn't erroneously alter our selection (FIXME-TESTS: Add a test for that!).
ImU32 dummy_col = (ImU32)ImGui::GetID(label);
ImGui::ColorButton("##", ImColor(dummy_col), ImGuiColorEditFlags_NoTooltip, color_button_sz);
ImGui::SameLine();
ImGui::SetNextItemSelectionData((void*)(intptr_t)n);
if (widget_type == WidgetType_Selectable)
{
ImGui::Selectable(label, item_is_selected);
if (ImGui::IsItemToggledSelection())
selection.SetSelected(n, !item_is_selected);
}
else if (widget_type == WidgetType_TreeNode)
{
ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAvailWidth;
tree_node_flags |= ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
if (item_is_selected)
tree_node_flags |= ImGuiTreeNodeFlags_Selected;
bool open = ImGui::TreeNodeEx(label, tree_node_flags);
if (ImGui::IsItemToggledSelection())
selection.SetSelected(n, !item_is_selected);
if (open)
ImGui::TreePop();
}
// Right-click: context menu
if (ImGui::BeginPopupContextItem())
{
ImGui::Text("(Testing Selectable inside an embedded popup)");
ImGui::Selectable("Close");
ImGui::EndPopup();
}
if (use_columns)
{
ImGui::NextColumn();
ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::InputText("###NoLabel", (char*)(void*)category, strlen(category), ImGuiInputTextFlags_ReadOnly);
ImGui::PopStyleVar();
ImGui::NextColumn();
}
ImGui::PopID();
}
}
if (use_columns)
ImGui::Columns(1);
// Apply multi-select requests
multi_select_data = ImGui::EndMultiSelect();
selection.RangeRef = (int)(intptr_t)multi_select_data->RangeSrc;
if (multi_select_data->RequestClear) { selection.Clear(); }
if (multi_select_data->RequestSelectAll) { selection.SelectAll(ITEMS_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); }
if (widget_type == WidgetType_TreeNode)
ImGui::PopStyleVar();
ImGui::EndListBox();
}
ImGui::TreePop();
}
IMGUI_DEMO_MARKER("Widgets/Selectables/Rendering more text into the same line");
if (ImGui::TreeNode("Rendering more text into the same line"))
{

View File

@ -122,6 +122,7 @@ struct ImGuiGroupData; // Stacked storage data for BeginGroup()/End
struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box
struct ImGuiLastItemData; // Status storage for last submitted items
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
@ -1081,18 +1082,20 @@ struct ImGuiNextWindowData
enum ImGuiNextItemDataFlags_
{
ImGuiNextItemDataFlags_None = 0,
ImGuiNextItemDataFlags_HasWidth = 1 << 0,
ImGuiNextItemDataFlags_HasOpen = 1 << 1
ImGuiNextItemDataFlags_None = 0,
ImGuiNextItemDataFlags_HasWidth = 1 << 0,
ImGuiNextItemDataFlags_HasOpen = 1 << 1,
ImGuiNextItemDataFlags_HasSelectionData = 1 << 2
};
struct ImGuiNextItemData
{
ImGuiNextItemDataFlags Flags;
float Width; // Set by SetNextItemWidth()
ImGuiID FocusScopeId; // Set by SetNextItemMultiSelectData() (!= 0 signify value has been set, so it's an alternate version of HasSelectionData, we don't use Flags for this because they are cleared too early. This is mostly used for debugging)
float Width; // Set by SetNextItemWidth()
ImGuiID FocusScopeId; // Set by SetNextItemMultiSelectData() (!= 0 signify value has been set, so it's an alternate version of HasSelectionData, we don't use Flags for this because they are cleared too early. This is mostly used for debugging)
ImGuiCond OpenCond;
bool OpenVal; // Set by SetNextItemOpen()
bool OpenVal; // Set by SetNextItemOpen()
void* SelectionData; // Set by SetNextItemSelectionData() (note that NULL/0 is a valid value)
ImGuiNextItemData() { memset(this, 0, sizeof(*this)); }
inline void ClearFlags() { Flags = ImGuiNextItemDataFlags_None; } // Also cleared manually by ItemAdd()!
@ -1335,9 +1338,10 @@ struct ImGuiNavItemData
float DistBox; // Move // Best candidate box distance to current NavId
float DistCenter; // Move // Best candidate center distance to current NavId
float DistAxial; // Move // Best candidate axial distance to current NavId
bool HasSelectionData; // Move // Copy of (NextItemData.Flags & ImGuiNextItemDataFlags_HasSelection)
ImGuiNavItemData() { Clear(); }
void Clear() { Window = NULL; ID = FocusScopeId = 0; InFlags = 0; DistBox = DistCenter = DistAxial = FLT_MAX; }
void Clear() { Window = NULL; ID = FocusScopeId = 0; InFlags = 0; DistBox = DistCenter = DistAxial = FLT_MAX; HasSelectionData = false; }
};
//-----------------------------------------------------------------------------
@ -1401,7 +1405,20 @@ struct ImGuiOldColumns
//-----------------------------------------------------------------------------
#ifdef IMGUI_HAS_MULTI_SELECT
// <this is filled in 'range_select' branch>
struct IMGUI_API ImGuiMultiSelectState
{
ImGuiID FocusScopeId; // Same as CurrentWindow->DC.FocusScopeIdCurrent (unless another selection scope was pushed manually)
ImGuiID BackupFocusScopeId;
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() { FocusScopeId = BackupFocusScopeId = 0; In.Clear(); Out.Clear(); InRangeDstPassedBy = InRequestSetRangeNav = false; }
};
#endif // #ifdef IMGUI_HAS_MULTI_SELECT
//-----------------------------------------------------------------------------
@ -1657,6 +1674,7 @@ struct ImGuiContext
ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest).
ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest).
ImGuiKeyModFlags NavJustMovedToKeyMods;
bool NavJustMovedToHasSelectionData; // " (FIXME-NAV: We should maybe just store ImGuiNavMoveResult)
ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame.
ImGuiActivateFlags NavNextActivateFlags;
ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS WILL ONLY BE None or NavGamepad or NavKeyboard.
@ -1699,6 +1717,12 @@ struct ImGuiContext
float NavWindowingHighlightAlpha;
bool NavWindowingToggleLayer;
// Range-Select/Multi-Select
ImGuiWindow* MultiSelectEnabledWindow; // FIXME-MULTISELECT: We currently don't support recursing/stacking multi-select
ImGuiMultiSelectFlags MultiSelectFlags;
ImGuiMultiSelectState MultiSelectState;
ImGuiKeyModFlags MultiSelectKeyMods;
// Render
float DimBgRatio; // 0.0..1.0 animation when fading in a dimming background (for modal window and CTRL+TAB list)
ImGuiMouseCursor MouseCursor;
@ -1869,6 +1893,7 @@ struct ImGuiContext
NavJustMovedToId = NavJustMovedToFocusScopeId = NavNextActivateId = 0;
NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None;
NavJustMovedToKeyMods = ImGuiKeyModFlags_None;
NavJustMovedToHasSelectionData = false;
NavInputSource = ImGuiInputSource_None;
NavLayer = ImGuiNavLayer_Main;
NavIdIsAlive = false;
@ -1894,6 +1919,10 @@ struct ImGuiContext
NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f;
NavWindowingToggleLayer = false;
MultiSelectEnabledWindow = NULL;
MultiSelectFlags = ImGuiMultiSelectFlags_None;
MultiSelectKeyMods = ImGuiKeyModFlags_None;
DimBgRatio = 0.0f;
MouseCursor = ImGuiMouseCursor_Arrow;
@ -2686,6 +2715,10 @@ namespace ImGui
IMGUI_API void ClearDragDrop();
IMGUI_API bool IsDragDropPayloadBeingAccepted();
// New Multi-Selection/Range-Selection API (FIXME-WIP)
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().

View File

@ -18,6 +18,7 @@ Index of this file:
// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
// [SECTION] Widgets: Selectable
// [SECTION] Widgets: Multi-Selection System
// [SECTION] Widgets: ListBox
// [SECTION] Widgets: PlotLines, PlotHistogram
// [SECTION] Widgets: Value helpers
@ -5900,8 +5901,6 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x;
const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x;
const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2);
if (window != g.HoveredWindow || !is_mouse_x_over_arrow)
button_flags |= ImGuiButtonFlags_NoKeyModifiers;
// Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags.
// Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support.
@ -5922,6 +5921,30 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
const bool was_selected = selected;
// Multi-selection support (header)
const bool is_multi_select = (g.MultiSelectEnabledWindow == window);
if (is_multi_select)
{
MultiSelectItemHeader(id, &selected);
button_flags |= ImGuiButtonFlags_NoHoveredOnFocus;
// We absolutely need to distinguish open vs select so this is the default when multi-select is enabled.
flags |= ImGuiTreeNodeFlags_OpenOnArrow;
// 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 MouseUp which is annoying, but it allows drag and drop of multiple items.
// FIXME-MULTISELECT: Consider opt-in for drag and drop behavior in ImGuiMultiSelectFlags?
if (!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore))
button_flags |= ImGuiButtonFlags_PressedOnClick;
else
button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
}
else
{
if (window != g.HoveredWindow || !is_mouse_x_over_arrow)
button_flags |= ImGuiButtonFlags_NoKeyModifiers;
}
bool hovered, held;
bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
bool toggled = false;
@ -5929,7 +5952,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
{
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
@ -5961,16 +5984,27 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen;
}
}
// 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, window->DC.NavFocusScopeIdCurrent, interact_bb);
}
if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
SetItemAllowOverlap();
// In this branch, TreeNodeBehavior() cannot toggle the selection so this will never trigger.
if (selected != was_selected) //-V547
if (selected != was_selected)
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
// Render
const ImU32 text_col = GetColorU32(ImGuiCol_Text);
ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin;
if (is_multi_select)
nav_highlight_flags |= ImGuiNavHighlightFlags_AlwaysDraw; // Always show the nav rectangle
if (display_frame)
{
// Framed type
@ -6220,20 +6254,43 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
if (flags & ImGuiSelectableFlags_AllowItemOverlap) { button_flags |= ImGuiButtonFlags_AllowItemOverlap; }
// Multi-selection support (header)
const bool is_multi_select = (g.MultiSelectEnabledWindow == 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 == window->DC.NavFocusScopeIdCurrent)
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 == window->DC.NavFocusScopeIdCurrent)
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)))
@ -6250,8 +6307,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
if (flags & ImGuiSelectableFlags_AllowItemOverlap)
SetItemAllowOverlap();
// 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
@ -6259,10 +6315,18 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
hovered = true;
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);
}
RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin | 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 && window->DC.CurrentColumns)
PopColumnsBackground();
@ -6278,6 +6342,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
if (disabled_item && !disabled_global)
EndDisabled();
// Users of BeginMultiSelect() scope: call ImGui::IsItemToggledSelection() to retrieve selection toggle. Selectable() returns a pressed state!
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
return pressed; //-V1020
}
@ -6292,6 +6357,254 @@ bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags
return false;
}
//-------------------------------------------------------------------------
// [SECTION] Widgets: Multi-Selection System
//-------------------------------------------------------------------------
// - BeginMultiSelect()
// - EndMultiSelect()
// - SetNextItemSelectionData()
// - MultiSelectItemHeader() [Internal]
// - MultiSelectItemFooter() [Internal]
//-------------------------------------------------------------------------
// FIXME: Shift+click on an item that has no multi-select data could treat selection the same as the last item with such data?
// The problem is that this may conflict with other behaviors of those items?
//-------------------------------------------------------------------------
ImGuiMultiSelectData* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, void* range_ref, bool range_ref_is_selected)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
IM_ASSERT(g.MultiSelectEnabledWindow == NULL); // No recursion allowed yet (we could allow it if we deem it useful)
IM_ASSERT(g.MultiSelectFlags == 0);
IM_ASSERT(g.MultiSelectState.FocusScopeId == 0);
// FIXME: BeginFocusScope()
ImGuiMultiSelectState* state = &g.MultiSelectState;
state->Clear();
state->BackupFocusScopeId = window->DC.NavFocusScopeIdCurrent;
state->FocusScopeId = window->DC.NavFocusScopeIdCurrent = window->IDStack.back();
g.MultiSelectEnabledWindow = window;
g.MultiSelectFlags = flags;
// Use copy of keyboard mods at the time of the request, otherwise we would requires mods to be held for an extra frame.
g.MultiSelectKeyMods = g.NavJustMovedToId ? g.NavJustMovedToKeyMods : g.IO.KeyMods;
if ((flags & ImGuiMultiSelectFlags_NoMultiSelect) == 0)
{
state->In.RangeSrc = state->Out.RangeSrc = range_ref;
state->In.RangeValue = state->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)
// FIXME: Polling key mods after the fact (frame following the move request) is incorrect, but latching it would requires non-trivial change in MultiSelectItemFooter()
if (g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == state->FocusScopeId && g.NavJustMovedToHasSelectionData)
{
if (g.MultiSelectKeyMods & ImGuiKeyModFlags_Shift)
state->InRequestSetRangeNav = true;
if ((g.MultiSelectKeyMods & (ImGuiKeyModFlags_Ctrl | ImGuiKeyModFlags_Shift)) == 0)
state->In.RequestClear = true;
}
// Select All helper shortcut
// Note: we are comparing FocusScope so we don't need to be testing for IsWindowFocused()
if (!(flags & ImGuiMultiSelectFlags_NoMultiSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll))
if (state->FocusScopeId == g.NavFocusScopeId && g.ActiveId == 0)
if (g.IO.KeyCtrl && IsKeyPressed(GetKeyIndex(ImGuiKey_A)))
state->In.RequestSelectAll = true;
#ifdef IMGUI_DEBUG_MULTISELECT
if (state->In.RequestClear) printf("[%05d] BeginMultiSelect: RequestClear\n", g.FrameCount);
if (state->In.RequestSelectAll) printf("[%05d] BeginMultiSelect: RequestSelectAll\n", g.FrameCount);
#endif
return &state->In;
}
ImGuiMultiSelectData* ImGui::EndMultiSelect()
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiMultiSelectState* state = &g.MultiSelectState;
IM_ASSERT(g.MultiSelectState.FocusScopeId != 0);
IM_ASSERT(g.MultiSelectState.FocusScopeId == window->DC.NavFocusScopeIdCurrent);
if (g.MultiSelectFlags & ImGuiMultiSelectFlags_NoUnselect)
state->Out.RangeValue = true;
g.MultiSelectState.FocusScopeId = 0;
window->DC.NavFocusScopeIdCurrent = g.MultiSelectState.BackupFocusScopeId;
g.MultiSelectEnabledWindow = NULL;
g.MultiSelectFlags = ImGuiMultiSelectFlags_None;
#ifdef IMGUI_DEBUG_MULTISELECT
if (state->Out.RequestClear) printf("[%05d] EndMultiSelect: RequestClear\n", g.FrameCount);
if (state->Out.RequestSelectAll) printf("[%05d] EndMultiSelect: RequestSelectAll\n", g.FrameCount);
if (state->Out.RequestSetRange) printf("[%05d] EndMultiSelect: RequestSetRange %p..%p = %d\n", g.FrameCount, state->Out.RangeSrc, state->Out.RangeDst, state->Out.RangeValue);
#endif
return &state->Out;
}
void ImGui::SetNextItemSelectionData(void* item_data)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
IM_ASSERT(window->DC.NavFocusScopeIdCurrent != 0);
g.NextItemData.SelectionData = item_data;
g.NextItemData.FocusScopeId = window->DC.NavFocusScopeIdCurrent;
// Note that the flag 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.
g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasSelectionData;
}
void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiMultiSelectState* state = &g.MultiSelectState;
IM_UNUSED(window);
IM_ASSERT(g.NextItemData.FocusScopeId == window->DC.NavFocusScopeIdCurrent && "Forgot to call SetNextItemSelectionData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope");
void* item_data = g.NextItemData.SelectionData;
// 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 (state->In.RequestClear)
selected = false;
else if (state->In.RequestSelectAll)
selected = true;
const bool is_range_src = (state->In.RangeSrc == item_data);
if (is_range_src)
state->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 (state->InRequestSetRangeNav)
{
IM_ASSERT(id != 0);
IM_ASSERT((g.MultiSelectKeyMods & ImGuiKeyModFlags_Shift) != 0);
const bool is_range_dst = !state->InRangeDstPassedBy && g.NavJustMovedToId == id; // Assume that g.NavJustMovedToId is not clipped.
if (is_range_dst)
state->InRangeDstPassedBy = true;
if (is_range_src || is_range_dst || state->In.RangeSrcPassedBy != state->InRangeDstPassedBy)
selected = state->In.RangeValue;
else if ((g.MultiSelectKeyMods & ImGuiKeyModFlags_Ctrl) == 0)
selected = false;
}
*p_selected = selected;
}
void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiMultiSelectState* state = &g.MultiSelectState;
void* item_data = g.NextItemData.SelectionData;
g.NextItemData.FocusScopeId = 0;
bool selected = *p_selected;
bool pressed = *p_pressed;
const bool is_multiselect = (g.MultiSelectFlags & ImGuiMultiSelectFlags_NoMultiSelect) == 0;
bool is_ctrl = (g.MultiSelectKeyMods & ImGuiKeyModFlags_Ctrl) != 0;
bool is_shift = (g.MultiSelectKeyMods & ImGuiKeyModFlags_Shift) != 0;
// Auto-select as you navigate a list
if (g.NavJustMovedToId == id)
{
if (is_ctrl && is_shift)
pressed = true;
else if (!is_ctrl)
selected = pressed = true;
}
// Right-click handling: this could be moved at the Selectable() level.
bool hovered = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
if (hovered && IsMouseClicked(1))
{
if (g.ActiveId != 0 && g.ActiveId != id)
ClearActiveID();
SetFocusID(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) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse;
if (is_shift && is_multiselect)
{
state->Out.RequestSetRange = true;
state->Out.RangeDst = item_data;
if (!is_ctrl)
state->Out.RangeValue = true;
state->Out.RangeDirection = state->In.RangeSrcPassedBy ? +1 : -1;
}
else
{
selected = (!is_ctrl || (g.MultiSelectFlags & ImGuiMultiSelectFlags_NoUnselect)) ? true : !selected;
state->Out.RangeSrc = state->Out.RangeDst = item_data;
state->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)
state->Out.RequestClear = true;
if (is_multiselect && !is_shift && !preserve_existing_selection && state->Out.RequestClear)
{
// For toggle selection unless there is a Clear request, we can handle it completely locally without sending a RangeSet request.
IM_ASSERT(state->Out.RangeSrc == state->Out.RangeDst); // Setup by block above
state->Out.RequestSetRange = true;
state->Out.RangeValue = selected;
state->Out.RangeDirection = +1;
}
if (!is_multiselect)
{
// Clear selection, set single item range
IM_ASSERT(state->Out.RangeSrc == item_data && state->Out.RangeDst == item_data); // Setup by block above
state->Out.RequestClear = true;
state->Out.RequestSetRange = true;
}
}
else if (input_source == ImGuiInputSource_Nav)
{
if (is_multiselect && is_shift && !is_ctrl)
state->Out.RequestClear = true;
else if (!is_multiselect)
state->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 (state->Out.RangeSrc == item_data && is_ctrl && is_shift && is_multiselect && !(g.MultiSelectFlags & ImGuiMultiSelectFlags_NoUnselect))
state->Out.RangeValue = selected;
*p_selected = selected;
*p_pressed = pressed;
}
//-------------------------------------------------------------------------
// [SECTION] Widgets: ListBox
//-------------------------------------------------------------------------