mirror of
https://github.com/Drezil/imgui.git
synced 2024-11-22 03:47:00 +00:00
RangeSelect/MultiSelect: WIP range-select (ref 1861) [rebased]
Internals: Rename ImGuiSelectableFlags_PressedOnXXX to ImGuiSelectableFlags_SelectOnXXX, ImGuiButtonFlags_NoHoveredOnNav to ImGuiButtonFlags_NoHoveredOnFocus.
This commit is contained in:
parent
28ba54a32a
commit
0d535afb41
@ -1063,6 +1063,7 @@ ImGuiStyle::ImGuiStyle()
|
||||
TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appears on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected.
|
||||
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,0); // 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.
|
||||
DisplayWindowPadding = ImVec2(19,19); // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows.
|
||||
DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows.
|
||||
@ -1101,6 +1102,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor)
|
||||
LogSliderDeadzone = ImFloor(LogSliderDeadzone * scale_factor);
|
||||
TabRounding = ImFloor(TabRounding * scale_factor);
|
||||
TabMinWidthForCloseButton = (TabMinWidthForCloseButton != FLT_MAX) ? ImFloor(TabMinWidthForCloseButton * scale_factor) : FLT_MAX;
|
||||
SelectableSpacing = ImFloor(SelectableSpacing * scale_factor);
|
||||
DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor);
|
||||
DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor);
|
||||
MouseCursorScale = ImFloor(MouseCursorScale * scale_factor);
|
||||
@ -2836,6 +2838,7 @@ static const ImGuiStyleVarInfo GStyleVarInfo[] =
|
||||
{ ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding
|
||||
{ ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding
|
||||
{ ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign
|
||||
{ ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, SelectableSpacing) }, // ImGuiStyleVar_SelectableSpacing
|
||||
{ ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign
|
||||
};
|
||||
|
||||
@ -4554,6 +4557,7 @@ void ImGui::Shutdown(ImGuiContext* context)
|
||||
g.ClipboardHandlerData.clear();
|
||||
g.MenusIdSubmittedThisFrame.clear();
|
||||
g.InputTextState.ClearFreeMemory();
|
||||
g.MultiSelectScopeWindow = NULL;
|
||||
|
||||
g.SettingsWindows.clear();
|
||||
g.SettingsHandlers.clear();
|
||||
|
73
imgui.h
73
imgui.h
@ -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 SetNextItemMultiSelectData(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?
|
||||
@ -1615,6 +1626,7 @@ enum ImGuiStyleVar_
|
||||
ImGuiStyleVar_GrabRounding, // float GrabRounding
|
||||
ImGuiStyleVar_TabRounding, // float TabRounding
|
||||
ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign
|
||||
ImGuiStyleVar_SelectableSpacing, // ImVec2 SelectableSpacing
|
||||
ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign
|
||||
ImGuiStyleVar_COUNT
|
||||
};
|
||||
@ -1720,6 +1732,17 @@ 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_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.
|
||||
@ -1867,6 +1890,7 @@ struct ImGuiStyle
|
||||
float TabMinWidthForCloseButton; // Minimum width for close button to appears on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected.
|
||||
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.
|
||||
ImVec2 DisplayWindowPadding; // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows.
|
||||
ImVec2 DisplaySafeAreaPadding; // If you cannot see the edges of your screen (e.g. on a TV) increase the safe area padding. Apply to popups/tooltips as well regular windows. NB: Prefer configuring your TV sets correctly!
|
||||
@ -2317,6 +2341,55 @@ struct ImGuiListClipper
|
||||
#endif
|
||||
};
|
||||
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
|
||||
// Helpers macros to generate 32-bit encoded colors
|
||||
#ifdef IMGUI_USE_BGRA_PACKED_COLOR
|
||||
#define IM_COL32_R_SHIFT 16
|
||||
|
@ -1198,7 +1198,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 +1215,68 @@ 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.
|
||||
// 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(ImGuiStorage::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::SetNextItemMultiSelectData((void*)(intptr_t)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_DEMO_MARKER("Widgets/Selectables/Rendering more text into the same line");
|
||||
if (ImGui::TreeNode("Rendering more text into the same line"))
|
||||
{
|
||||
@ -1272,6 +1334,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++)
|
||||
{
|
||||
@ -1290,8 +1361,10 @@ static void ShowDemoWindowWidgets()
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
if (winning_state)
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
IMGUI_DEMO_MARKER("Widgets/Selectables/Alignment");
|
||||
@ -6147,6 +6220,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
|
||||
ImGui::SliderFloat2("CellPadding", (float*)&style.CellPadding, 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");
|
||||
|
@ -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
|
||||
@ -1093,6 +1094,8 @@ struct ImGuiNextItemData
|
||||
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 MultiSelectDataIsSet;
|
||||
void* MultiSelectData;
|
||||
|
||||
ImGuiNextItemData() { memset(this, 0, sizeof(*this)); }
|
||||
inline void ClearFlags() { Flags = ImGuiNextItemDataFlags_None; } // Also cleared manually by ItemAdd()!
|
||||
@ -1400,8 +1403,20 @@ struct ImGuiOldColumns
|
||||
// [SECTION] Multi-select support
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#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
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -1699,6 +1714,12 @@ struct ImGuiContext
|
||||
float NavWindowingHighlightAlpha;
|
||||
bool NavWindowingToggleLayer;
|
||||
|
||||
// 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)
|
||||
ImGuiMouseCursor MouseCursor;
|
||||
@ -1894,6 +1915,10 @@ struct ImGuiContext
|
||||
NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f;
|
||||
NavWindowingToggleLayer = false;
|
||||
|
||||
MultiSelectScopeId = 0;
|
||||
MultiSelectScopeWindow = NULL;
|
||||
MultiSelectFlags = 0;
|
||||
|
||||
DimBgRatio = 0.0f;
|
||||
MouseCursor = ImGuiMouseCursor_Arrow;
|
||||
|
||||
@ -2686,6 +2711,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().
|
||||
|
@ -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
|
||||
@ -5922,6 +5923,26 @@ 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.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;
|
||||
@ -5929,7 +5950,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 +5982,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
|
||||
@ -6170,8 +6202,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_FLOOR(spacing_x * 0.50f);
|
||||
const float spacing_U = IM_FLOOR(spacing_y * 0.50f);
|
||||
bb.Min.x -= spacing_L;
|
||||
@ -6220,20 +6252,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.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 == 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 +6305,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 +6313,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();
|
||||
@ -6279,7 +6341,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)
|
||||
@ -6292,6 +6354,227 @@ bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags
|
||||
return false;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// [SECTION] Widgets: Multi-Selection System
|
||||
//-------------------------------------------------------------------------
|
||||
// - 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* state = &g.MultiSelectState;
|
||||
g.MultiSelectScopeId = window->IDStack.back();
|
||||
g.MultiSelectScopeWindow = window;
|
||||
g.MultiSelectFlags = flags;
|
||||
state->Clear();
|
||||
|
||||
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)
|
||||
if (g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.MultiSelectScopeId)
|
||||
{
|
||||
if (g.IO.KeyShift)
|
||||
state->InRequestSetRangeNav = true;
|
||||
if (!g.IO.KeyCtrl && !g.IO.KeyShift)
|
||||
state->In.RequestClear = true;
|
||||
}
|
||||
|
||||
// Select All helper shortcut
|
||||
if (!(flags & ImGuiMultiSelectFlags_NoMultiSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll))
|
||||
if (IsWindowFocused() && 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 = *ImGui::GetCurrentContext();
|
||||
ImGuiMultiSelectState* state = &g.MultiSelectState;
|
||||
IM_ASSERT(g.MultiSelectScopeId != 0);
|
||||
if (g.MultiSelectFlags & ImGuiMultiSelectFlags_NoUnselect)
|
||||
state->Out.RangeValue = true;
|
||||
g.MultiSelectScopeId = 0;
|
||||
g.MultiSelectScopeWindow = NULL;
|
||||
g.MultiSelectFlags = 0;
|
||||
|
||||
#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::SetNextItemMultiSelectData(void* item_data)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
g.NextItemData.MultiSelectData = item_data;
|
||||
g.NextItemData.MultiSelectDataIsSet = true;
|
||||
}
|
||||
|
||||
void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiMultiSelectState* state = &g.MultiSelectState;
|
||||
|
||||
IM_ASSERT(g.NextItemData.MultiSelectDataIsSet && "Forgot to call SetNextItemMultiSelectData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope");
|
||||
void* item_data = g.NextItemData.MultiSelectData;
|
||||
|
||||
// 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.IO.KeyShift);
|
||||
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.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* state = &g.MultiSelectState;
|
||||
|
||||
void* item_data = g.NextItemData.MultiSelectData;
|
||||
g.NextItemData.MultiSelectDataIsSet = false;
|
||||
|
||||
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) ? 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)
|
||||
state->Out.RequestClear = true;
|
||||
else if (is_shift && !is_ctrl && 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
|
||||
//-------------------------------------------------------------------------
|
||||
|
Loading…
Reference in New Issue
Block a user