mirror of
https://github.com/Drezil/imgui.git
synced 2025-07-12 15:59:54 +02:00
Compare commits
13 Commits
master
...
features/r
Author | SHA1 | Date | |
---|---|---|---|
a02d500990 | |||
a7977d1e8b | |||
910d83951d | |||
f68e03d92e | |||
4c376cb3e5 | |||
304270a99a | |||
dd52a2854c | |||
abfa8487eb | |||
72da877c4c | |||
0d73bf3755 | |||
9b925d51bb | |||
1b069b5d32 | |||
0d535afb41 |
@ -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
79
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 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
|
||||
|
159
imgui_demo.cpp
159
imgui_demo.cpp
@ -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"))
|
||||
{
|
||||
|
@ -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().
|
||||
|
@ -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
|
||||
//-------------------------------------------------------------------------
|
||||
|
Reference in New Issue
Block a user