Browse Source

MultiSelect: (Breaking) io contains a ImVector<ImGuiSelectionRequest> list.

ocornut 2 năm trước cách đây
mục cha
commit
6feff6ff05
4 tập tin đã thay đổi với 91 bổ sung55 xóa
  1. 33 20
      imgui.h
  2. 18 15
      imgui_demo.cpp
  3. 3 1
      imgui_internal.h
  4. 37 19
      imgui_widgets.cpp

+ 33 - 20
imgui.h

@@ -44,7 +44,7 @@ Index of this file:
 // [SECTION] ImGuiIO
 // [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload)
 // [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor)
-// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectIO)
+// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiSelectionRequestType, ImGuiSelectionRequest, ImGuiMultiSelectIO)
 // [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData)
 // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont)
 // [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport)
@@ -2720,7 +2720,7 @@ struct ImColor
 };
 
 //-----------------------------------------------------------------------------
-// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectIO)
+// [SECTION] Multi-Select API flags & structures (ImGuiMultiSelectFlags, ImGuiSelectionRequestType, ImGuiSelectionRequest, ImGuiMultiSelectIO)
 //-----------------------------------------------------------------------------
 
 #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.
@@ -2775,36 +2775,49 @@ enum ImGuiMultiSelectFlags_
 // - If you need to wrap this API for another language/framework, feel free to expose this as 'int' if simpler.
 // Usage flow:
 //   BEGIN - (1) Call BeginMultiSelect() and retrieve the ImGuiMultiSelectIO* result.
-//         - (2) [If using clipper] Honor Clear/SelectAll/SetRange requests by updating your selection data. Same code as Step 6.
+//         - (2) [If using clipper] Honor request list (Clear/SelectAll/SetRange requests) by updating your selection data. Same code as Step 6.
 //         - (3) [If using clipper] You need to make sure RangeSrcItem is always submitted. Calculate its index and pass to clipper.IncludeIndex(). If already using indices in ImGuiSelectionUserData, it is as simple as clipper.IncludeIndex((int)ms_io->RangeSrcItem);
 //   LOOP  - (4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls.
 //   END   - (5) Call EndMultiSelect() and retrieve the ImGuiMultiSelectIO* result.
-//         - (6) Honor Clear/SelectAll/SetRange requests by updating your selection data. Same code as Step 2.
+//         - (6) Honor request list (Clear/SelectAll/SetRange requests) by updating your selection data. Same code as Step 2.
 //   If you submit all items (no clipper), Step 2 and 3 and will be handled by Selectable()/TreeNode on a per-item basis.
 //   However it is perfectly fine to honor all steps even if you don't use a clipper.
 // Advanced:
 // - Deletion: If you need to handle items deletion a little more work if needed for post-deletion focus and scrolling to be correct.
 //   refer to 'Demo->Widgets->Selection State' for demos supporting deletion.
+
+enum ImGuiSelectionRequestType
+{
+    ImGuiSelectionRequestType_None = 0,
+    ImGuiSelectionRequestType_Clear,            // Request app to clear selection.
+    ImGuiSelectionRequestType_SelectAll,        // Request app to select all.
+    ImGuiSelectionRequestType_SetRange,         // Request app to select/unselect [RangeFirstItem..RangeLastItem] items based on 'bool RangeSelected'. Only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false.
+};
+
+// List of requests stored in ImGuiMultiSelectIO
+// - Use 'Demo->Tools->Debug Log->Selection' to see requests as they happen.
+// - Some fields are only necessary if your list is dynamic and allows deletion (handling deletion and getting "post-deletion" state right is shown in the demo)
+// - Below: who reads/writes each fields? 'r'=read, 'w'=write, 'ms'=multi-select code, 'app'=application/user code, 'BEGIN'=BeginMultiSelect() and after, 'END'=EndMultiSelect() and after.
+struct ImGuiSelectionRequest
+{
+    ImGuiSelectionRequestType   Type;           //  ms:w, app:r  /  ms:w, app:r
+    bool                        RangeSelected;  //               /  ms:w, app:r  // Parameter for SetRange request (true = select range, false = unselect range)
+    ImGuiSelectionUserData      RangeFirstItem; //               /  ms:w, app:r  // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from top to bottom)
+    ImGuiSelectionUserData      RangeLastItem;  //               /  ms:w, app:r  // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from bottom to top)
+
+    ImGuiSelectionRequest(ImGuiSelectionRequestType type = ImGuiSelectionRequestType_None) { Type = type; RangeSelected = false; RangeFirstItem = RangeLastItem = (ImGuiSelectionUserData)-1; }
+};
+
 struct ImGuiMultiSelectIO
 {
-    // - Always process requests in this order: Clear, SelectAll, SetRange. Use 'Demo->Tools->Debug Log->Selection' to see requests as they happen.
-    // - Some fields are only necessary if your list is dynamic and allows deletion (getting "post-deletion" state right is shown in the demo)
-    // - Below: who reads/writes each fields? 'r'=read, 'w'=write, 'ms'=multi-select code, 'app'=application/user code, 'BEGIN'=BeginMultiSelect() and after, 'END'=EndMultiSelect() and after.
-    // REQUESTS --------------------------------// BEGIN         / END
-    bool                    RequestClear;       //  ms:w, app:r  /  ms:w, app:r  // 1. Request app/user to clear selection.
-    bool                    RequestSelectAll;   //  ms:w, app:r  /  ms:w, app:r  // 2. Request app/user to select all.
-    bool                    RequestSetRange;    //               /  ms:w, app:r  // 3. Request app/user to select/unselect [RangeFirstItem..RangeLastItem] items based on 'bool RangeSelected'. Only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false.
-    // STATE/ARGUMENTS -------------------------// BEGIN         / END
-    ImGuiSelectionUserData  RangeSrcItem;       //  ms:w  app:r  /               // (If using clipper) Begin: Source item (generally the first selected item when multi-selecting, which is used as a reference point) must never be clipped!
-    ImGuiSelectionUserData  RangeFirstItem;     //               /  ms:w, app:r  // End: parameter for RequestSetRange request (this is generally == RangeSrcItem when shift selecting from top to bottom)
-    ImGuiSelectionUserData  RangeLastItem;      //               /  ms:w, app:r  // End: parameter for RequestSetRange request (this is generally == RangeSrcItem when shift selecting from bottom to top)
-    bool                    RangeSelected;      //               /  ms:w, app:r  // End: parameter for RequestSetRange request. true = Select Range, false = Unselect Range.
-    bool                    RangeSrcReset;      //        app:w  /  ms:r         // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection).
-    bool                    NavIdSelected;      //  ms:w, app:r  /        app:r  // (If using deletion) Last known selection state for NavId (if part of submitted items).
-    ImGuiSelectionUserData  NavIdItem;          //  ms:w, app:r  /               // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items).
+    ImVector<ImGuiSelectionRequest> Requests;   //  ms:w, app:r  /  ms:w  app:r  // Requests
+    ImGuiSelectionUserData      RangeSrcItem;   //  ms:w  app:r  /               // (If using clipper) Begin: Source item (generally the first selected item when multi-selecting, which is used as a reference point) must never be clipped!
+    ImGuiSelectionUserData      NavIdItem;      //  ms:w, app:r  /               // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items).
+    bool                        NavIdSelected;  //  ms:w, app:r  /        app:r  // (If using deletion) Last known selection state for NavId (if part of submitted items).
+    bool                        RangeSrcReset;  //        app:w  /  ms:r         // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection).
 
     ImGuiMultiSelectIO()    { Clear(); }
-    void Clear()            { memset(this, 0, sizeof(*this)); NavIdItem = RangeSrcItem = RangeFirstItem = RangeLastItem = (ImGuiSelectionUserData)-1; }
+    void Clear()            { Requests.resize(0); RangeSrcItem = NavIdItem = (ImGuiSelectionUserData)-1; NavIdSelected = RangeSrcReset = false; }
 };
 
 //-----------------------------------------------------------------------------

+ 18 - 15
imgui_demo.cpp

@@ -2838,26 +2838,29 @@ struct ExampleSelection
     // WHEN YOUR APPLICATION SETTLES ON A CHOICE, YOU WILL PROBABLY PREFER TO GET RID OF THIS UNNECESSARY 'ExampleSelectionAdapter' INDIRECTION LOGIC.
     // Notice that with the simplest adapter (using indices everywhere), all functions return their parameters.
     // The most simple implementation (using indices everywhere) would look like:
-    //   if (ms_io->RequestClear)        { Clear(); }
-    //   if (ms_io->RequestSelectAll)    { Clear(); for (int n = 0; n < items_count; n++) { AddItem(n); } }
-    //   if (ms_io->RequestSetRange)     { for (int n = (int)ms_io->RangeFirstItem; n <= (int)ms_io->RangeLastItem; n++) { UpdateItem(n, ms_io->RangeSelected); } }
+    //   for (ImGuiSelectionRequest& req : ms_io->Requests)
+    //   {
+    //      if (req.Type == ImGuiSelectionRequestType_Clear)     { Clear(); }
+    //      if (req.Type == ImGuiSelectionRequestType_SelectAll) { Clear(); for (int n = 0; n < items_count; n++) { AddItem(n); } }
+    //      if (req.Type == ImGuiSelectionRequestType_SetRange)  { for (int n = (int)ms_io->RangeFirstItem; n <= (int)ms_io->RangeLastItem; n++) { UpdateItem(n, ms_io->RangeSelected); } }
+    //   }
     void ApplyRequests(ImGuiMultiSelectIO* ms_io, ExampleSelectionAdapter* adapter, int items_count)
     {
         IM_ASSERT(adapter->IndexToStorage != NULL);
-
-        if (ms_io->RequestClear || ms_io->RequestSelectAll)
-            Clear();
-
-        if (ms_io->RequestSelectAll)
+        for (ImGuiSelectionRequest& req : ms_io->Requests)
         {
-            Storage.Data.reserve(items_count);
-            for (int idx = 0; idx < items_count; idx++)
-                AddItem(adapter->IndexToStorage(adapter, idx));
+            if (req.Type == ImGuiSelectionRequestType_Clear || req.Type == ImGuiSelectionRequestType_SelectAll)
+                Clear();
+            if (req.Type == ImGuiSelectionRequestType_SelectAll)
+            {
+                Storage.Data.reserve(items_count);
+                for (int idx = 0; idx < items_count; idx++)
+                    AddItem(adapter->IndexToStorage(adapter, idx));
+            }
+            if (req.Type == ImGuiSelectionRequestType_SetRange)
+                for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)
+                    UpdateItem(adapter->IndexToStorage(adapter, idx), req.RangeSelected);
         }
-
-        if (ms_io->RequestSetRange)
-            for (int idx = (int)ms_io->RangeFirstItem; idx <= (int)ms_io->RangeLastItem; idx++)
-                UpdateItem(adapter->IndexToStorage(adapter, idx), ms_io->RangeSelected);
     }
 
     // Find which item should be Focused after deletion.

+ 3 - 1
imgui_internal.h

@@ -1722,6 +1722,8 @@ struct IMGUI_API ImGuiMultiSelectTempData
     ImGuiKeyChord           KeyMods;
     ImGuiMultiSelectIO      BeginIO;            // Requests are set and returned by BeginMultiSelect(), written to by user during the loop.
     ImGuiMultiSelectIO      EndIO;              // Requests are set during the loop and returned by EndMultiSelect().
+    bool                    LoopRequestClear;
+    bool                    LoopRequestSelectAll;
     bool                    IsFocused;          // Set if currently focusing the selection scope (any item of the selection). May be used if you have custom shortcut associated to selection.
     bool                    IsSetRange;         // Set by BeginMultiSelect() when using Shift+Navigation. Because scrolling may be affected we can't afford a frame of lag with Shift+Navigation.
     bool                    NavIdPassedBy;
@@ -1730,7 +1732,7 @@ struct IMGUI_API ImGuiMultiSelectTempData
     //ImRect                Rect;               // Extent of selection scope between BeginMultiSelect() / EndMultiSelect(), used by ImGuiMultiSelectFlags_ClearOnClickRectVoid.
 
     ImGuiMultiSelectTempData()  { Clear(); }
-    void Clear()                { memset(this, 0, sizeof(*this)); BeginIO.Clear(); EndIO.Clear(); }
+    void Clear()            { Storage = NULL; FocusScopeId = 0; Flags = 0; KeyMods = 0; BeginIO.Clear(); EndIO.Clear(); LoopRequestClear = LoopRequestSelectAll = IsFocused = IsSetRange = NavIdPassedBy = RangeSrcPassedBy = RangeDstPassedBy = false; }
 };
 
 // Persistent storage for multi-select (as long as selection is alive)

+ 37 - 19
imgui_widgets.cpp

@@ -7114,9 +7114,12 @@ void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data)
 static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSelectIO* io)
 {
     ImGuiContext& g = *GImGui;
-    if (io->RequestClear)     IMGUI_DEBUG_LOG_SELECTION("[selection] %s: RequestClear\n", function);
-    if (io->RequestSelectAll) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: RequestSelectAll\n", function);
-    if (io->RequestSetRange)  IMGUI_DEBUG_LOG_SELECTION("[selection] %s: RequestSetRange %" IM_PRId64 "..%" IM_PRId64 " (0x%" IM_PRIX64 "..0x%" IM_PRIX64 ") = %d\n", function, io->RangeFirstItem, io->RangeLastItem, io->RangeFirstItem, io->RangeLastItem, io->RangeSelected);
+    for (const ImGuiSelectionRequest& req : io->Requests)
+    {
+        if (req.Type == ImGuiSelectionRequestType_Clear)     IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: Clear\n", function);
+        if (req.Type == ImGuiSelectionRequestType_SelectAll) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SelectAll\n", function);
+        if (req.Type == ImGuiSelectionRequestType_SetRange)  IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetRange %" IM_PRId64 "..%" IM_PRId64 " (0x%" IM_PRIX64 "..0x%" IM_PRIX64 ") = %d\n", function, req.RangeFirstItem, req.RangeLastItem, req.RangeFirstItem, req.RangeLastItem, req.RangeSelected);
+    }
 }
 
 // Return ImGuiMultiSelectIO structure. Lifetime: valid until corresponding call to EndMultiSelect().
@@ -7151,6 +7154,9 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags)
     ms->BeginIO.NavIdItem = ms->EndIO.NavIdItem = storage->NavIdItem;
     ms->BeginIO.NavIdSelected = ms->EndIO.NavIdSelected = (storage->NavIdSelected == 1) ? true : false;
 
+    bool request_clear = false;
+    bool request_select_all = false;
+
     // Clear when using Navigation to move within the scope
     // (we compare FocusScopeId so it possible to use multiple selections inside a same window)
     if (g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == ms->FocusScopeId && g.NavJustMovedToHasSelectionData)
@@ -7160,13 +7166,13 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags)
         if (ms->IsSetRange)
             IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid); // Not ready -> could clear?
         if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0)
-            ms->BeginIO.RequestClear = true;
+            request_clear = true;
     }
     else if (g.NavJustMovedFromFocusScopeId == ms->FocusScopeId)
     {
         // Also clear on leaving scope (may be optional?)
         if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0)
-            ms->BeginIO.RequestClear = true;
+            request_clear = true;
     }
 
     if (ms->IsFocused)
@@ -7176,14 +7182,19 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags)
         // Otherwise may be done by caller but it means Shortcut() needs to be exposed.
         if (flags & ImGuiMultiSelectFlags_ClearOnEscape)
             if (Shortcut(ImGuiKey_Escape))
-                ms->BeginIO.RequestClear = true;
+                request_clear = true;
 
         // Shortcut: Select all (CTRL+A)
         if (!(flags & ImGuiMultiSelectFlags_SingleSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll))
             if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_A))
-                ms->BeginIO.RequestSelectAll = true;
+                request_select_all = true;
     }
 
+    if (request_clear || request_select_all)
+        ms->BeginIO.Requests.push_back(ImGuiSelectionRequest(request_select_all ? ImGuiSelectionRequestType_SelectAll : ImGuiSelectionRequestType_Clear));
+    ms->LoopRequestClear = request_clear;
+    ms->LoopRequestSelectAll = request_select_all;
+
     if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)
         DebugLogMultiSelectRequests("BeginMultiSelect", &ms->BeginIO);
 
@@ -7220,8 +7231,8 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect()
         if (IsWindowHovered() && g.HoveredId == 0)
             if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None)
             {
-                ms->EndIO.RequestClear = true;
-                ms->EndIO.RequestSelectAll = ms->EndIO.RequestSetRange = false;
+                ms->EndIO.Requests.resize(0);
+                ms->EndIO.Requests.push_back(ImGuiSelectionRequest(ImGuiSelectionRequestType_Clear));
             }
 
     // Unwind
@@ -7273,9 +7284,9 @@ void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags
         // 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()
-        if (ms->BeginIO.RequestClear)
+        if (ms->LoopRequestClear)
             selected = false;
-        else if (ms->BeginIO.RequestSelectAll)
+        else if (ms->LoopRequestSelectAll)
             selected = 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)
@@ -7396,22 +7407,28 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
         //----------------------------------------------------------------------------------------
 
         const ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse;
+        bool request_clear = false;
         if (is_singleselect)
-            ms->EndIO.RequestClear = true;
+            request_clear = true;
         else if ((input_source == ImGuiInputSource_Mouse || g.NavActivateId == id) && !is_ctrl)
-            ms->EndIO.RequestClear = true;
+            request_clear = true;
         else if ((input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Gamepad) && is_shift && !is_ctrl)
-            ms->EndIO.RequestClear = true; // With is_shift==false the RequestClear was done in BeginIO, not necessary to do again.
+            request_clear = true; // With is_shift==false the RequestClear was done in BeginIO, not necessary to do again.
+        if (request_clear)
+        {
+            ms->EndIO.Requests.resize(0);
+            ms->EndIO.Requests.push_back(ImGuiSelectionRequest(ImGuiSelectionRequestType_Clear));
+        }
 
         int range_direction;
-        ms->EndIO.RequestSetRange = true;
+        ImGuiSelectionRequest req(ImGuiSelectionRequestType_SetRange);
         if (is_shift && !is_singleselect)
         {
             // Shift+Arrow always select
             // Ctrl+Shift+Arrow copy source selection state (alrady stored by BeginMultiSelect() in RangeSelected)
             //IM_ASSERT(storage->HasRangeSrc && storage->HasRangeValue);
             ms->EndIO.RangeSrcItem = (storage->RangeSrcItem != ImGuiSelectionUserData_Invalid) ? storage->RangeSrcItem : item_data;
-            ms->EndIO.RangeSelected = (is_ctrl && storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true;
+            req.RangeSelected = (is_ctrl && storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true;
             range_direction = ms->RangeSrcPassedBy ? +1 : -1;
         }
         else
@@ -7419,12 +7436,13 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
             // Ctrl inverts selection, otherwise always select
             selected = is_ctrl ? !selected : true;
             ms->EndIO.RangeSrcItem = storage->RangeSrcItem = item_data;
-            ms->EndIO.RangeSelected = selected;
+            req.RangeSelected = selected;
             range_direction = +1;
         }
         ImGuiSelectionUserData range_dst_item = item_data;
-        ms->EndIO.RangeFirstItem = (range_direction > 0) ? ms->EndIO.RangeSrcItem : range_dst_item;
-        ms->EndIO.RangeLastItem = (range_direction > 0) ? range_dst_item : ms->EndIO.RangeSrcItem;
+        req.RangeFirstItem = (range_direction > 0) ? ms->EndIO.RangeSrcItem : range_dst_item;
+        req.RangeLastItem = (range_direction > 0) ? range_dst_item : ms->EndIO.RangeSrcItem;
+        ms->EndIO.Requests.push_back(req);
     }
 
     // Update/store the selection state of the Source item (used by CTRL+SHIFT, when Source is unselected we perform a range unselect)