1
0
mirror of https://github.com/ocornut/imgui.git synced 2024-11-12 02:00:58 +01:00

MultiSelect: Demo: Deletion: Various renames to clarify. Use adapter and item list in both ApplyDeletion functions.

This also minify the patch for an alternative/wip attmept at redesgining pre/post deletion logic. But turns out current attempt may be easier to grasp.
This commit is contained in:
ocornut 2023-08-28 16:33:30 +02:00
parent e1d2109208
commit ba698df7bb

View File

@ -2845,61 +2845,63 @@ struct ExampleSelection
UpdateItem(adapter->IndexToStorage(adapter, idx), ms_io->RangeSelected); UpdateItem(adapter->IndexToStorage(adapter, idx), ms_io->RangeSelected);
} }
// Find which item should be focused after deletion. // Find which item should be Focused after deletion.
// We output an index in the before-deletion-items list, that user will call SetKeyboardFocusHere() on.
// The subsequent ApplyDeletionPostLoop() code will use it to apply Selection.
// - We cannot provide this logic in core Dear ImGui because we don't have access to selection data. // - We cannot provide this logic in core Dear ImGui because we don't have access to selection data.
// - Important: Deletion only works if the underlying imgui id for your items are stable: aka not depend on their index, but on e.g. item id/ptr. // - We don't actually manipulate the ImVector<> here, only in ApplyDeletionPostLoop(), but using similar API for consistency and flexibility.
int ApplyDeletionPreLoop(ImGuiMultiSelectIO* ms_io, ExampleSelectionAdapter* adapter, int items_count, void* items = NULL) // - Important: Deletion only works if the underlying ImGuiID for your items are stable: aka not depend on their index, but on e.g. item id/ptr.
// FIXME-MULTISELECT: Doesn't take account of the possibility focus target will be moved during deletion. Need refocus or offset.
template<typename ITEM_TYPE>
int ApplyDeletionPreLoop(ImGuiMultiSelectIO* ms_io, ExampleSelectionAdapter* adapter, ImVector<ITEM_TYPE>& items)
{ {
QueueDeletion = false; QueueDeletion = false;
// If current item is not selected: land on same item. // If focused item is not selected...
if (ms_io->NavIdSelected == false) // At this point 'ms_io->NavIdSelected == Contains(ms_io->NavIdItem)' should be true. const int focused_idx = adapter->UserDataToIndex(adapter, ms_io->NavIdItem); // Index of currently focused item
if (ms_io->NavIdSelected == false) // This is merely a shortcut, == Contains(adapter->IndexToStorage(items, focused_idx))
{ {
int idx = adapter->UserDataToIndex(items, ms_io->NavIdItem); ms_io->RangeSrcReset = true; // Request to recover RangeSrc from NavId next frame. Would be ok to reset even when NavIdSelected==true, but it would take an extra frame to recover RangeSrc when deleting a selected item.
ms_io->RangeSrcReset = true; // Request to recover RangeSrc from NavId next frame. Would be ok to reset even without the NavIdSelected==false test but it would take an extra frame to recover RangeSrc when deleting a selected item. return focused_idx; // Request to focus same item after deletion.
return idx; // Request to land on same item after deletion.
} }
// If current item is selected: land on first unselected item after RangeSrc. // If focused item is selected: land on first unselected item after focused item.
int src_idx = adapter->UserDataToIndex(items, ms_io->RangeSrcItem); for (int idx = focused_idx + 1; idx < items.Size; idx++)
for (int idx = src_idx + 1; idx < items_count; idx++) if (!Contains(adapter->IndexToStorage(adapter, idx)))
if (!Contains(adapter->IndexToStorage(items, idx)))
return idx; return idx;
// If current item is selected: otherwise return last unselected item. // If focused item is selected: otherwise return last unselected item before focused item.
for (int idx = IM_MIN(src_idx, items_count) - 1; idx >= 0; idx--) for (int idx = IM_MIN(focused_idx, items.Size) - 1; idx >= 0; idx--)
if (!Contains(adapter->IndexToStorage(items, idx))) if (!Contains(adapter->IndexToStorage(adapter, idx)))
return idx; return idx;
return -1; return -1;
} }
// Call after EndMultiSelect() // Rewrite item list (delete items) + update selection.
// Apply deletion request on items + apply deletion request on selection data // - Call after EndMultiSelect()
// - We cannot provide this logic in core Dear ImGui because we don't have access to your items, nor to selection data.
template<typename ITEM_TYPE> template<typename ITEM_TYPE>
void ApplyDeletionPostLoop(ImGuiMultiSelectIO* ms_io, ImVector<ITEM_TYPE>& items, int next_focus_idx_in_old_list) void ApplyDeletionPostLoop(ImGuiMultiSelectIO* ms_io, ExampleSelectionAdapter* adapter, ImVector<ITEM_TYPE>& items, int item_curr_idx_to_select)
{ {
// This does two things: // Rewrite item list (delete items) + convert old selection index (before deletion) to new selection index (after selection).
// - (1) Update Items List (delete items from it) // If NavId was not part of selection, we will stay on same item.
// - (2) Convert from old selection index (before deletion) to new selection index (after selection), and select it.
// If NavId was not selected, next_focus_idx_in_old_selection == -1 and we stay on same item.
// You are expected to handle both of those in user-space because Dear ImGui rightfully doesn't own items data nor selection data.
ImVector<ITEM_TYPE> new_items; ImVector<ITEM_TYPE> new_items;
new_items.reserve(items.Size - Size); new_items.reserve(items.Size - Size);
int next_focus_idx_in_new_list = -1; int item_next_idx_to_select = -1;
for (int n = 0; n < items.Size; n++) for (int idx = 0; idx < items.Size; idx++)
{ {
if (!Contains(n)) if (!Contains(adapter->IndexToStorage(adapter, idx)))
new_items.push_back(items[n]); new_items.push_back(items[idx]);
if (next_focus_idx_in_old_list == n) if (item_curr_idx_to_select == idx)
next_focus_idx_in_new_list = new_items.Size - 1; item_next_idx_to_select = new_items.Size - 1;
} }
items.swap(new_items); items.swap(new_items);
// Update selection // Update selection
Clear(); Clear();
if (next_focus_idx_in_new_list != -1 && ms_io->NavIdSelected) if (item_next_idx_to_select != -1 && ms_io->NavIdSelected)
AddItem(next_focus_idx_in_new_list); AddItem(adapter->IndexToStorage(adapter, item_next_idx_to_select));
} }
}; };
@ -3051,8 +3053,7 @@ static void ShowDemoWindowMultiSelect()
// But you may decide to store selection data inside your item (aka intrusive storage). // But you may decide to store selection data inside your item (aka intrusive storage).
static ImVector<int> items; static ImVector<int> items;
static ExampleSelection selection; static ExampleSelection selection;
ExampleSelectionAdapter selection_adapter; ExampleSelectionAdapter selection_adapter; // Use default: Pass index to SetNextItemSelectionUserData(), store index in Selection
selection_adapter.SetupForDirectIndexes(); // Pass index to SetNextItemSelectionUserData(), store index in Selection
ImGui::Text("Adding features:"); ImGui::Text("Adding features:");
ImGui::BulletText("Dynamic list with Delete key support."); ImGui::BulletText("Dynamic list with Delete key support.");
@ -3083,9 +3084,9 @@ static void ShowDemoWindowMultiSelect()
// FIXME-MULTISELECT: may turn into 'ms_io->RequestDelete' -> need HasSelection passed. // FIXME-MULTISELECT: may turn into 'ms_io->RequestDelete' -> need HasSelection passed.
// FIXME-MULTISELECT: If pressing Delete + another key we have ambiguous behavior. // FIXME-MULTISELECT: If pressing Delete + another key we have ambiguous behavior.
const bool want_delete = selection.QueueDeletion || ((selection.GetSize() > 0) && ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_Delete)); const bool want_delete = selection.QueueDeletion || ((selection.GetSize() > 0) && ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_Delete));
int next_focus_item_idx = -1; int item_curr_idx_to_focus = -1;
if (want_delete) if (want_delete)
next_focus_item_idx = selection.ApplyDeletionPreLoop(ms_io, &selection_adapter, items.Size); item_curr_idx_to_focus = selection.ApplyDeletionPreLoop(ms_io, &selection_adapter, items);
for (int n = 0; n < items.Size; n++) for (int n = 0; n < items.Size; n++)
{ {
@ -3096,7 +3097,7 @@ static void ShowDemoWindowMultiSelect()
bool item_is_selected = selection.Contains((ImGuiID)n); bool item_is_selected = selection.Contains((ImGuiID)n);
ImGui::SetNextItemSelectionUserData(n); ImGui::SetNextItemSelectionUserData(n);
ImGui::Selectable(label, item_is_selected); ImGui::Selectable(label, item_is_selected);
if (next_focus_item_idx == n) if (item_curr_idx_to_focus == n)
ImGui::SetKeyboardFocusHere(-1); ImGui::SetKeyboardFocusHere(-1);
} }
@ -3104,7 +3105,7 @@ static void ShowDemoWindowMultiSelect()
ms_io = ImGui::EndMultiSelect(); ms_io = ImGui::EndMultiSelect();
selection.ApplyRequests(ms_io, &selection_adapter, items.Size); selection.ApplyRequests(ms_io, &selection_adapter, items.Size);
if (want_delete) if (want_delete)
selection.ApplyDeletionPostLoop(ms_io, items, next_focus_item_idx); selection.ApplyDeletionPostLoop(ms_io, &selection_adapter, items, item_curr_idx_to_focus);
ImGui::EndListBox(); ImGui::EndListBox();
} }
@ -3204,9 +3205,9 @@ static void ShowDemoWindowMultiSelect()
// FIXME-MULTISELECT: Shortcut(). Hard to demo this? May be helpful to send a helper/optional "delete" signal. // FIXME-MULTISELECT: Shortcut(). Hard to demo this? May be helpful to send a helper/optional "delete" signal.
// FIXME-MULTISELECT: may turn into 'ms_io->RequestDelete' -> need HasSelection passed. // FIXME-MULTISELECT: may turn into 'ms_io->RequestDelete' -> need HasSelection passed.
const bool want_delete = selection.QueueDeletion || ((selection.GetSize() > 0) && ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_Delete)); const bool want_delete = selection.QueueDeletion || ((selection.GetSize() > 0) && ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_Delete));
int next_focus_item_idx = -1; int item_curr_idx_to_focus = -1;
if (want_delete) if (want_delete)
next_focus_item_idx = selection.ApplyDeletionPreLoop(ms_io, &selection_adapter, items.Size); item_curr_idx_to_focus = selection.ApplyDeletionPreLoop(ms_io, &selection_adapter, items);
if (show_in_table) if (show_in_table)
{ {
@ -3222,8 +3223,8 @@ static void ShowDemoWindowMultiSelect()
if (use_clipper) if (use_clipper)
{ {
clipper.Begin(items.Size); clipper.Begin(items.Size);
if (next_focus_item_idx != -1) if (item_curr_idx_to_focus != -1)
clipper.IncludeItemByIndex(next_focus_item_idx); // Ensure focused item is not clipped clipper.IncludeItemByIndex(item_curr_idx_to_focus); // Ensure focused item is not clipped
if (ms_io->RangeSrcItem > 0) if (ms_io->RangeSrcItem > 0)
clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped. clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped.
} }
@ -3262,7 +3263,7 @@ static void ShowDemoWindowMultiSelect()
if (widget_type == WidgetType_Selectable) if (widget_type == WidgetType_Selectable)
{ {
ImGui::Selectable(label, item_is_selected); ImGui::Selectable(label, item_is_selected);
if (next_focus_item_idx == n) if (item_curr_idx_to_focus == n)
ImGui::SetKeyboardFocusHere(-1); ImGui::SetKeyboardFocusHere(-1);
if (use_drag_drop && ImGui::BeginDragDropSource()) if (use_drag_drop && ImGui::BeginDragDropSource())
@ -3278,7 +3279,7 @@ static void ShowDemoWindowMultiSelect()
if (item_is_selected) if (item_is_selected)
tree_node_flags |= ImGuiTreeNodeFlags_Selected; tree_node_flags |= ImGuiTreeNodeFlags_Selected;
bool open = ImGui::TreeNodeEx(label, tree_node_flags); bool open = ImGui::TreeNodeEx(label, tree_node_flags);
if (next_focus_item_idx == n) if (item_curr_idx_to_focus == n)
ImGui::SetKeyboardFocusHere(-1); ImGui::SetKeyboardFocusHere(-1);
if (use_drag_drop && ImGui::BeginDragDropSource()) if (use_drag_drop && ImGui::BeginDragDropSource())
{ {
@ -3327,7 +3328,7 @@ static void ShowDemoWindowMultiSelect()
ms_io = ImGui::EndMultiSelect(); ms_io = ImGui::EndMultiSelect();
selection.ApplyRequests(ms_io, &selection_adapter, items.Size); selection.ApplyRequests(ms_io, &selection_adapter, items.Size);
if (want_delete) if (want_delete)
selection.ApplyDeletionPostLoop(ms_io, items, next_focus_item_idx); selection.ApplyDeletionPostLoop(ms_io, &selection_adapter, items, item_curr_idx_to_focus);
if (widget_type == WidgetType_TreeNode) if (widget_type == WidgetType_TreeNode)
ImGui::PopStyleVar(); ImGui::PopStyleVar();