Merge branch 'master' into docking

# Conflicts:
#	backends/imgui_impl_opengl3.cpp
This commit is contained in:
ocornut
2026-06-24 16:03:19 +02:00
10 changed files with 401 additions and 103 deletions

View File

@@ -25,8 +25,10 @@
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2026-06-23: OpenGL: GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. (#9427, #6577)
// 2026-06-17: OpenGL: Expose selected render state in ImGui_ImplOpenGL3_RenderState, Allowing to dynamically select between use of glBindSampler() and glTexParameter(). You can access in 'void* platform_io.Renderer_RenderState' during rendering.
// 2026-06-03: OpenGL: GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. (#9427, #6577)
// 2026-04-23: OpenGL: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378)
// (Breaking): this change prioritize using glBindSampler() when available, which would override glTexParameter() settings you may have set on custom textures.
// 2026-03-12: OpenGL: Fixed invalid assert in ImGui_ImplOpenGL3_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295)
// 2025-12-11: OpenGL: Fixed embedded loader multiple init/shutdown cycles broken on some platforms. (#8792, #9112)
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
@@ -256,8 +258,6 @@ struct ImGui_ImplOpenGL3_Data
bool HasBindSampler;
bool HasClipOrigin;
bool UseBufferSubData;
bool UseTexParameterToSetSampler;
GLuint NextSampler; // Used if !HasBindSampler && UseTexParameterToSetSampler.
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
GLuint TexSamplers[2]; // Used if HasBindSimpler. (0=linear, 1=nearest)
#endif
@@ -337,7 +337,7 @@ void ImGui_ImplOpenGL3_NewFrame()
IM_ASSERT(0 && "ImGui_ImplOpenGL3_CreateDeviceObjects() failed!");
}
static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object)
static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, ImGui_ImplOpenGL3_RenderState* render_state, int fb_width, int fb_height, GLuint vertex_array_object)
{
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
@@ -391,8 +391,11 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid
glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
if (bd->HasBindSampler)
glBindSampler(0, bd->TexSamplers[0]); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise.
if (render_state->UseBindSampler)
{
render_state->CurrentSampler = bd->TexSamplers[0];
glBindSampler(0, render_state->CurrentSampler); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise.
}
#endif
(void)vertex_array_object;
@@ -413,13 +416,42 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid
// Draw callbacks
static void ImGui_ImplOpenGL3_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way.
static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*)
{
ImGui_ImplOpenGL3_RenderState* render_state = ImGui_ImplOpenGL3_GetRenderState();
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); if (bd->HasBindSampler) { glBindSampler(0, bd->TexSamplers[0]); } else { bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_LINEAR; } }
static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); if (bd->HasBindSampler) { glBindSampler(0, bd->TexSamplers[1]); } else { bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_NEAREST; } }
#else
static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_LINEAR; }
static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_NEAREST; }
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
if (bd->HasBindSampler)
{
render_state->CurrentSampler = bd->TexSamplers[0];
render_state->UseTexParameterFilter = false;
glBindSampler(0, render_state->CurrentSampler);
}
else
#endif
{
render_state->UseTexParameterFilter = true;
render_state->CurrentTexParameterFilter = GL_LINEAR;
}
}
static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*)
{
ImGui_ImplOpenGL3_RenderState* render_state = ImGui_ImplOpenGL3_GetRenderState();
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
if (bd->HasBindSampler)
{
render_state->CurrentSampler = bd->TexSamplers[1];
render_state->UseTexParameterFilter = false;
glBindSampler(0, render_state->CurrentSampler);
}
else
#endif
{
render_state->UseTexParameterFilter = true;
render_state->CurrentTexParameterFilter = GL_NEAREST;
}
}
// OpenGL3 Render function.
// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly.
@@ -489,7 +521,17 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
GL_CALL(glGenVertexArrays(1, &vertex_array_object));
#endif
ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
// Setup render state structure (for callbacks and custom texture bindings)
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
ImGui_ImplOpenGL3_RenderState render_state;
render_state.UseBindSampler = bd->HasBindSampler;
render_state.UseTexParameterFilter = false;
render_state.CurrentSampler = 0;
render_state.CurrentTexParameterFilter = 0;
platform_io.Renderer_RenderState = &render_state;
ImGui_ImplOpenGL3_SetupRenderState(draw_data, &render_state, fb_width, fb_height, vertex_array_object);
// Will project scissor/clipping rectangles into framebuffer space
ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports
@@ -536,7 +578,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
{
// User callback, registered via ImDrawList::AddCallback()
if (pcmd->UserCallback == ImGui_ImplOpenGL3_DrawCallback_ResetRenderState)
ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
ImGui_ImplOpenGL3_SetupRenderState(draw_data, &render_state, fb_width, fb_height, vertex_array_object);
else
pcmd->UserCallback(draw_list, pcmd);
}
@@ -555,11 +597,11 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()));
// Emulate sampler change (even though it is technically part of texture data)
// As a sort of hack/workaround, we only start writing using glTextParameter() if sampler is ever changed explicitly.
if (!bd->HasBindSampler && bd->UseTexParameterToSetSampler)
// As a sort of hack/workaround, we only start writing using glTexParameter() if sampler is ever changed explicitly.
if (render_state.UseTexParameterFilter)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, bd->NextSampler);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, bd->NextSampler);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, render_state.CurrentTexParameterFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, render_state.CurrentTexParameterFilter);
}
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
@@ -571,6 +613,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
}
}
}
platform_io.Renderer_RenderState = nullptr;
// Destroy the temporary VAO
#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY

View File

@@ -66,4 +66,17 @@ IMGUI_IMPL_API void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex);
#endif
// [BETA] Selected render state data shared with callbacks.
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplOpenGL3_RenderDrawData() call.
// (Please open an issue if you feel you need access to more data)
struct ImGui_ImplOpenGL3_RenderState
{
bool UseBindSampler;
bool UseTexParameterFilter;
unsigned int CurrentSampler; // (GLuint) Used if UseBindSampler == true, otherwise always 0
unsigned int CurrentTexParameterFilter; // (GLuint) Used if UseTexParameterToSetSampler == true
};
static inline ImGui_ImplOpenGL3_RenderState* ImGui_ImplOpenGL3_GetRenderState() { return (ImGui_ImplOpenGL3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; }
#endif // #ifndef IMGUI_DISABLE

View File

@@ -566,8 +566,17 @@ static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline
// Draw callbacks
static void ImGui_ImplVulkan_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way.
static void ImGui_ImplVulkan_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerLinearDS, 0, nullptr); }
static void ImGui_ImplVulkan_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerNearestDS, 0, nullptr); }
static void ImGui_ImplVulkan_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->RenderState->PipelineLayout, 1, 1, &bd->SamplerLinearDS, 0, nullptr); }
static void ImGui_ImplVulkan_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->RenderState->PipelineLayout, 1, 1, &bd->SamplerNearestDS, 0, nullptr); }
// If you want to use your own sampler, you can create your own callback, access ImGui_ImplVulkan_RenderState for command-buffer and pipeline layout, e.g.
/*
void ImGui_ImplVulkan_DrawCallback_SetSamplerCustom(const ImDrawList*, const ImDrawCmd* cmd)
{
ImGui_ImplVulkan_RenderState* render_state = (ImGui_ImplVulkan_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState;
vkCmdBindDescriptorSets(render_state->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, render_state->PipelineLayout, 1, 1, (VkDescriptorSet*)cmd->UserCallbackData, 0, nullptr);
}
*/
// Render function
void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline)

View File

@@ -175,6 +175,7 @@ IMGUI_IMPL_API bool ImGui_ImplVulkan_LoadFunctions(uint32_t api_vers
// [BETA] Selected render state data shared with callbacks.
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplVulkan_RenderDrawData() call.
// ImGui_ImplVulkan_RenderState* render_state = (ImGui_ImplVulkan_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState;
// (Please open an issue if you feel you need access to more data)
struct ImGui_ImplVulkan_RenderState
{

View File

@@ -52,10 +52,26 @@ Other Changes:
for user code to bypass it without using a clickable item. (#9382)
- Clicking on a window's empty-space to move/focus a window checks
for lack of queued focus request. (#9382)
- Fixed double-click collapse toggle not owning the mouse button. If a
`SetNextWindowPos()` with pivot was queued in the same frame, the second
click could trigger another item in the same window. (#9439) [@Cleroth]
- InputText:
- Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409)
This is automatically scaled by `style.ScaleAllSizes()`.
- Tables:
- Redesigned/rewrote code to reconcile columns and settings on topology changes. (#9108)
- When a column label is passed to TableSetupColumn(), the underlying identifier
is used to match live columns data and .ini settings data when changing.
This makes it possible to add/remove columns from a table without losing
neither live data neither .ini settings data.
- PS: Note that this is distinct from toggling column visibility or reordering
columns, which was always possible. The new matching makes it easier to create
tables that are entirely customized by user or code, without losing state.
- Columns without identifiers or with duplicate identifiers are matched
sequentially, matching old behavior.
- Column ID are stored in .ini file.
- Code is being tested both for live topology changes and for loading .ini
data with mismatched topology.
- Context Menu: added a "Reset" sub-menu with a "Reset Visibility" option.
(which is greyed out when using default settings)
- Headers: fixed label being clipped early to reserve space for a sort marker
@@ -71,6 +87,9 @@ Other Changes:
calling ClearFonts() during rendering.
- Fixed an issue where passing a manually created ImFontAtlas to CreateContext() would
incorrectly destroy it in DestroyContext() when ref-count gets back to zero. (#9426)
- Nav:
- Fixed context menu activation with gamepad erroneously testing for _NavEnableKeyboard
instead of _NavEnableGamepad. (#9454, #8803, #9270) [@Clownacy]
- DrawList:
- Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091)
- Added `ImDrawListFlags_TextNoPixelSnap` to disable snapping of AddText()
@@ -86,6 +105,8 @@ Other Changes:
- OpenGL3:
- GLSL version detection assume GLSL 410 when GL context is 4.1.
Fixes an issue running on macOS with Wine. [#9427, #6577) [@perminovVS]
- Expose selected render state in ImGui_ImplOpenGL3_RenderState, allowing to
dynamically select between use of glBindSampler() and glTexParameter(). (#9378)
- Win32:
- Uses `SetProcessDpiAwarenessContext()` instead of `SetThreadDpiAwarenessContext()`
when available, fixing OpenGL DPI scaling issues as e.g. NVIDIA drivers tends
@@ -152,6 +173,8 @@ Breaking Changes:
- DirectX10, DirectX11, SDLGPU3, Vulkan: removed samplers from `ImGui_ImplXXXX_RenderState`.
Prefer to use backend-agnostic DrawCallback_SetSamplerLinear which works everywhere! (#9378)
If there is a legit need/request for them or any render state we can always add them back.
- OpenGL3: the new sampler support system prioritize using glBindSampler() when available,
which would override glTexParameter() settings you may have set on custom textures. (#9378)
Other Changes:

View File

@@ -5949,8 +5949,11 @@ void ImGui::NewFrame()
if (g.TablesLastTimeActive[i] >= 0.0f && g.TablesLastTimeActive[i] < memory_compact_start_time)
TableGcCompactTransientBuffers(g.Tables.GetByIndex(i));
for (ImGuiTableTempData& table_temp_data : g.TablesTempData)
{
table_temp_data.ReconcileColumnsRequests.clear(); // Unusual: clear every frame because this is rarely used.
if (table_temp_data.LastTimeActive >= 0.0f && table_temp_data.LastTimeActive < memory_compact_start_time)
TableGcCompactTransientBuffers(&table_temp_data);
}
if (g.GcCompactAll)
GcCompactTransientMiscBuffers();
g.GcCompactAll = false;
@@ -8160,7 +8163,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
ImRect title_bar_rect = window->TitleBarRect();
if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && g.ActiveId == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max))
if (g.IO.MouseClickedCount[0] == 2 && GetKeyOwner(ImGuiKey_MouseLeft) == ImGuiKeyOwner_NoOwner)
{
window->WantCollapseToggle = true;
SetKeyOwner(ImGuiKey_MouseLeft, window->MoveId); // Claim input the same way ButtonBehavior() does. Prevent a same-frame move from triggering other items. (#9439)
}
if (window->WantCollapseToggle)
{
window->Collapsed = !window->Collapsed;
@@ -15065,7 +15071,7 @@ static void ImGui::NavUpdateContextMenuRequest()
ImGuiContext& g = *GImGui;
g.NavOpenContextMenuItemId = g.NavOpenContextMenuWindowId = 0;
const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
const bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
const bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0;
if ((!nav_keyboard_active && !nav_gamepad_active) || g.NavWindow == NULL)
return;

View File

@@ -30,7 +30,7 @@
// Library Version
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345')
#define IMGUI_VERSION "1.92.9 WIP"
#define IMGUI_VERSION_NUM 19283
#define IMGUI_VERSION_NUM 19284
#define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000
#define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198
#define IMGUI_HAS_VIEWPORT // In 'docking' WIP branch.
@@ -657,7 +657,7 @@ namespace ImGui
// Widgets: Images
// - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
// - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above.
// - Image() pads adds style.ImageBorderSize on each side, ImageButton() adds style.FramePadding on each side.
// - Image() adds style.ImageBorderSize on each side, ImageButton() adds style.FramePadding on each side.
// - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified.
// - An obsolete version of Image(), before 1.91.9 (March 2025), had a 'tint_col' parameter which is now supported by the ImageWithBg() function.
IMGUI_API void Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1));
@@ -817,7 +817,7 @@ namespace ImGui
// - Use BeginMenuBar() on a window ImGuiWindowFlags_MenuBar to append to its menu bar.
// - Use BeginMainMenuBar() to create a menu bar at the top of the screen and append to it.
// - Use BeginMenu() to create a menu. You can call BeginMenu() multiple time with the same identifier to append more items to it.
// - Not that MenuItem() keyboardshortcuts are displayed as a convenience but _not processed_ by Dear ImGui at the moment.
// - Note that MenuItem() keyboard shortcuts are displayed as a convenience but _not processed_ by Dear ImGui at the moment.
IMGUI_API bool BeginMenuBar(); // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window).
IMGUI_API void EndMenuBar(); // only call EndMenuBar() if BeginMenuBar() returns true!
IMGUI_API bool BeginMainMenuBar(); // create and append to a full screen menu-bar.

View File

@@ -3127,7 +3127,7 @@ struct IMGUI_API ImGuiTabBar
#define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color.
#define IMGUI_TABLE_MAX_COLUMNS 512 // Arbitrary "safety" maximum, may be lifted in the future if needed. Must fit in ImGuiTableColumnIdx/ImGuiTableDrawChannelIdx.
// [Internal] sizeof() ~ 112
// [Internal] sizeof() ~ 120
// We use the terminology "Enabled" to refer to a column that is not Hidden by user/api.
// We use the terminology "Clipped" to refer to a column that is out of sight because of scrolling/clipping.
// This is in contrast with some user-facing api such as IsItemVisible() / IsRectVisible() which use "Visible" to mean "not clipped".
@@ -3168,7 +3168,11 @@ struct ImGuiTableColumn
bool IsVisibleY;
bool IsRequestOutput; // Return value for TableSetColumnIndex() / TableNextColumn(): whether we request user to output contents or not.
bool IsSkipItems; // Do we want item submissions to this column to be completely ignored (no layout will happen).
bool IsPreserveWidthAuto;
bool IsPreserveWidthAuto : 1;
bool IsJustCreated : 1;
bool IsLoadedSettings : 1;
bool IsNeedReconcileSrc : 1;
bool IsNeedReconcileDst : 1;
ImS8 NavLayerCurrent; // ImGuiNavLayer in 1 byte
ImU8 AutoFitQueue : 4; // Queue of 4 values for the next 4 frames to request auto-fit
ImU8 CannotSkipItemsQueue : 4; // Queue of 4 values for the next 4 frames to disable Clipped/SkipItem
@@ -3186,10 +3190,28 @@ struct ImGuiTableColumn
PrevEnabledColumn = NextEnabledColumn = -1;
SortOrder = -1;
SortDirection = ImGuiSortDirection_None;
IsJustCreated = true;
DrawChannelCurrent = DrawChannelFrozen = DrawChannelUnfrozen = (ImU8)-1;
}
};
// Passed to TableSetupColumn()
// sizeof() ~ 24+120 bytes
struct ImGuiTableReconcileColumnData
{
// Setup data
ImGuiID ID;
ImS16 NameOffset;
ImGuiTableColumnFlags Flags;
float InitWidthOrWeight;
ImGuiID UserData;
// Reconcile data
ImGuiTableColumnIdx ColumnNewIdx; // Index in the current table.
ImGuiTableColumnIdx ColumnOldIdx; // Index in the previous frame table.
ImGuiTableColumn ColumnOldData; // Full backup of the column. Could be avoided by storing 1 of them and applying reconcile in the right order. Not worth bothering.
};
// Transient cell data stored per row.
// sizeof() ~ 6 bytes
struct ImGuiTableCellData
@@ -3323,6 +3345,7 @@ struct IMGUI_API ImGuiTable
bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row.
bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow().
bool IsInitializing;
bool IsReconcileMode;
bool IsSortSpecsDirty;
bool IsUsingHeaders; // Set when the first row had the ImGuiTableRowFlags_Headers flag.
bool IsContextPopupOpen; // Set when default context menu is open (also see: ContextPopupColumn, InstanceInteracted).
@@ -3351,7 +3374,7 @@ struct IMGUI_API ImGuiTable
// - Accessing those requires chasing an extra pointer so for very frequently used data we leave them in the main table structure.
// - We also leave out of this structure data that tend to be particularly useful for debugging/metrics.
// FIXME-TABLE: more transient data could be stored in a stacked ImGuiTableTempData: e.g. SortSpecs.
// sizeof() ~ 136 bytes.
// sizeof() ~ 176 bytes.
struct IMGUI_API ImGuiTableTempData
{
ImGuiID WindowID; // Shortcut to g.Tables[TableIndex]->OuterWindow->ID.
@@ -3360,6 +3383,11 @@ struct IMGUI_API ImGuiTableTempData
float AngledHeadersExtraWidth; // Used in EndTable()
ImVector<ImGuiTableHeaderData> AngledHeadersRequests; // Used in TableAngledHeadersRow() // FIXME: Single instance is enough?
// Topology change
ImVector<ImGuiTableReconcileColumnData> ReconcileColumnsRequests; // Used in TableSetupColumn(), TableUpdateLayout(). Cleared every frame.
void* OldColumnsRawData; // Used in BeginTable() -> TableUpdateLayout() when resizing.
ImSpan<ImGuiTableColumn> OldColumnsData;
ImVec2 UserOuterSize; // outer_size.x passed to BeginTable()
ImDrawListSplitter DrawSplitter;
@@ -3386,6 +3414,7 @@ struct ImGuiTableColumnSettings
ImU8 SortDirection : 2;
ImS8 IsEnabled : 2; // "Visible" in .ini file
ImU8 IsStretch : 1;
bool IsLoaded : 1; // Using during loading to mark finding a matching column.
ImGuiTableColumnSettings()
{
@@ -3396,6 +3425,7 @@ struct ImGuiTableColumnSettings
SortDirection = ImGuiSortDirection_None;
IsEnabled = -1;
IsStretch = 0;
IsLoaded = false;
}
};
@@ -3435,6 +3465,7 @@ namespace ImGui
IMGUI_API void TableBeginInitMemory(ImGuiTable* table, int columns_count);
IMGUI_API void TableApplyQueuedRequests(ImGuiTable* table);
IMGUI_API void TableSetupDrawChannels(ImGuiTable* table);
IMGUI_API void TableReconcileColumns(ImGuiTable* table);
IMGUI_API void TableUpdateLayout(ImGuiTable* table);
IMGUI_API void TableUpdateBorders(ImGuiTable* table);
IMGUI_API void TableUpdateColumnsWeightFromWidth(ImGuiTable* table);
@@ -3448,6 +3479,7 @@ namespace ImGui
IMGUI_API void TableFixDisplayOrder(ImGuiTable* table);
IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table);
IMGUI_API void TableSortSpecsBuild(ImGuiTable* table);
IMGUI_API void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags init_mask);
IMGUI_API ImGuiSortDirection TableGetColumnNextSortDirection(ImGuiTableColumn* column);
IMGUI_API void TableFixColumnSortDirection(ImGuiTable* table, ImGuiTableColumn* column);
IMGUI_API float TableGetColumnWidthAuto(ImGuiTable* table, ImGuiTableColumn* column);
@@ -3471,6 +3503,7 @@ namespace ImGui
// Tables: Settings
IMGUI_API void TableLoadSettings(ImGuiTable* table);
IMGUI_API void TableLoadSettingsForColumns(ImGuiTable* table);
IMGUI_API void TableLoadSettingsForColumn(ImGuiTableColumn* column, ImGuiTableColumnSettings* column_settings, ImGuiTableFlags load_flags);
IMGUI_API void TableSaveSettings(ImGuiTable* table);
IMGUI_API void TableResetSettings(ImGuiTable* table);
IMGUI_API ImGuiTableSettings* TableGetBoundSettings(ImGuiTable* table);

View File

@@ -45,7 +45,6 @@ Index of this file:
// - TableSetupScrollFreeze() user submit scroll freeze information (optional)
//-----------------------------------------------------------------------------
// - TableUpdateLayout() [Internal] followup to BeginTable(): setup everything: widths, columns positions, clipping rectangles. Automatically called by the FIRST call to TableNextRow() or TableHeadersRow().
// | TableLoadSettingsForColumns() - on settings load
// | TableApplyQueuedRequests() - apply queued resizing/reordering/hiding requests
// | - TableSetColumnWidth() - apply resizing width (for mouse resize, often requested by previous frame)
// | - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width
@@ -364,6 +363,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
g.TablesTempData.resize(g.TablesTempDataStacked, ImGuiTableTempData());
ImGuiTableTempData* temp_data = table->TempData = &g.TablesTempData[g.TablesTempDataStacked - 1];
temp_data->TableIndex = table_idx;
temp_data->ReconcileColumnsRequests.resize(0); // FIXME: Use shrink(0) everywhere?
table->DrawSplitter = &table->TempData->DrawSplitter;
table->DrawSplitter->Clear();
@@ -375,6 +375,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
const int previous_frame_active = table->LastFrameActive;
const int instance_no = (previous_frame_active != g.FrameCount) ? 0 : table->InstanceCurrent + 1;
const ImGuiTableFlags previous_flags = table->Flags;
const bool is_new_table = (previous_frame_active == -1);
table->ID = id;
table->Flags = flags;
table->LastFrameActive = g.FrameCount;
@@ -564,30 +565,36 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
table->MemoryCompacted = false;
// Setup memory buffer (clear data if columns count changed)
ImGuiTableColumn* old_columns_to_preserve = NULL;
void* old_columns_raw_data = NULL;
const int old_columns_count = table->Columns.size();
if (old_columns_count != 0 && old_columns_count != columns_count)
{
// Attempt to preserve width and other settings on column count/specs change (#4046)
// Attempt to preserve width and other settings on column count/specs change (#4046, #9108)
IMGUI_DEBUG_LOG_TABLE("[table] Table 0x%08X column count %d -> %d, recreating storage.\n", table->ID, old_columns_count, columns_count);
old_columns_to_preserve = table->Columns.Data;
old_columns_raw_data = table->RawData; // Free at end of function
IM_ASSERT(temp_data->OldColumnsRawData == NULL);
temp_data->OldColumnsRawData = table->RawData; // Freed during layout
temp_data->OldColumnsData = table->Columns;
for (ImGuiTableColumn& src_column : table->TempData->OldColumnsData)
src_column.IsNeedReconcileSrc = true;
table->RawData = NULL;
}
if (table->RawData == NULL)
{
TableBeginInitMemory(table, columns_count);
table->IsInitializing = table->IsSettingsRequestLoad = true;
table->IsInitializing = true;
}
if (table->IsResetAllRequest)
TableResetSettings(table);
if (table->IsInitializing)
{
// Initialize
table->SettingsOffset = -1;
if (is_new_table)
{
table->SettingsOffset = -1;
table->IsSettingsRequestLoad = true;
}
table->IsSortSpecsDirty = true;
table->IsSettingsDirty = true; // Records itself into .ini file even when in default state (#7934)
table->IsReconcileMode = false;
table->InstanceInteracted = -1;
table->ContextPopupColumn = -1;
table->ReorderColumn = table->ReorderColumnDstOrder = table->ResizedColumn = table->LastResizedColumn = -1;
@@ -596,9 +603,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
for (int n = 0; n < columns_count; n++)
{
ImGuiTableColumn* column = &table->Columns[n];
if (old_columns_to_preserve && n < old_columns_count)
if (temp_data->OldColumnsData.Data && n < temp_data->OldColumnsData.size())
{
*column = old_columns_to_preserve[n];
*column = temp_data->OldColumnsData[n];
}
else
{
@@ -612,8 +619,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
table->DisplayOrderToIndex[n] = column->DisplayOrder;
}
}
if (old_columns_raw_data)
IM_FREE(old_columns_raw_data);
// Load settings
if (table->IsSettingsRequestLoad)
@@ -843,7 +848,37 @@ static void TableSetupColumnFlags(ImGuiTable* table, ImGuiTableColumn* column, I
void ImGui::TableUpdateLayout(ImGuiTable* table)
{
ImGuiContext& g = *GImGui;
ImGuiTableTempData* temp_data = table->TempData;
IM_ASSERT(table->IsLayoutLocked == false);
const int columns_count = table->ColumnsCount;
// Reconcile moved columns
if (temp_data->ReconcileColumnsRequests.Size > 0)
TableReconcileColumns(table);
if (temp_data->OldColumnsRawData)
{
IM_FREE(temp_data->OldColumnsRawData);
temp_data->OldColumnsRawData = NULL;
temp_data->OldColumnsData.clear();
}
// Apply columns settings
if (table->IsSettingsRequestLoad)
TableLoadSettingsForColumns(table);
if (table->IsInitializing || table->IsSettingsRequestLoad)
{
for (ImGuiTableColumn& column : table->Columns)
{
ImGuiTableFlags init_flags;
if (table->IsSettingsRequestLoad)
init_flags = column.IsLoadedSettings ? ~table->SettingsLoadedFlags : ~0;
else
init_flags = column.IsJustCreated ? ~0 : 0;
TableInitColumnDefaults(table, &column, init_flags);
}
TableFixDisplayOrder(table); // Call even for non _Reorderable table as we loaded .ini data.
table->IsSettingsRequestLoad = false;
}
// Apply queued resizing/reordering/hiding requests
TableApplyQueuedRequests(table);
@@ -854,7 +889,6 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
// FIXME-DPI: Provide consistent standards for reference size. Perhaps using g.CurrentDpiScale would be more self explanatory.
// This is will lead us to non-rounded WidthRequest in columns, which should work but is a poorly tested path.
const float new_ref_scale_unit = g.FontSize; // g.Font->GetCharAdvance('A') ?
const int columns_count = table->ColumnsCount;
if (table->RefScale != 0.0f && table->RefScale != new_ref_scale_unit)
{
const float scale_factor = new_ref_scale_unit / table->RefScale;
@@ -893,7 +927,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
{
TableSetupColumnFlags(table, column, ImGuiTableColumnFlags_None);
column->NameOffset = -1;
column->UserData = 0;
column->ID = column->UserData = 0;
column->InitStretchWeightOrWidth = -1.0f;
}
@@ -906,6 +940,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
table->IsSettingsDirty = true;
}
column->IsEnabled = column->IsUserEnabled && (column->Flags & ImGuiTableColumnFlags_Disabled) == 0;
column->IsJustCreated = false;
if (column->IsEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) ? 0 : 1))
table->IsDefaultVisibility = false;
@@ -1645,9 +1680,9 @@ void ImGui::EndTable()
NavUpdateCurrentWindowIsScrollPushableX();
}
// Called in TableSetupColumn() when initializing and in TableLoadSettings() for defaults before applying stored settings.
// Called in TableUpdateLayout() when initializing/after loading settings.
// 'init_mask' specify which fields to initialize.
static void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags init_mask)
void ImGui::TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags init_mask)
{
ImGuiTableColumnFlags flags = column->Flags;
if (init_mask & ImGuiTableFlags_Resizable)
@@ -1659,7 +1694,7 @@ static void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column,
column->AutoFitQueue = 0x00;
}
if (init_mask & ImGuiTableFlags_Reorderable)
column->DisplayOrder = (ImGuiTableColumnIdx)table->Columns.index_from_ptr(column);
column->DisplayOrder = (ImGuiTableColumnIdx)((table->Flags & ImGuiTableFlags_Reorderable) ? -1 : table->Columns.index_from_ptr(column));
if (init_mask & ImGuiTableFlags_Hideable)
column->IsUserEnabled = column->IsUserEnabledNextFrame = (flags & ImGuiTableColumnFlags_DefaultHide) ? 0 : 1;
if (init_mask & ImGuiTableFlags_Sortable)
@@ -1696,20 +1731,10 @@ static void TableSetupColumnApply(ImGuiTable* table, int idx, ImGuiID id, ImS16
column->ID = id;
column->UserData = user_data;
column->NameOffset = name_offset;
flags = column->Flags;
// Initialize defaults
column->InitStretchWeightOrWidth = init_width_or_weight;
if (table->IsInitializing)
{
ImGuiTableFlags init_flags = ~table->SettingsLoadedFlags;
if (column->WidthRequest < 0.0f && column->StretchWeight < 0.0f)
init_flags |= ImGuiTableFlags_Resizable;
TableInitColumnDefaults(table, column, init_flags);
}
}
void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_data_for_sort_specs)
void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_data)
{
ImGuiContext& g = *GImGui;
ImGuiTable* table = g.CurrentTable;
@@ -1726,10 +1751,104 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo
name_offset = (ImS16)table->ColumnsNames.size();
table->ColumnsNames.append(label, label + ImStrlen(label) + 1);
}
const ImGuiID column_id = (label != NULL && label[0] != 0) ? ImHashStr(label) : 0;
TableSetupColumnApply(table, table->DeclColumnsCount, column_id, name_offset, flags, init_width_or_weight, user_data_for_sort_specs);
table->DeclColumnsCount++;
// When ID changed or a column moved: defer the request until layout where we will process full reconcile.
const int column_idx = table->DeclColumnsCount++;
ImGuiTableColumn* column = &table->Columns[column_idx];
// If topology change goes into reconcile mode
if (table->IsReconcileMode == false && column->ID != column_id)
{
table->IsReconcileMode = true;
table->TempData->ReconcileColumnsRequests.reserve(table->ColumnsCount - column_idx);
}
// Fast/common path
if (table->IsReconcileMode == false)
{
TableSetupColumnApply(table, column_idx, column_id, name_offset, flags, init_width_or_weight, user_data);
column->IsNeedReconcileSrc = column->IsNeedReconcileDst = false;
if (column_idx < table->TempData->OldColumnsData.size())
table->TempData->OldColumnsData[column_idx].IsNeedReconcileSrc = false;
return;
}
// Reconcile path: defer applying data to TableUpdateLayout() -> TableReconcileMovedColumns() -> TableSetupColumnApply().
// On column count change: search the previous columns:
// - the live Columns[] array is truncated on shrink, so a moved column may only exist in old data.
// - the live Columns[] array may already be partially reset.
table->TempData->ReconcileColumnsRequests.push_back(ImGuiTableReconcileColumnData());
ImGuiTableReconcileColumnData& reconcile_data = table->TempData->ReconcileColumnsRequests.back();
reconcile_data.ID = column_id;
reconcile_data.NameOffset = name_offset;
reconcile_data.Flags = flags;
reconcile_data.InitWidthOrWeight = init_width_or_weight;
reconcile_data.UserData = user_data;
reconcile_data.ColumnNewIdx = (ImGuiTableColumnIdx)column_idx;
reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)-1;
column->NameOffset = name_offset; // Allow TableGetColumnName() to work before layout
column->IsNeedReconcileSrc = column->IsNeedReconcileDst = true;
}
// NB: This was written to be similar to the logic in TableLoadSettingsForColumns().
void ImGui::TableReconcileColumns(ImGuiTable* table)
{
ImGuiContext& g = *GImGui;
ImGuiTableTempData* temp_data = table->TempData;
IM_UNUSED(g);
IMGUI_DEBUG_LOG_TABLE("[table] Reconcile columns for table 0x%08X\n", table->ID);
ImSpan<ImGuiTableColumn>& dst_columns = table->Columns;
ImSpan<ImGuiTableColumn>& src_columns = table->TempData->OldColumnsData.empty() ? table->Columns : table->TempData->OldColumnsData;
// Find matches for named columns.
int matches = 0;
ImVector<ImGuiTableReconcileColumnData>& reconcile_requests = temp_data->ReconcileColumnsRequests;
for (ImGuiTableReconcileColumnData& reconcile_data : reconcile_requests)
if (reconcile_data.ID != 0)
for (ImGuiTableColumn& src_column : src_columns)
if (src_column.ID == reconcile_data.ID && src_column.IsNeedReconcileSrc)
{
ImGuiTableColumn& dst_column = dst_columns[reconcile_data.ColumnNewIdx];
IM_ASSERT(src_column.IsNeedReconcileSrc && dst_column.IsNeedReconcileDst);
src_column.IsNeedReconcileSrc = dst_column.IsNeedReconcileDst = false;
reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)src_columns.index_from_ptr(&src_column);
reconcile_data.ColumnOldData = src_column;
matches++;
break;
}
// Remaining entries are matched sequentially.
int dst_idx = 0; // index in reconcile array
if (matches != reconcile_requests.Size)
for (ImGuiTableColumn& src_column : src_columns)
{
if (!src_column.IsNeedReconcileSrc)
continue;
while (dst_idx < reconcile_requests.Size && reconcile_requests[dst_idx].ColumnOldIdx != (ImGuiTableColumnIdx)-1)
dst_idx++;
if (dst_idx == reconcile_requests.Size)
break;
ImGuiTableReconcileColumnData& reconcile_data = reconcile_requests[dst_idx];
ImGuiTableColumn& dst_column = dst_columns[reconcile_data.ColumnNewIdx];
IM_ASSERT(src_column.IsNeedReconcileSrc && dst_column.IsNeedReconcileDst);
dst_column.IsNeedReconcileSrc = dst_column.IsNeedReconcileDst = false;
reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)src_columns.index_from_ptr(&src_column);
reconcile_data.ColumnOldData = src_column;
}
// Apply in the final pass. Because it is possible that src_columns == table->Columns we went through a temporary copy.
for (ImGuiTableReconcileColumnData& reconcile_data : reconcile_requests)
{
table->Columns[reconcile_data.ColumnNewIdx] = reconcile_data.ColumnOldData; // When old column was not found clear anyway with default-constructed data (which will set IsJustCreated=true)
TableSetupColumnApply(table, reconcile_data.ColumnNewIdx, reconcile_data.ID, reconcile_data.NameOffset, reconcile_data.Flags, reconcile_data.InitWidthOrWeight, reconcile_data.UserData);
IMGUI_DEBUG_LOG_TABLE("[table] - old %d -> new %d \"%s\"\n", reconcile_data.ColumnOldIdx, reconcile_data.ColumnNewIdx, TableGetColumnName(table, reconcile_data.ColumnNewIdx)); // Log at the end so NameOffset was copied.
}
TableFixDisplayOrder(table);
table->IsSettingsDirty = true; // FIXME-RECONCILE: Necessary?
table->IsReconcileMode = false;
reconcile_requests.resize(0); // GC-ed once in NewFrame()
}
// [Public]
@@ -3721,6 +3840,7 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags
// - TableResetSettings()
// - TableSaveSettings() [Internal]
// - TableLoadSettings() [Internal]
// - TableLoadSettingsForColumns() [Internal]
// - TableSettingsHandler_ClearAll() [Internal]
// - TableSettingsHandler_ApplyAll() [Internal]
// - TableSettingsHandler_ReadOpen() [Internal]
@@ -3755,6 +3875,8 @@ static size_t TableSettingsCalcChunkSize(int columns_count)
ImGuiTableSettings* ImGui::TableSettingsCreate(ImGuiID id, int columns_count)
{
ImGuiContext& g = *GImGui;
//ImGuiTableSettings* old_settings = TableSettingsFindByID(id); // Comment out sanity check to avoid unnecessary lookups.
//IM_ASSERT(old_settings == NULL || old_settings->ColumnsCountMax < columns_count);
ImGuiTableSettings* settings = g.SettingsTables.alloc_chunk(TableSettingsCalcChunkSize(columns_count));
TableSettingsInit(settings, id, columns_count, columns_count);
return settings;
@@ -3774,16 +3896,12 @@ ImGuiTableSettings* ImGui::TableSettingsFindByID(ImGuiID id)
// Get settings for a given table, NULL if none
ImGuiTableSettings* ImGui::TableGetBoundSettings(ImGuiTable* table)
{
if (table->SettingsOffset != -1)
{
ImGuiContext& g = *GImGui;
ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset);
IM_ASSERT(settings->ID == table->ID);
if (settings->ColumnsCountMax >= table->ColumnsCount)
return settings; // OK
settings->ID = 0; // Invalidate storage, we won't fit because of a count change
}
return NULL;
if (table->SettingsOffset == -1)
return NULL;
ImGuiContext& g = *GImGui;
ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset);
IM_ASSERT(settings->ID == table->ID);
return settings;
}
// Restore initial state of table (with or without saved settings)
@@ -3804,6 +3922,11 @@ void ImGui::TableSaveSettings(ImGuiTable* table)
// Bind or create settings data
ImGuiContext& g = *GImGui;
ImGuiTableSettings* settings = TableGetBoundSettings(table);
if (settings != NULL && table->ColumnsCount > settings->ColumnsCountMax)
{
settings->ID = 0; // Invalidate storage, we won't fit because of a count change
settings = NULL;
}
if (settings == NULL)
{
settings = TableSettingsCreate(table->ID, table->ColumnsCount);
@@ -3854,9 +3977,11 @@ void ImGui::TableSaveSettings(ImGuiTable* table)
void ImGui::TableLoadSettings(ImGuiTable* table)
{
ImGuiContext& g = *GImGui;
table->IsSettingsRequestLoad = false;
if (table->Flags & ImGuiTableFlags_NoSavedSettings)
{
table->IsSettingsRequestLoad = false; // Done
return;
}
// Bind settings
ImGuiTableSettings* settings;
@@ -3876,44 +4001,83 @@ void ImGui::TableLoadSettings(ImGuiTable* table)
table->SettingsLoadedFlags = settings->SaveFlags;
table->RefScale = settings->RefScale;
// TableUpdateLayout() will then call TableLoadSettingsForColumns() to apply the data.
}
// Initialize default columns settings
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
{
ImGuiTableColumn* column = &table->Columns[column_n];
TableInitColumnDefaults(table, column, ~0);
column->AutoFitQueue = 0x00;
}
// NB: This was written to be similar to the logic in TableReconcileColumns().
void ImGui::TableLoadSettingsForColumns(ImGuiTable* table)
{
for (ImGuiTableColumn& column : table->Columns)
column.IsLoadedSettings = false;
ImGuiTableSettings* settings = TableGetBoundSettings(table);
if (settings == NULL)
return;
table->SettingsLoadedFlags |= ImGuiTableFlags_Reorderable; // We handle above in code above.
// Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn
ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings();
for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++)
// Fast path
int matches = 0;
for (int n = 0; n < table->ColumnsCount; n++)
{
int column_n = column_settings->Index;
if (column_n < 0 || column_n >= table->ColumnsCount)
continue;
ImGuiTableColumn* column = &table->Columns[column_n];
if (settings->SaveFlags & ImGuiTableFlags_Resizable)
{
if (column_settings->IsStretch)
column->StretchWeight = column_settings->WidthOrWeight;
else
column->WidthRequest = column_settings->WidthOrWeight;
}
if (settings->SaveFlags & ImGuiTableFlags_Reorderable)
column->DisplayOrder = column_settings->DisplayOrder;
if ((settings->SaveFlags & ImGuiTableFlags_Hideable) && column_settings->IsEnabled != -1)
column->IsUserEnabled = column->IsUserEnabledNextFrame = column_settings->IsEnabled == 1;
column->SortOrder = column_settings->SortOrder;
column->SortDirection = column_settings->SortDirection;
if (n >= settings->ColumnsCount || column_settings[n].ID != table->Columns[n].ID)
break;
TableLoadSettingsForColumn(&table->Columns[n], &column_settings[n], settings->SaveFlags);
matches++;
}
if (matches == settings->ColumnsCount)
return;
const int settings_start_n = matches; // Small optimization
for (int n = settings_start_n; n < settings->ColumnsCount; n++)
column_settings[n].IsLoaded = false;
// Fix display order and build index
if (settings->SaveFlags & ImGuiTableFlags_Reorderable)
TableFixDisplayOrder(table);
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n;
// Find matches for named columns
for (ImGuiTableColumn& column : table->Columns)
if (column.ID != 0 && !column.IsLoadedSettings)
for (int n = settings_start_n; n < settings->ColumnsCount; n++)
if (column_settings[n].ID == column.ID && !column_settings[n].IsLoaded)
{
TableLoadSettingsForColumn(&column, &column_settings[n], settings->SaveFlags);
matches++;
break;
}
// Remaining entries are matched sequentially
int dst_idx = 0;
for (int n = settings_start_n; n < settings->ColumnsCount; n++)
if (!column_settings[n].IsLoaded)
{
while (dst_idx < table->ColumnsCount && table->Columns[dst_idx].IsLoadedSettings)
dst_idx++;
if (dst_idx >= table->ColumnsCount)
break;
TableLoadSettingsForColumn(&table->Columns[dst_idx], &column_settings[n], settings->SaveFlags);
dst_idx++;
}
}
void ImGui::TableLoadSettingsForColumn(ImGuiTableColumn* column, ImGuiTableColumnSettings* column_settings, ImGuiTableFlags load_flags)
{
column->IsLoadedSettings = true;
column_settings->IsLoaded = true;
if (load_flags & ImGuiTableFlags_Resizable)
{
if (column_settings->IsStretch)
column->StretchWeight = column_settings->WidthOrWeight;
else
column->WidthRequest = column_settings->WidthOrWeight;
column->AutoFitQueue = 0x00;
}
if (load_flags & ImGuiTableFlags_Reorderable)
column->DisplayOrder = column_settings->DisplayOrder;
else
column->DisplayOrder = column_settings->Index; // Because default depends on previous Index, we need to set that up and cannot rely on TableInitColumnDefaults()
if ((load_flags & ImGuiTableFlags_Hideable) && column_settings->IsEnabled != -1)
column->IsUserEnabled = column->IsUserEnabledNextFrame = (column_settings->IsEnabled == 1);
column->SortOrder = column_settings->SortOrder;
column->SortDirection = column_settings->SortDirection;
}
struct ImGuiTableFixDisplayOrderColumnData
@@ -3928,7 +4092,8 @@ static int IMGUI_CDECL TableFixDisplayOrderComparer(const void* lhs, const void*
const ImGuiTable* table = ((const ImGuiTableFixDisplayOrderColumnData*)lhs)->Table;
const ImGuiTableColumnIdx lhs_idx = ((const ImGuiTableFixDisplayOrderColumnData*)lhs)->Idx;
const ImGuiTableColumnIdx rhs_idx = ((const ImGuiTableFixDisplayOrderColumnData*)rhs)->Idx;
const int order_delta = (table->Columns[lhs_idx].DisplayOrder - table->Columns[rhs_idx].DisplayOrder);
// Assume ImGuiTableColumnIdx == signed short, to turn -1 into a large value so that it always sort after.
const int order_delta = ((unsigned short)table->Columns[lhs_idx].DisplayOrder - (unsigned short)table->Columns[rhs_idx].DisplayOrder);
return (order_delta > 0) ? +1 : (order_delta < 0) ? -1 : (lhs_idx > rhs_idx) ? +1 : -1;
}
@@ -3946,6 +4111,8 @@ void ImGui::TableFixDisplayOrder(ImGuiTable* table)
ImQsort(fdo_columns, (size_t)table->ColumnsCount, sizeof(ImGuiTableFixDisplayOrderColumnData), TableFixDisplayOrderComparer);
for (int n = 0; n < table->ColumnsCount; n++)
table->Columns[fdo_columns[n].Idx].DisplayOrder = (ImGuiTableColumnIdx)n;
for (int n = 0; n < table->ColumnsCount; n++)
table->DisplayOrderToIndex[table->Columns[n].DisplayOrder] = (ImGuiTableColumnIdx)n;
}
static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
@@ -4111,7 +4278,7 @@ void ImGui::TableGcCompactTransientBuffers(ImGuiTableTempData* temp_data)
temp_data->LastTimeActive = -1.0f;
}
// Compact and remove unused settings data (currently only used by TestEngine)
// Compact and remove unused or resize settings data (only TestEngine mark unused, but resizing table down can lead to compaction)
void ImGui::TableGcCompactSettings()
{
ImGuiContext& g = *GImGui;
@@ -4127,6 +4294,9 @@ void ImGui::TableGcCompactSettings()
if (settings->ID != 0)
memcpy(new_chunk_stream.alloc_chunk(TableSettingsCalcChunkSize(settings->ColumnsCount)), settings, TableSettingsCalcChunkSize(settings->ColumnsCount));
g.SettingsTables.swap(new_chunk_stream);
for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++)
if (ImGuiTable* table = g.Tables.TryGetMapData(table_n))
table->SettingsOffset = -1;
}
@@ -4255,7 +4425,7 @@ void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings, ImGuiTable* tab
#else // #ifndef IMGUI_DISABLE_DEBUG_TOOLS
void ImGui::DebugNodeTable(ImGuiTable*) {}
void ImGui::DebugNodeTableSettings(ImGuiTableSettings*, ImGuiTable* table) {}
void ImGui::DebugNodeTableSettings(ImGuiTableSettings*, ImGuiTable*) {}
#endif

View File

@@ -1584,7 +1584,7 @@ bool ImGui::TextLinkOpenURL(const char* label, const char* url)
bool pressed = TextLink(label);
if (pressed && g.PlatformIO.Platform_OpenInShellFn != NULL)
g.PlatformIO.Platform_OpenInShellFn(&g, url);
SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when we same as label
SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when it is the same as the label.
if (BeginPopupContextItem())
{
if (MenuItem(LocalizeGetMsg(ImGuiLocKey_CopyLink)))