mirror of
https://github.com/Drezil/imgui.git
synced 2024-11-15 01:17:00 +00:00
RangeSelect/MultiSelect: Comments. Tweak demo.
This commit is contained in:
parent
4c376cb3e5
commit
f68e03d92e
41
imgui.h
41
imgui.h
@ -2342,31 +2342,36 @@ struct ImGuiListClipper
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Abstract:
|
// Abstract:
|
||||||
// - This system implements standard multi-selection idioms (CTRL+Click/Arrow, SHIFT+Click/Arrow, etc) in a way that allow items to be
|
// - This system helps you implements standard multi-selection idioms (CTRL+Click/Arrow, SHIFT+Click/Arrow, etc) in a way that allow
|
||||||
// fully clipped (= not submitted at all) when not visible. Clipping is typically provided by ImGuiListClipper.
|
// 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.
|
// 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,
|
// Note however that if you don't need SHIFT+Click/Arrow range-select + clipping, you can handle a simpler form of multi-selection
|
||||||
// by reacting to click/presses on Selectable() items and checking keyboard modifiers.
|
// 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 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
|
// - 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
|
// 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,
|
// 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).
|
// 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.
|
// 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 SetNextItemSelectionData().
|
// - 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. But the code never assume that sortable integers are used.
|
// 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
|
||||||
// - 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),
|
// between them to honor range selection. But the code never assume that sortable integers are used (you may store pointers to your 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.
|
// 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:
|
// 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,
|
// 1) Call BeginMultiSelect() with the last saved value of ->RangeSrc and its selection state.
|
||||||
// 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*.
|
// It is because you need to pass its selection state (and you own selection) that we don't store this value in Dear ImGui.
|
||||||
// 2) Honor Clear/SelectAll requests by updating your selection data. [Only required if you are using a clipper in step 4]
|
// (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*).
|
||||||
// 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]
|
// 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.
|
// 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; }
|
// 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.
|
// 4) Submit your items with SetNextItemSelectionData() + 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.
|
// Call IsItemToggledSelection() to query if the selection state has been toggled, if you need the info immediately for your display (before EndMultiSelect()).
|
||||||
// When cannot reliably return a "IsItemSelected()" value because we need to consider clipped (unprocessed) item, this is why we return a toggle event instead.
|
// 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)
|
// 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)
|
// 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.
|
// If you submit all items (no clipper), Step 2 and 3 and will be handled by Selectable() on a per-item basis.
|
||||||
@ -2376,7 +2381,7 @@ struct ImGuiMultiSelectData
|
|||||||
bool RequestClear; // Begin, End // Request user to clear selection
|
bool RequestClear; // Begin, End // Request user to clear selection
|
||||||
bool RequestSelectAll; // Begin, End // Request user to select all
|
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 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 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.
|
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* 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.
|
void* RangeDst; // End // End: parameter from RequestSetRange request.
|
||||||
|
@ -566,12 +566,15 @@ void ImGui::ShowDemoWindow(bool* p_open)
|
|||||||
// are generally appropriate. Even a large array of bool might work for you...
|
// 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
|
// - 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.
|
// your storage, so "Select All" becomes "Negative=1, Clear" and then sparse unselect can add to the storage.
|
||||||
struct ExampleSelectionData
|
// About RefItem:
|
||||||
|
// - The MultiSelect API requires you to store information about the reference/pivot item (generally the last clicked item).
|
||||||
|
struct ExampleSelection
|
||||||
{
|
{
|
||||||
ImGuiStorage Storage;
|
ImGuiStorage Storage;
|
||||||
int SelectionSize; // Number of selected items (== number of 1 in the 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)
|
||||||
|
|
||||||
ExampleSelectionData() { Clear(); }
|
ExampleSelection() { RangeRef = 0; Clear(); }
|
||||||
void Clear() { Storage.Clear(); SelectionSize = 0; }
|
void Clear() { Storage.Clear(); SelectionSize = 0; }
|
||||||
bool GetSelected(int n) const { return Storage.GetInt((ImGuiID)n, 0) != 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; }
|
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; }
|
||||||
@ -1252,8 +1255,7 @@ static void ShowDemoWindowWidgets()
|
|||||||
if (ImGui::TreeNode("Selection State: Multiple Selection (Full)"))
|
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.
|
// Demonstrate holding/updating multi-selection data and using the BeginMultiSelect/EndMultiSelect API to support range-selection and clipping.
|
||||||
static int selection_ref = 0; // Selection pivot (last clicked item, we need to preserve this to handle range-select)
|
static ExampleSelection selection;
|
||||||
static ExampleSelectionData selection;
|
|
||||||
const char* random_names[] =
|
const char* random_names[] =
|
||||||
{
|
{
|
||||||
"Artichoke", "Arugula", "Asparagus", "Avocado", "Bamboo Shoots", "Bean Sprouts", "Beans", "Beet", "Belgian Endive", "Bell Pepper",
|
"Artichoke", "Arugula", "Asparagus", "Avocado", "Bamboo Shoots", "Bean Sprouts", "Beans", "Beet", "Belgian Endive", "Bell Pepper",
|
||||||
@ -1281,7 +1283,7 @@ static void ShowDemoWindowWidgets()
|
|||||||
if (widget_type == WidgetType_TreeNode)
|
if (widget_type == WidgetType_TreeNode)
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f));
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f));
|
||||||
|
|
||||||
ImGuiMultiSelectData* multi_select_data = ImGui::BeginMultiSelect(ImGuiMultiSelectFlags_None, (void*)(intptr_t)selection_ref, selection.GetSelected(selection_ref));
|
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->RequestClear) { selection.Clear(); }
|
||||||
if (multi_select_data->RequestSelectAll) { selection.SelectAll(ITEMS_COUNT); }
|
if (multi_select_data->RequestSelectAll) { selection.SelectAll(ITEMS_COUNT); }
|
||||||
|
|
||||||
@ -1295,7 +1297,7 @@ static void ShowDemoWindowWidgets()
|
|||||||
clipper.Begin(ITEMS_COUNT);
|
clipper.Begin(ITEMS_COUNT);
|
||||||
while (clipper.Step())
|
while (clipper.Step())
|
||||||
{
|
{
|
||||||
if (clipper.DisplayStart > (int)selection_ref)
|
if (clipper.DisplayStart > selection.RangeRef)
|
||||||
multi_select_data->RangeSrcPassedBy = true;
|
multi_select_data->RangeSrcPassedBy = true;
|
||||||
for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++)
|
for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++)
|
||||||
{
|
{
|
||||||
@ -1305,7 +1307,7 @@ static void ShowDemoWindowWidgets()
|
|||||||
sprintf(label, "Object %05d (category: %s)", n, category);
|
sprintf(label, "Object %05d (category: %s)", n, category);
|
||||||
bool item_is_selected = selection.GetSelected(n);
|
bool item_is_selected = selection.GetSelected(n);
|
||||||
|
|
||||||
// Emit a color button, to test that Shift+LeftArrow landing on an item that is not part
|
// 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!).
|
// of the selection scope doesn't erroneously alter our selection (FIXME-TESTS: Add a test for that!).
|
||||||
ImU32 dummy_col = (ImU32)ImGui::GetID(label);
|
ImU32 dummy_col = (ImU32)ImGui::GetID(label);
|
||||||
ImGui::ColorButton("##", ImColor(dummy_col), ImGuiColorEditFlags_NoTooltip, color_button_sz);
|
ImGui::ColorButton("##", ImColor(dummy_col), ImGuiColorEditFlags_NoTooltip, color_button_sz);
|
||||||
@ -1319,12 +1321,15 @@ static void ShowDemoWindowWidgets()
|
|||||||
}
|
}
|
||||||
else if (widget_type == WidgetType_TreeNode)
|
else if (widget_type == WidgetType_TreeNode)
|
||||||
{
|
{
|
||||||
ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_OpenOnDoubleClick;
|
ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAvailWidth;
|
||||||
|
tree_node_flags |= ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
|
||||||
if (item_is_selected)
|
if (item_is_selected)
|
||||||
tree_node_flags |= ImGuiTreeNodeFlags_Selected;
|
tree_node_flags |= ImGuiTreeNodeFlags_Selected;
|
||||||
ImGui::TreeNodeEx(label, tree_node_flags);
|
bool open = ImGui::TreeNodeEx(label, tree_node_flags);
|
||||||
if (ImGui::IsItemToggledSelection())
|
if (ImGui::IsItemToggledSelection())
|
||||||
selection.SetSelected(n, !item_is_selected);
|
selection.SetSelected(n, !item_is_selected);
|
||||||
|
if (open)
|
||||||
|
ImGui::TreePop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (use_columns)
|
if (use_columns)
|
||||||
@ -1332,7 +1337,7 @@ static void ShowDemoWindowWidgets()
|
|||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||||
ImGui::InputText("###NoLabel", (char*)category, strlen(category), ImGuiInputTextFlags_ReadOnly);
|
ImGui::InputText("###NoLabel", (char*)(void*)category, strlen(category), ImGuiInputTextFlags_ReadOnly);
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
}
|
}
|
||||||
@ -1346,7 +1351,7 @@ static void ShowDemoWindowWidgets()
|
|||||||
|
|
||||||
// Apply multi-select requests
|
// Apply multi-select requests
|
||||||
multi_select_data = ImGui::EndMultiSelect();
|
multi_select_data = ImGui::EndMultiSelect();
|
||||||
selection_ref = (int)(intptr_t)multi_select_data->RangeSrc;
|
selection.RangeRef = (int)(intptr_t)multi_select_data->RangeSrc;
|
||||||
if (multi_select_data->RequestClear) { selection.Clear(); }
|
if (multi_select_data->RequestClear) { selection.Clear(); }
|
||||||
if (multi_select_data->RequestSelectAll) { selection.SelectAll(ITEMS_COUNT); }
|
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 (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); }
|
||||||
|
@ -5932,7 +5932,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
|
|||||||
flags |= ImGuiTreeNodeFlags_OpenOnArrow;
|
flags |= ImGuiTreeNodeFlags_OpenOnArrow;
|
||||||
|
|
||||||
// To handle drag and drop of multiple items we need to avoid clearing selection on click.
|
// 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.
|
// 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?
|
// FIXME-MULTISELECT: Consider opt-in for drag and drop behavior in ImGuiMultiSelectFlags?
|
||||||
if (!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore))
|
if (!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore))
|
||||||
button_flags |= ImGuiButtonFlags_PressedOnClick;
|
button_flags |= ImGuiButtonFlags_PressedOnClick;
|
||||||
|
Loading…
Reference in New Issue
Block a user