imgui-mirror/imgui_stacklayout.cpp
2026-10-17 12:23:11 +02:00

1383 lines
54 KiB
C++

// dear imgui: stack layouts
// (stack layout code)
/*
Index of this file:
// [SECTION] Commentary
// [SECTION] Header mess
// [SECTION] Stack layout: Forward declarations
// [SECTION] Stack layout: flags, enums, data structures
// [SECTION] Stack Layout: Context forward declarations
// [SECTION] Stack Layout: Internal code forward declarations
// [SECTION] Stack Layout: Context
// [SECTION] Stack Layout: Internal code
// [SECTION] Stack Layout: Internal API
// [SECTION] Stack Layout: Public API
*/
// Navigating this file:
// - In Visual Studio IDE: CTRL+comma ("Edit.NavigateTo") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot.
// - With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments.
//-----------------------------------------------------------------------------
// [SECTION] Commentary
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Typical call flow: (root level is generally public API):
//-----------------------------------------------------------------------------
//
// - BeginHorizontal/BeginVertical() user begin into a layout
// - [...] user emit contents
// - | Spring() - separate widget groups in layout
// - | SuspendLayout() - stop any layout operations
// - | ResumeLayout() - resume layout operations
//-----------------------------------------------------------------------------
// - EndHorizontal/EndVertical() user ends the layout
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// [SECTION] Header mess
//-----------------------------------------------------------------------------
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_internal.h"
// Clang/GCC warnings with -Weverything
#if defined(__clang__)
#if __has_warning("-Wunknown-warning-option")
#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great!
#endif
#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx'
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
#pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok.
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access
#elif defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe
#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may change value
#pragma GCC diagnostic ignored "-Wsign-conversion" // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result
#endif
namespace ImGuiStackLayout
{
//-----------------------------------------------------------------------------
// [SECTION] Stack layout: Forward declarations
//-----------------------------------------------------------------------------
// Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists.
typedef int ImGuiLayoutItemType; // -> enum ImGuiLayoutItemType_ // Enum: Item or Spring
//-----------------------------------------------------------------------------
// [SECTION] Stack layout: flags, enums, data structures
//-----------------------------------------------------------------------------
enum ImGuiLayoutItemType_
{
ImGuiLayoutItemType_Item,
ImGuiLayoutItemType_Spring
};
// sizeof() == 48
struct ImGuiLayoutItem
{
ImGuiLayoutItemType Type; // Type of an item
ImRect MeasuredBounds;
float SpringWeight; // Weight of a spring
float SpringSpacing; // Spring spacing
float SpringSize; // Calculated spring size
float CurrentAlign;
float CurrentAlignOffset;
int VertexIndexBegin;
int VertexIndexEnd;
ImGuiLayoutItem(ImGuiLayoutItemType type)
{
Type = type;
MeasuredBounds = ImRect(0, 0, 0, 0); // FIXME: @thedmd are you sure the default ImRect value FLT_MAX,FLT_MAX,-FLT_MAX,-FLT_MAX aren't enough here?
SpringWeight = 1.0f;
SpringSpacing = -1.0f;
SpringSize = 0.0f;
CurrentAlign = 0.0f;
CurrentAlignOffset = 0.0f;
VertexIndexBegin = VertexIndexEnd = 0;
}
};
struct ImGuiLayout
{
ImGuiID Id;
ImGuiLayoutType Type;
int LastFrameUsed;
float LastTimeUsed;
int InstanceCount; // Further uses of the ID in the current frame.
int IDStackSize; // Size of the ID stack before BeginLayout() pushed the ID.
int GroupStackSize; // Size of the group stack when BeginLayout() was called.
ImVec2 Size; // Size passed to BeginLayout
ImVec2 CurrentSize; // Bounds of layout known at the beginning the frame.
ImVec2 MinimumSize; // Minimum possible size when springs are collapsed.
ImVec2 MeasuredSize; // Measured size with springs expanded.
ImVector<ImGuiLayoutItem> Items;
int CurrentItemIndex;
int ParentItemIndex;
ImGuiLayout* Parent;
ImGuiLayout* FirstChild;
ImGuiLayout* NextSibling;
float Align; // Current item alignment.
ImVec2 StartPos; // Initial cursor position when BeginLayout is called.
ImVec2 StartCursorMaxPos; // Maximum cursor position when BeginLayout is called.
// Line state of the enclosing flow, restored by EndLayout() as EndGroup() does.
ImVec2 BackupCursorPosPrevLine;
ImVec1 BackupIndent;
ImVec2 BackupCurrLineSize;
float BackupCurrLineTextBaseOffset;
bool BackupIsSameLine;
ImGuiLayoutType BackupLayoutType;
// Last item when the current layout item began, to tell whether an item was submitted inside it.
ImGuiID ItemBeginLastItemId;
ImRect ItemBeginLastItemRect;
bool ClipRectPushed;
// Root layouts: how far corrections can move items, and the clip rect widened by it while items are submitted.
ImVec2 SpringSizeInTree; // Spring sizes of the layout and its nested layouts at the end of the previous frame, per axis.
bool WideClipRectPushed;
int WideClipRectCmdIndex;
ImVec4 WideClipRect;
ImVec4 NarrowClipRect;
ImGuiLayoutDebugFlags DebugFlags;
ImGuiLayout(ImGuiID id, ImGuiLayoutType type)
{
Id = id;
Type = type;
LastFrameUsed = -1;
LastTimeUsed = -1.0f;
InstanceCount = 0;
IDStackSize = 0;
GroupStackSize = 0;
Size = CurrentSize = MinimumSize = MeasuredSize = ImVec2(0, 0);
CurrentItemIndex = 0;
ParentItemIndex = 0;
Parent = FirstChild = NextSibling = NULL;
Align = -1.0f;
StartPos = ImVec2(0, 0);
StartCursorMaxPos = ImVec2(0, 0);
BackupCursorPosPrevLine = BackupCurrLineSize = ImVec2(0, 0);
BackupCurrLineTextBaseOffset = 0.0f;
BackupIsSameLine = false;
BackupLayoutType = ImGuiLayoutType_Vertical;
ItemBeginLastItemId = 0;
ClipRectPushed = false;
SpringSizeInTree = ImVec2(0, 0);
WideClipRectPushed = false;
WideClipRectCmdIndex = 0;
WideClipRect = NarrowClipRect = ImVec4(0, 0, 0, 0);
DebugFlags = ImGuiLayoutDebugFlags_None;
}
};
struct ImGuiLayoutSuspendBackup
{
ImGuiLayoutType LayoutType;
ImVec2 CursorMaxPos;
int IDStackSize; // Size of the ID stack before SuspendLayout() pushed the current ID again.
};
struct ImGuiLayoutWindowState
{
ImGuiWindow* Window;
ImGuiLayout* CurrentLayout;
ImVector<ImGuiLayout*> LayoutStack;
ImVector<ImGuiLayoutSuspendBackup> SuspendStack;
ImGuiStorage Layouts;
ImGuiLayoutWindowState()
{
Window = NULL;
CurrentLayout = NULL;
}
};
struct ImGuiLayoutContextState
{
ImGuiStorage WindowStates; // ImGuiLayoutWindowState* by window ID
ImGuiLayoutWindowState* LastWindowState;
ImGuiLayoutDebugFlags NextDebugFlags;
ImGuiLayoutContextState()
{
LastWindowState = NULL;
NextDebugFlags = ImGuiLayoutDebugFlags_None;
}
};
//-----------------------------------------------------------------------------
// [SECTION] Stack Layout: Context forward declarations
//-----------------------------------------------------------------------------
// The state of a context is the UserData of its Shutdown hook, found by the hook owner.
static ImGuiID GetHookOwnerID();
static ImGuiLayoutContextState* FindContextState(ImGuiContext& g);
static ImGuiLayoutContextState* GetContextState(ImGuiContext& g);
static void NewFramePreHook(ImGuiContext* ctx, ImGuiContextHook* hook);
static void ShutdownHook(ImGuiContext* ctx, ImGuiContextHook* hook);
static ImGuiLayoutWindowState* GetCurrentWindowLayoutState();
static void DestroyWindowLayoutState(ImGuiLayoutWindowState* window_state);
//-----------------------------------------------------------------------------
// [SECTION] Stack Layout: Internal code forward declarations
//-----------------------------------------------------------------------------
// Stack Layout
static ImGuiLayout* FindLayout(ImGuiID id, ImGuiLayoutType type);
static ImGuiLayout* CreateNewLayout(ImGuiID id, ImGuiLayoutType type, ImVec2 size);
static void BeginLayout(ImGuiID id, ImGuiLayoutType type, ImVec2 size, float align);
static void EndLayout(ImGuiLayoutWindowState* window_state);
static ImVec2 CalculateLayoutSize(ImGuiLayout& layout, bool collapse_springs);
static void PushLayout(ImGuiLayout* layout);
static void PopLayout(ImGuiLayout* layout);
static void BalanceLayoutSprings(ImGuiLayout& layout);
static ImVec2 BalanceLayoutItemAlignment(ImGuiLayout& layout, ImGuiLayoutItem& item);
static void BalanceLayoutItemsAlignment(ImGuiLayout& layout);
static bool HasAnyNonZeroSpring(ImGuiLayout& layout);
static void BalanceChildLayouts(ImGuiLayout& layout);
static void BeginLayoutClipRect(ImGuiLayout& layout, bool measured);
static void EndLayoutClipRect(ImGuiLayout& layout);
static void BeginLayoutWideClipRect(ImGuiLayout& layout);
static void EndLayoutWideClipRect(ImGuiLayout& layout);
static ImVec2 CalculateSpringSizeInTree(const ImGuiLayout& layout);
static ImGuiLayoutItem* GenerateLayoutItem(ImGuiLayout& layout, ImGuiLayoutItemType type);
static float CalculateLayoutItemAlignmentOffset(ImGuiLayout& layout, ImGuiLayoutItem& item);
static void TranslateLayoutItem(ImGuiLayoutItem& item, const ImVec2& offset);
static void SignedIndent(float indent);
static void MarkLayoutLastItem(ImGuiLayout& layout);
static void BeginLayoutItem(ImGuiLayout& layout);
static void EndLayoutItem(ImGuiLayout& layout);
static void AddLayoutSpring(ImGuiLayout& layout, float weight, float spacing);
static void DebugDrawLayout(const ImGuiLayout& layout);
//-----------------------------------------------------------------------------
// [SECTION] Stack Layout: Context
//-----------------------------------------------------------------------------
static ImGuiID GetHookOwnerID()
{
static const ImGuiID owner_id = ImHashStr("ImGuiStackLayout");
return owner_id;
}
static ImGuiLayoutContextState* FindContextState(ImGuiContext& g)
{
const ImGuiID owner_id = GetHookOwnerID();
for (ImGuiContextHook& hook : g.Hooks)
if (hook.Owner == owner_id && hook.Type == ImGuiContextHookType_Shutdown)
return (ImGuiLayoutContextState*)hook.UserData;
return NULL;
}
static ImGuiLayoutContextState* GetContextState(ImGuiContext& g)
{
if (ImGuiLayoutContextState* state = FindContextState(g))
return state;
ImGuiLayoutContextState* state = IM_NEW(ImGuiLayoutContextState);
ImGuiContextHook hook;
hook.Owner = GetHookOwnerID();
hook.UserData = state;
hook.Type = ImGuiContextHookType_NewFramePre;
hook.Callback = NewFramePreHook;
ImGui::AddContextHook(&g, &hook);
hook.Type = ImGuiContextHookType_Shutdown;
hook.Callback = ShutdownHook;
ImGui::AddContextHook(&g, &hook);
return state;
}
static void NewFramePreHook(ImGuiContext* ctx, ImGuiContextHook* hook)
{
ImGuiContext& g = *ctx;
ImGuiLayoutContextState* state = (ImGuiLayoutContextState*)hook->UserData;
// Same policy as windows and tables: free layouts unused for io.ConfigMemoryCompactTimer, all of a compacted window.
const bool gc_all = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f);
const float memory_compact_start_time = gc_all ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer;
ImVector<ImGuiStoragePair>& window_states = state->WindowStates.Data;
int window_states_kept = 0;
for (ImGuiStoragePair& window_pair : window_states)
{
ImGuiLayoutWindowState* window_state = (ImGuiLayoutWindowState*)window_pair.val_p;
// Error recovery in End() closes layouts left open, unless disabled.
if (window_state->LayoutStack.Size > 0)
{
IM_ASSERT_USER_ERROR(0, "Missing EndHorizontal(), EndVertical() or ResumeLayout() at the end of the frame!");
window_state->LayoutStack.resize(0);
window_state->SuspendStack.resize(0);
window_state->CurrentLayout = NULL;
}
if (window_state->Window->MemoryCompacted)
{
DestroyWindowLayoutState(window_state);
continue;
}
ImVector<ImGuiStoragePair>& layouts = window_state->Layouts.Data;
int layouts_kept = 0;
for (ImGuiStoragePair& layout_pair : layouts)
{
ImGuiLayout* layout = (ImGuiLayout*)layout_pair.val_p;
if (layout->LastFrameUsed != g.FrameCount && layout->LastTimeUsed < memory_compact_start_time)
IM_DELETE(layout);
else
layouts[layouts_kept++] = layout_pair;
}
layouts.resize(layouts_kept);
window_states[window_states_kept++] = window_pair;
}
window_states.resize(window_states_kept);
state->LastWindowState = NULL;
}
static void ShutdownHook(ImGuiContext* ctx, ImGuiContextHook* hook)
{
IM_UNUSED(ctx);
ImGuiLayoutContextState* state = (ImGuiLayoutContextState*)hook->UserData;
for (ImGuiStoragePair& window_pair : state->WindowStates.Data)
DestroyWindowLayoutState((ImGuiLayoutWindowState*)window_pair.val_p);
IM_DELETE(state);
hook->UserData = NULL;
}
static ImGuiLayoutWindowState* GetCurrentWindowLayoutState()
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiLayoutContextState* state = GetContextState(g);
if (state->LastWindowState != NULL && state->LastWindowState->Window == window)
return state->LastWindowState;
ImGuiLayoutWindowState* window_state = (ImGuiLayoutWindowState*)state->WindowStates.GetVoidPtr(window->ID);
if (window_state == NULL)
{
window_state = IM_NEW(ImGuiLayoutWindowState);
window_state->Window = window;
state->WindowStates.SetVoidPtr(window->ID, window_state);
}
state->LastWindowState = window_state;
return window_state;
}
static void DestroyWindowLayoutState(ImGuiLayoutWindowState* window_state)
{
for (ImGuiStoragePair& layout_pair : window_state->Layouts.Data)
IM_DELETE((ImGuiLayout*)layout_pair.val_p);
IM_DELETE(window_state);
}
//-----------------------------------------------------------------------------
// [SECTION] Stack Layout: Internal code
//-----------------------------------------------------------------------------
static ImGuiLayout* FindLayout(ImGuiID id, ImGuiLayoutType type)
{
IM_ASSERT(type == ImGuiLayoutType_Horizontal || type == ImGuiLayoutType_Vertical);
ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState();
ImGuiLayout* layout = (ImGuiLayout*)window_state->Layouts.GetVoidPtr(id);
if (!layout)
return NULL;
if (layout->Type != type)
{
layout->Type = type;
layout->MinimumSize = ImVec2(0.0f, 0.0f);
layout->Items.clear();
}
return layout;
}
static ImGuiLayout* CreateNewLayout(ImGuiID id, ImGuiLayoutType type, ImVec2 size)
{
IM_ASSERT(type == ImGuiLayoutType_Horizontal || type == ImGuiLayoutType_Vertical);
ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState();
ImGuiLayout* layout = IM_NEW(ImGuiLayout)(id, type);
layout->Size = size;
window_state->Layouts.SetVoidPtr(id, layout);
return layout;
}
static void BeginLayout(ImGuiID id, ImGuiLayoutType type, ImVec2 size, float align)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
if (size.x < 0.0f || size.y < 0.0f)
size = ImGui::CalcItemSize(size, 0.0f, 0.0f);
const int id_stack_size = window->IDStack.Size;
ImGui::PushOverrideID(id);
// Find or create
ImGuiLayout* layout = FindLayout(id, type);
if (!layout)
layout = CreateNewLayout(id, type, size);
// Each further use of the ID in a frame gets a layout of its own, as table instances do.
if (layout->LastFrameUsed == g.FrameCount)
{
IM_ASSERT_USER_ERROR(0, "BeginHorizontal()/BeginVertical() with an ID already used in this frame! Use PushID() to make IDs unique.");
const int instance_no = ++layout->InstanceCount;
const ImGuiID instance_id = ImHashData(&instance_no, sizeof(instance_no), id);
ImGuiLayout* instance = FindLayout(instance_id, type);
layout = instance ? instance : CreateNewLayout(instance_id, type, size);
}
else
{
layout->InstanceCount = 0;
}
const bool measured = (layout->LastFrameUsed == g.FrameCount - 1);
layout->LastFrameUsed = g.FrameCount;
layout->LastTimeUsed = (float)g.Time;
layout->IDStackSize = id_stack_size;
layout->GroupStackSize = g.GroupStack.Size;
PushLayout(layout);
ImGuiLayoutContextState* context_state = GetContextState(g);
layout->DebugFlags = context_state->NextDebugFlags | (layout->Parent ? layout->Parent->DebugFlags : ImGuiLayoutDebugFlags_None);
context_state->NextDebugFlags = ImGuiLayoutDebugFlags_None;
if (layout->Size.x != size.x || layout->Size.y != size.y)
layout->Size = size;
if (align < 0.0f)
layout->Align = -1.0f;
else
layout->Align = ImClamp(align, 0.0f, 1.0f);
// Start capture
layout->CurrentItemIndex = 0;
layout->CurrentSize.x = layout->Size.x > 0.0f ? layout->Size.x : layout->MinimumSize.x;
layout->CurrentSize.y = layout->Size.y > 0.0f ? layout->Size.y : layout->MinimumSize.y;
// The layout is a block starting at the cursor, like a group: save the line it is on.
layout->StartPos = window->DC.CursorPos;
layout->StartCursorMaxPos = window->DC.CursorMaxPos;
layout->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;
layout->BackupIndent = window->DC.Indent;
layout->BackupCurrLineSize = window->DC.CurrLineSize;
layout->BackupCurrLineTextBaseOffset = window->DC.CurrLineTextBaseOffset;
layout->BackupIsSameLine = window->DC.IsSameLine;
layout->BackupLayoutType = window->DC.LayoutType;
// ItemSize() flows items along the layout axis. New lines start at the layout's left edge.
window->DC.LayoutType = type;
window->DC.Indent.x = layout->StartPos.x - window->Pos.x - window->DC.ColumnsOffset.x;
window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
if (layout->Parent == NULL)
BeginLayoutWideClipRect(*layout);
BeginLayoutClipRect(*layout, measured);
BeginLayoutItem(*layout);
}
static void EndLayout(ImGuiLayoutWindowState* window_state)
{
ImGuiLayout* layout = window_state->CurrentLayout;
ImGuiWindow* window = window_state->Window;
EndLayoutItem(*layout);
if (layout->CurrentItemIndex < layout->Items.Size)
layout->Items.resize(layout->CurrentItemIndex);
PopLayout(layout);
const bool auto_width = layout->Size.x <= 0.0f;
const bool auto_height = layout->Size.y <= 0.0f;
ImVec2 new_size = layout->Size;
if (auto_width)
new_size.x = layout->CurrentSize.x;
if (auto_height)
new_size.y = layout->CurrentSize.y;
ImVec2 new_minimum_size = CalculateLayoutSize(*layout, true);
if (new_minimum_size.x != layout->MinimumSize.x || new_minimum_size.y != layout->MinimumSize.y)
{
layout->MinimumSize = new_minimum_size;
// Shrink
if (auto_width)
new_size.x = new_minimum_size.x;
if (auto_height)
new_size.y = new_minimum_size.y;
}
if (!auto_width)
new_size.x = layout->Size.x;
if (!auto_height)
new_size.y = layout->Size.y;
layout->CurrentSize = new_size;
ImVec2 measured_size = new_size;
// Inflate to parent size only when there are springs that need the free space.
if ((auto_width || auto_height) && layout->Parent && HasAnyNonZeroSpring(*layout))
{
if (layout->Type == ImGuiLayoutType_Horizontal && auto_width && layout->Parent->CurrentSize.x > 0)
layout->CurrentSize.x = layout->Parent->CurrentSize.x;
else if (layout->Type == ImGuiLayoutType_Vertical && auto_height && layout->Parent->CurrentSize.y > 0)
layout->CurrentSize.y = layout->Parent->CurrentSize.y;
BalanceLayoutSprings(*layout);
measured_size = layout->CurrentSize;
}
layout->CurrentSize = new_size;
layout->MeasuredSize = measured_size;
IM_ASSERT_USER_ERROR(window->IDStack.Size == layout->IDStackSize + 1, "Mismatching PushID()/PopID() inside a layout!");
while (window->IDStack.Size > layout->IDStackSize)
ImGui::PopID();
EndLayoutClipRect(*layout);
EndLayoutWideClipRect(*layout);
// Restore the enclosing line, then submit the layout as one item, as EndGroup() does.
window->DC.CursorPos = layout->StartPos;
window->DC.CursorPosPrevLine = layout->BackupCursorPosPrevLine;
window->DC.CursorMaxPos = layout->StartCursorMaxPos;
window->DC.Indent = layout->BackupIndent;
window->DC.CurrLineSize = layout->BackupCurrLineSize;
window->DC.CurrLineTextBaseOffset = layout->BackupCurrLineTextBaseOffset;
window->DC.IsSameLine = layout->BackupIsSameLine;
window->DC.LayoutType = layout->BackupLayoutType;
ImGui::ItemSize(new_size);
// The item rect includes the inflation to the parent, which GetItemRect*() users draw with, except inside a group
// opened in the parent: EndGroup() would include it, feeding the parent's size back into itself.
const bool in_group_of_parent = layout->Parent != NULL && layout->GroupStackSize > layout->Parent->GroupStackSize;
ImGui::ItemAdd(ImRect(layout->StartPos, layout->StartPos + (in_group_of_parent ? new_size : measured_size)), 0);
// The parent measures this layout by the size given to ItemSize(), without the inflation in the item rect.
if (window_state->CurrentLayout)
MarkLayoutLastItem(*window_state->CurrentLayout);
if (layout->Parent == NULL)
{
BalanceChildLayouts(*layout);
layout->SpringSizeInTree = CalculateSpringSizeInTree(*layout);
}
if (layout->DebugFlags != ImGuiLayoutDebugFlags_None)
DebugDrawLayout(*layout);
//window->DrawList->AddRect(layout->StartPos, layout->StartPos + measured_size, IM_COL32(0,255,0,255)); // [DEBUG]
//window->DrawList->AddRect(window->DC.LastItemRect.Min, window->DC.LastItemRect.Max, IM_COL32(255,255,0,255)); // [DEBUG]
}
static ImVec2 CalculateLayoutSize(ImGuiLayout& layout, bool collapse_springs)
{
ImVec2 bounds = ImVec2(0.0f, 0.0f);
if (layout.Type == ImGuiLayoutType_Vertical)
{
for (int i = 0; i < layout.Items.Size; i++)
{
ImGuiLayoutItem& item = layout.Items[i];
ImVec2 item_size = item.MeasuredBounds.GetSize();
if (item.Type == ImGuiLayoutItemType_Item)
{
bounds.x = ImMax(bounds.x, item_size.x);
bounds.y += item_size.y;
}
else
{
bounds.y += ImFloor(item.SpringSpacing);
if (!collapse_springs)
bounds.y += item.SpringSize;
}
}
}
else
{
for (int i = 0; i < layout.Items.Size; i++)
{
ImGuiLayoutItem& item = layout.Items[i];
ImVec2 item_size = item.MeasuredBounds.GetSize();
if (item.Type == ImGuiLayoutItemType_Item)
{
bounds.x += item_size.x;
bounds.y = ImMax(bounds.y, item_size.y);
}
else
{
bounds.x += ImFloor(item.SpringSpacing);
if (!collapse_springs)
bounds.x += item.SpringSize;
}
}
}
return bounds;
}
static void PushLayout(ImGuiLayout* layout)
{
ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState();
if (layout)
{
layout->Parent = window_state->CurrentLayout;
if (layout->Parent != NULL)
layout->ParentItemIndex = layout->Parent->CurrentItemIndex;
if (window_state->CurrentLayout)
{
layout->NextSibling = window_state->CurrentLayout->FirstChild;
layout->FirstChild = NULL;
window_state->CurrentLayout->FirstChild = layout;
}
else
{
layout->NextSibling = NULL;
layout->FirstChild = NULL;
}
}
window_state->LayoutStack.push_back(layout);
window_state->CurrentLayout = layout;
}
static void PopLayout(ImGuiLayout* layout)
{
ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState();
IM_ASSERT(!window_state->LayoutStack.empty());
IM_ASSERT(window_state->LayoutStack.back() == layout);
IM_UNUSED(layout);
window_state->LayoutStack.pop_back();
window_state->CurrentLayout = window_state->LayoutStack.empty() ? NULL : window_state->LayoutStack.back();
}
static void BalanceLayoutSprings(ImGuiLayout& layout)
{
// Accumulate amount of occupied space and springs weights
float total_spring_weight = 0.0f;
int last_spring_item_index = -1;
for (int i = 0; i < layout.Items.Size; i++)
{
ImGuiLayoutItem& item = layout.Items[i];
if (item.Type == ImGuiLayoutItemType_Spring)
{
total_spring_weight += item.SpringWeight;
last_spring_item_index = i;
}
}
// Determine occupied space and available space depending on layout type
const bool is_horizontal = (layout.Type == ImGuiLayoutType_Horizontal);
const bool is_auto_sized = ((is_horizontal ? layout.Size.x : layout.Size.y) <= 0.0f) && (layout.Parent == NULL);
const float occupied_space = is_horizontal ? layout.MinimumSize.x : layout.MinimumSize.y;
const float available_space = is_auto_sized ? occupied_space : (is_horizontal ? layout.CurrentSize.x : layout.CurrentSize.y);
const float free_space = ImMax(available_space - occupied_space, 0.0f);
// Springs that change size move the items after them, to avoid one frame visual bugs. Offsets accumulate, so each
// item is translated once.
ImVec2 offset = ImVec2(0.0f, 0.0f);
float span_start = 0.0f;
float current_weight = 0.0f;
for (int i = 0; i < layout.Items.Size; i++)
{
ImGuiLayoutItem& item = layout.Items[i];
if (item.Type != ImGuiLayoutItemType_Spring)
{
if (offset.x != 0.0f || offset.y != 0.0f)
{
TranslateLayoutItem(item, offset);
item.MeasuredBounds.Min += offset;
item.MeasuredBounds.Max += offset;
}
continue;
}
float last_spring_size = item.SpringSize;
if (free_space > 0.0f && total_spring_weight > 0.0f)
{
float next_weight = current_weight + item.SpringWeight;
float span_end = ImFloor((i == last_spring_item_index) ? free_space : (free_space * next_weight / total_spring_weight));
float spring_size = span_end - span_start;
item.SpringSize = spring_size;
span_start = span_end;
current_weight = next_weight;
}
else
{
item.SpringSize = 0.0f;
}
item.MeasuredBounds.Min += offset;
item.MeasuredBounds.Max += offset;
if (last_spring_size != item.SpringSize)
{
float difference = item.SpringSize - last_spring_size;
ImVec2 spring_offset = is_horizontal ? ImVec2(difference, 0.0f) : ImVec2(0.0f, difference);
item.MeasuredBounds.Max += spring_offset;
offset += spring_offset;
}
}
}
static ImVec2 BalanceLayoutItemAlignment(ImGuiLayout& layout, ImGuiLayoutItem& item)
{
// Fixup item alignment if necessary.
ImVec2 position_correction = ImVec2(0.0f, 0.0f);
if (item.CurrentAlign > 0.0f)
{
float item_align_offset = CalculateLayoutItemAlignmentOffset(layout, item);
if (item.CurrentAlignOffset != item_align_offset)
{
float offset = item_align_offset - item.CurrentAlignOffset;
if (layout.Type == ImGuiLayoutType_Horizontal)
position_correction.y = offset;
else
position_correction.x = offset;
TranslateLayoutItem(item, position_correction);
item.CurrentAlignOffset = item_align_offset;
}
}
return position_correction;
}
static void BalanceLayoutItemsAlignment(ImGuiLayout& layout)
{
for (int i = 0; i < layout.Items.Size; ++i)
{
ImGuiLayoutItem& item = layout.Items[i];
BalanceLayoutItemAlignment(layout, item);
}
}
static bool HasAnyNonZeroSpring(ImGuiLayout& layout)
{
for (int i = 0; i < layout.Items.Size; ++i)
{
ImGuiLayoutItem& item = layout.Items[i];
if (item.Type != ImGuiLayoutItemType_Spring)
continue;
if (item.SpringWeight > 0)
return true;
}
return false;
}
static void BalanceChildLayouts(ImGuiLayout& layout)
{
for (ImGuiLayout* child = layout.FirstChild; child != NULL; child = child->NextSibling)
{
//ImVec2 child_layout_size = child->CurrentSize;
// Propagate layout size down to child layouts.
//
// TODO: Distribution assume inner layout is only
// element inside parent item and assigns
// all available space to it.
//
// Investigate how to split space between
// adjacent layouts.
//
// Investigate how to measure non-layout items
// to treat them as fixed size blocks.
//
if (child->Type == ImGuiLayoutType_Horizontal && child->Size.x <= 0.0f)
child->CurrentSize.x = layout.CurrentSize.x;
else if (child->Type == ImGuiLayoutType_Vertical && child->Size.y <= 0.0f)
child->CurrentSize.y = layout.CurrentSize.y;
BalanceChildLayouts(*child);
//child->CurrentSize = child_layout_size;
if (HasAnyNonZeroSpring(*child))
{
// Expand item measured bounds to make alignment correct.
ImGuiLayoutItem& item = layout.Items[child->ParentItemIndex];
if (child->Type == ImGuiLayoutType_Horizontal && child->Size.x <= 0.0f)
item.MeasuredBounds.Max.x = ImMax(item.MeasuredBounds.Max.x, item.MeasuredBounds.Min.x + layout.CurrentSize.x);
else if (child->Type == ImGuiLayoutType_Vertical && child->Size.y <= 0.0f)
item.MeasuredBounds.Max.y = ImMax(item.MeasuredBounds.Max.y, item.MeasuredBounds.Min.y + layout.CurrentSize.y);
}
}
BalanceLayoutSprings(layout);
BalanceLayoutItemsAlignment(layout);
}
// Only fixed-size axes clip, and only when the contents measured in the previous frame overflow them.
static void BeginLayoutClipRect(ImGuiLayout& layout, bool measured)
{
const bool clip_x = layout.Size.x > 0.0f && (!measured || layout.MinimumSize.x > layout.Size.x);
const bool clip_y = layout.Size.y > 0.0f && (!measured || layout.MinimumSize.y > layout.Size.y);
layout.ClipRectPushed = clip_x || clip_y;
if (!layout.ClipRectPushed)
return;
ImVec2 clip_rect_min(clip_x ? layout.StartPos.x : -FLT_MAX, clip_y ? layout.StartPos.y : -FLT_MAX);
ImVec2 clip_rect_max(clip_x ? layout.StartPos.x + layout.Size.x : FLT_MAX, clip_y ? layout.StartPos.y + layout.Size.y : FLT_MAX);
ImGui::PushClipRect(clip_rect_min, clip_rect_max, true);
}
static void EndLayoutClipRect(ImGuiLayout& layout)
{
if (layout.ClipRectPushed)
ImGui::PopClipRect();
}
// Until the root layout corrects them, items can land too far by up to the spring sizes of the tree in the previous
// frame. The clip rect is widened by that much toward the far side meanwhile, so that ItemAdd() and text rendering do
// not cull them. Not toward the near side: hover tests use it too, and items scrolled under the title bar would take
// its clicks.
static void BeginLayoutWideClipRect(ImGuiLayout& layout)
{
layout.WideClipRectPushed = layout.SpringSizeInTree.x > 0.0f || layout.SpringSizeInTree.y > 0.0f;
if (!layout.WideClipRectPushed)
return;
ImDrawList* draw_list = ImGui::GetWindowDrawList();
const ImVec2 margin = layout.SpringSizeInTree;
layout.NarrowClipRect = draw_list->_CmdHeader.ClipRect;
ImGui::PushClipRect(ImVec2(layout.NarrowClipRect.x, layout.NarrowClipRect.y), ImVec2(layout.NarrowClipRect.z + margin.x, layout.NarrowClipRect.w + margin.y), false);
layout.WideClipRect = draw_list->_CmdHeader.ClipRect;
layout.WideClipRectCmdIndex = draw_list->CmdBuffer.Size - 1;
}
// Commands drawn under the wide clip rect get the original one back, then merge where it made them split.
static void EndLayoutWideClipRect(ImGuiLayout& layout)
{
if (!layout.WideClipRectPushed)
return;
ImGui::PopClipRect();
ImDrawList* draw_list = ImGui::GetWindowDrawList();
const ImVec4 wide = layout.WideClipRect;
const ImVec4 narrow = layout.NarrowClipRect;
for (int n = layout.WideClipRectCmdIndex; n < draw_list->CmdBuffer.Size; n++)
{
// Clip rects pushed without intersecting (e.g. full screen ones) are not inside the wide one: they stay.
ImVec4& clip_rect = draw_list->CmdBuffer[n].ClipRect;
if (clip_rect.x < wide.x || clip_rect.y < wide.y || clip_rect.z > wide.z || clip_rect.w > wide.w)
continue;
clip_rect.x = ImClamp(clip_rect.x, narrow.x, narrow.z);
clip_rect.y = ImClamp(clip_rect.y, narrow.y, narrow.w);
clip_rect.z = ImClamp(clip_rect.z, clip_rect.x, narrow.z);
clip_rect.w = ImClamp(clip_rect.w, clip_rect.y, narrow.w);
}
// Same header comparison as ImDrawList::_TryMergeDrawCmds(): clip rect, texture and vertex offset.
const size_t header_size = offsetof(ImDrawCmd, VtxOffset) + sizeof(unsigned int);
int dst = ImMax(layout.WideClipRectCmdIndex - 1, 0);
for (int src = dst + 1; src < draw_list->CmdBuffer.Size; src++)
{
ImDrawCmd& prev_cmd = draw_list->CmdBuffer[dst];
ImDrawCmd& cmd = draw_list->CmdBuffer[src];
if (memcmp(&prev_cmd, &cmd, header_size) == 0 && prev_cmd.IdxOffset + prev_cmd.ElemCount == cmd.IdxOffset && prev_cmd.UserCallback == NULL && cmd.UserCallback == NULL)
prev_cmd.ElemCount += cmd.ElemCount;
else
draw_list->CmdBuffer[++dst] = cmd;
}
draw_list->CmdBuffer.resize(dst + 1);
}
static ImVec2 CalculateSpringSizeInTree(const ImGuiLayout& layout)
{
ImVec2 size(0.0f, 0.0f);
const int axis = (layout.Type == ImGuiLayoutType_Horizontal) ? 0 : 1;
for (const ImGuiLayoutItem& item : layout.Items)
if (item.Type == ImGuiLayoutItemType_Spring)
size[axis] += item.SpringSize;
for (const ImGuiLayout* child = layout.FirstChild; child != NULL; child = child->NextSibling)
size += CalculateSpringSizeInTree(*child);
return size;
}
static ImGuiLayoutItem* GenerateLayoutItem(ImGuiLayout& layout, ImGuiLayoutItemType type)
{
IM_ASSERT(layout.CurrentItemIndex <= layout.Items.Size);
if (layout.CurrentItemIndex < layout.Items.Size)
{
ImGuiLayoutItem& item = layout.Items[layout.CurrentItemIndex];
if (item.Type != type)
item = ImGuiLayoutItem(type);
}
else
{
layout.Items.push_back(ImGuiLayoutItem(type));
}
return &layout.Items[layout.CurrentItemIndex];
}
// Calculate how many pixels from top/left layout edge item need to be moved to match
// layout alignment.
static float CalculateLayoutItemAlignmentOffset(ImGuiLayout& layout, ImGuiLayoutItem& item)
{
if (item.CurrentAlign <= 0.0f)
return 0.0f;
ImVec2 item_size = item.MeasuredBounds.GetSize();
float layout_extent = (layout.Type == ImGuiLayoutType_Horizontal) ? layout.CurrentSize.y : layout.CurrentSize.x;
float item_extent = (layout.Type == ImGuiLayoutType_Horizontal) ? item_size.y : item_size.x;
if (item_extent <= 0/* || layout_extent <= item_extent*/)
return 0.0f;
float align_offset = ImFloor(item.CurrentAlign * (layout_extent - item_extent));
return align_offset;
}
static void TranslateLayoutItem(ImGuiLayoutItem& item, const ImVec2& offset)
{
if ((offset.x == 0.0f && offset.y == 0.0f) || (item.VertexIndexBegin == item.VertexIndexEnd))
return;
//IMGUI_DEBUG_LOG("TranslateLayoutItem by %f,%f\n", offset.x, offset.y);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImDrawVert* begin = draw_list->VtxBuffer.Data + item.VertexIndexBegin;
ImDrawVert* end = draw_list->VtxBuffer.Data + item.VertexIndexEnd;
for (ImDrawVert* vtx = begin; vtx < end; ++vtx)
{
vtx->pos.x += offset.x;
vtx->pos.y += offset.y;
}
}
static void SignedIndent(float indent)
{
if (indent > 0.0f)
ImGui::Indent(indent);
else if (indent < 0.0f)
ImGui::Unindent(-indent);
}
// EndLayoutItem() includes the last item rect only if an item was submitted after this.
static void MarkLayoutLastItem(ImGuiLayout& layout)
{
ImGuiContext& g = *GImGui;
layout.ItemBeginLastItemId = g.LastItemData.ID;
layout.ItemBeginLastItemRect = g.LastItemData.Rect;
}
static void BeginLayoutItem(ImGuiLayout& layout)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiLayoutItem& item = *GenerateLayoutItem(layout, ImGuiLayoutItemType_Item);
item.CurrentAlign = layout.Align;
if (item.CurrentAlign < 0.0f)
item.CurrentAlign = ImClamp(g.Style.LayoutAlign, 0.0f, 1.0f);
// Align item according to data from previous frame.
// If layout changes in current frame alignment will
// be corrected in EndLayout() to it visualy coherent.
item.CurrentAlignOffset = CalculateLayoutItemAlignmentOffset(layout, item);
if (layout.Type == ImGuiLayoutType_Horizontal)
{
window->DC.CursorPos.y = layout.StartPos.y + item.CurrentAlignOffset;
}
else if (item.CurrentAlign > 0.0f)
{
float new_position = window->DC.CursorPos.x + item.CurrentAlignOffset;
// Make placement behave like in horizontal case when next
// widget is placed at very same Y position. This indent
// make sure for vertical layout placed widgets has same X position.
SignedIndent(item.CurrentAlignOffset);
window->DC.CursorPos.x = new_position;
}
// Each item starts its own line and is measured like a group, from what its contents extend CursorMaxPos to.
window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
window->DC.IsSameLine = false;
window->DC.CursorMaxPos = window->DC.CursorPos;
MarkLayoutLastItem(layout);
item.MeasuredBounds.Min = item.MeasuredBounds.Max = window->DC.CursorPos;
item.VertexIndexBegin = item.VertexIndexEnd = window->DrawList->VtxBuffer.Size;
}
static void EndLayoutItem(ImGuiLayout& layout)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
IM_ASSERT(layout.CurrentItemIndex < layout.Items.Size);
ImGuiLayoutItem& item = layout.Items[layout.CurrentItemIndex];
// Like EndGroup(), include the last item submitted inside, for e.g. EndTable() undershooting with CursorMaxPos.
item.MeasuredBounds.Max = ImMax(item.MeasuredBounds.Max, window->DC.CursorMaxPos);
if (g.LastItemData.ID != layout.ItemBeginLastItemId || memcmp(&g.LastItemData.Rect, &layout.ItemBeginLastItemRect, sizeof(ImRect)) != 0)
item.MeasuredBounds.Max = ImMax(item.MeasuredBounds.Max, g.LastItemData.Rect.Max);
ImDrawList* draw_list = window->DrawList;
item.VertexIndexEnd = draw_list->VtxBuffer.Size;
if (item.CurrentAlign > 0.0f && layout.Type == ImGuiLayoutType_Vertical)
SignedIndent(-item.CurrentAlignOffset);
// Fixup item alignment in case item size changed in current frame.
ImVec2 position_correction = BalanceLayoutItemAlignment(layout, item);
item.MeasuredBounds.Min += position_correction;
item.MeasuredBounds.Max += position_correction;
if (layout.Type == ImGuiLayoutType_Horizontal)
window->DC.CursorPos.y = layout.StartPos.y;
else
window->DC.CursorPos.x = layout.StartPos.x;
layout.CurrentItemIndex++;
}
static void AddLayoutSpring(ImGuiLayout& layout, float weight, float spacing)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
EndLayoutItem(layout);
// Undo item padding, spring should consume all space between items.
ImGuiLayoutItem* previous_item = &layout.Items[layout.CurrentItemIndex - 1];
if (layout.Type == ImGuiLayoutType_Horizontal)
window->DC.CursorPos.x = previous_item->MeasuredBounds.Max.x;
else
window->DC.CursorPos.y = previous_item->MeasuredBounds.Max.y;
window->DC.IsSameLine = false;
previous_item = NULL; // may be invalid after call to GenerateLayoutItem()
ImGuiLayoutItem* spring_item = GenerateLayoutItem(layout, ImGuiLayoutItemType_Spring);
spring_item->MeasuredBounds.Min = spring_item->MeasuredBounds.Max = window->DC.CursorPos;
if (weight < 0.0f)
weight = 0.0f;
if (spring_item->SpringWeight != weight)
spring_item->SpringWeight = weight;
if (spacing < 0.0f)
{
ImVec2 style_spacing = g.Style.ItemSpacing;
if (layout.Type == ImGuiLayoutType_Horizontal)
spacing = style_spacing.x;
else
spacing = style_spacing.y;
}
if (spring_item->SpringSpacing != spacing)
spring_item->SpringSpacing = spacing;
// Submitted even when empty, so that GetItemRect*() and IsItem*() after Spring() refer to the spring.
ImVec2 spring_size, spring_spacing;
if (layout.Type == ImGuiLayoutType_Horizontal)
{
spring_spacing = ImVec2(0.0f, g.Style.ItemSpacing.y);
spring_size = ImVec2(spacing + spring_item->SpringSize, layout.CurrentSize.y);
}
else
{
spring_spacing = ImVec2(g.Style.ItemSpacing.x, 0.0f);
spring_size = ImVec2(layout.CurrentSize.x, spacing + spring_item->SpringSize);
}
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImFloor(spring_spacing));
ImGui::Dummy(ImFloor(spring_size));
ImGui::PopStyleVar();
spring_item->MeasuredBounds.Max = spring_item->MeasuredBounds.Min + ImFloor(spring_size);
layout.CurrentItemIndex++;
BeginLayoutItem(layout);
}
static void DebugDrawLayout(const ImGuiLayout& layout)
{
ImDrawList* draw_list = ImGui::GetWindowDrawList();
const int axis = (layout.Type == ImGuiLayoutType_Horizontal) ? 0 : 1;
const ImRect layout_rect(layout.StartPos, layout.StartPos + layout.MeasuredSize);
for (const ImGuiLayoutItem& item : layout.Items)
{
if (item.Type == ImGuiLayoutItemType_Item)
{
if (layout.DebugFlags & ImGuiLayoutDebugFlags_DrawItems)
draw_list->AddRect(item.MeasuredBounds.Min, item.MeasuredBounds.Max, IM_COL32(255, 255, 0, 160));
continue;
}
if ((layout.DebugFlags & ImGuiLayoutDebugFlags_DrawSprings) == 0)
continue;
// Along the layout: the spring, then its spacing. Across it: the whole layout.
ImRect rect = item.MeasuredBounds;
rect.Min[axis ^ 1] = layout_rect.Min[axis ^ 1];
rect.Max[axis ^ 1] = layout_rect.Max[axis ^ 1];
const float length = rect.Max[axis] - rect.Min[axis];
if (length <= 0.0f)
continue;
const float spacing = ImMin(ImFloor(item.SpringSpacing), length);
ImRect spring_rect = rect, spacing_rect = rect;
spring_rect.Max[axis] = spacing_rect.Min[axis] = rect.Max[axis] - spacing;
draw_list->PushClipRect(rect.Min, rect.Max, true);
draw_list->AddRectFilled(spring_rect.Min, spring_rect.Max, IM_COL32(80, 20, 80, 255));
draw_list->AddRectFilled(spacing_rect.Min, spacing_rect.Max, IM_COL32(80, 20, 20, 255));
// Zig-zag along the middle of the spring.
const float zig_zag_size = 3.0f;
ImVec2 direction(0.0f, 0.0f);
direction[axis] = 1.0f;
ImVec2 normal(-direction.y, direction.x);
ImVec2 origin = ImFloor(rect.GetCenter());
origin[axis] = ImFloor(rect.Min[axis]);
origin += ImVec2(0.5f, 0.5f);
draw_list->PathLineTo(origin);
for (float x = zig_zag_size * 0.5f; x <= length - spacing; x += zig_zag_size)
{
draw_list->PathLineTo(origin + direction * x + normal * zig_zag_size);
normal = -normal;
}
draw_list->PathStroke(IM_COL32(255, 255, 255, 190), 1.0f);
draw_list->PopClipRect();
}
}
//-----------------------------------------------------------------------------
// [SECTION] Stack Layout: Internal API
//-----------------------------------------------------------------------------
void ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryState* state_in)
{
// Called for every window, so it creates no state.
ImGuiContext& g = *GImGui;
ImGuiLayoutContextState* context_state = FindContextState(g);
ImGuiLayoutWindowState* state = context_state ? (ImGuiLayoutWindowState*)context_state->WindowStates.GetVoidPtr(g.CurrentWindow->ID) : NULL;
if (state == NULL)
return;
// Layouts and suspensions each push onto the ID stack: those begun after state_in was stored are above its size.
while (state->LayoutStack.Size > 0) //-V1044
{
if (state->CurrentLayout == NULL)
{
if (state->SuspendStack.back().IDStackSize < state_in->SizeOfIDStack)
break;
IM_ASSERT_USER_ERROR(0, "Missing ResumeLayout()");
ImGui::ResumeLayout();
}
else
{
if (state->CurrentLayout->IDStackSize < state_in->SizeOfIDStack)
break;
if (state->CurrentLayout->Type == ImGuiLayoutType_Horizontal)
{
IM_ASSERT_USER_ERROR(0, "Missing EndHorizontal()");
ImGui::EndHorizontal();
}
else
{
IM_ASSERT_USER_ERROR(0, "Missing EndVertical()");
ImGui::EndVertical();
}
}
}
}
} // namespace ImGuiStackLayout
//-----------------------------------------------------------------------------
// [SECTION] Stack Layout: Public API
//-----------------------------------------------------------------------------
void ImGui::BeginHorizontal(const char* str_id, const ImVec2& size/* = ImVec2(0, 0)*/, float align/* = -1*/)
{
ImGuiWindow* window = GetCurrentWindow();
ImGuiStackLayout::BeginLayout(window->GetID(str_id), ImGuiLayoutType_Horizontal, size, align);
}
void ImGui::BeginHorizontal(const void* ptr_id, const ImVec2& size/* = ImVec2(0, 0)*/, float align/* = -1*/)
{
ImGuiWindow* window = GetCurrentWindow();
ImGuiStackLayout::BeginLayout(window->GetID(ptr_id), ImGuiLayoutType_Horizontal, size, align);
}
void ImGui::BeginHorizontal(int id, const ImVec2& size/* = ImVec2(0, 0)*/, float align/* = -1*/)
{
ImGuiWindow* window = GetCurrentWindow();
ImGuiStackLayout::BeginLayout(window->GetID(id), ImGuiLayoutType_Horizontal, size, align);
}
void ImGui::EndHorizontal()
{
ImGuiStackLayout::ImGuiLayoutWindowState* window_state = ImGuiStackLayout::GetCurrentWindowLayoutState();
IM_ASSERT_USER_ERROR_RET(window_state->CurrentLayout != NULL || window_state->LayoutStack.Size == 0, "Calling EndHorizontal() between SuspendLayout() and ResumeLayout()!");
IM_ASSERT_USER_ERROR_RET(window_state->CurrentLayout != NULL, "Calling EndHorizontal() too many times!");
IM_ASSERT_USER_ERROR(window_state->CurrentLayout->Type == ImGuiLayoutType_Horizontal, "Calling EndHorizontal() to end a vertical layout!");
ImGuiStackLayout::EndLayout(window_state);
}
void ImGui::BeginVertical(const char* str_id, const ImVec2& size/* = ImVec2(0, 0)*/, float align/* = -1*/)
{
ImGuiWindow* window = GetCurrentWindow();
ImGuiStackLayout::BeginLayout(window->GetID(str_id), ImGuiLayoutType_Vertical, size, align);
}
void ImGui::BeginVertical(const void* ptr_id, const ImVec2& size/* = ImVec2(0, 0)*/, float align/* = -1*/)
{
ImGuiWindow* window = GetCurrentWindow();
ImGuiStackLayout::BeginLayout(window->GetID(ptr_id), ImGuiLayoutType_Vertical, size, align);
}
void ImGui::BeginVertical(int id, const ImVec2& size/* = ImVec2(0, 0)*/, float align/* = -1*/)
{
ImGuiWindow* window = GetCurrentWindow();
ImGuiStackLayout::BeginLayout(window->GetID(id), ImGuiLayoutType_Vertical, size, align);
}
void ImGui::EndVertical()
{
ImGuiStackLayout::ImGuiLayoutWindowState* window_state = ImGuiStackLayout::GetCurrentWindowLayoutState();
IM_ASSERT_USER_ERROR_RET(window_state->CurrentLayout != NULL || window_state->LayoutStack.Size == 0, "Calling EndVertical() between SuspendLayout() and ResumeLayout()!");
IM_ASSERT_USER_ERROR_RET(window_state->CurrentLayout != NULL, "Calling EndVertical() too many times!");
IM_ASSERT_USER_ERROR(window_state->CurrentLayout->Type == ImGuiLayoutType_Vertical, "Calling EndVertical() to end a horizontal layout!");
ImGuiStackLayout::EndLayout(window_state);
}
// Inserts spring separator in layout
// weight <= 0 : spring will always have zero size
// weight > 0 : power of current spring
// spacing < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0
// spacing >= 0 : enforce spacing amount
void ImGui::Spring(float weight/* = 1.0f*/, float spacing/* = -1.0f*/)
{
ImGuiStackLayout::ImGuiLayoutWindowState* window_state = ImGuiStackLayout::GetCurrentWindowLayoutState();
IM_ASSERT_USER_ERROR_RET(window_state->CurrentLayout != NULL, "Calling Spring() outside of a layout, or between SuspendLayout() and ResumeLayout()!");
ImGuiStackLayout::AddLayoutSpring(*window_state->CurrentLayout, weight, spacing);
}
void ImGui::SuspendLayout()
{
ImGuiWindow* window = GetCurrentWindow();
ImGuiStackLayout::ImGuiLayoutWindowState* window_state = ImGuiStackLayout::GetCurrentWindowLayoutState();
// Items in between flow as they would outside of the outermost layout, and are not measured by the layout.
// Pushing the current ID again leaves item IDs unchanged, and orders the suspension for error recovery.
ImGuiStackLayout::ImGuiLayoutSuspendBackup backup;
backup.LayoutType = window->DC.LayoutType;
backup.CursorMaxPos = window->DC.CursorMaxPos;
backup.IDStackSize = window->IDStack.Size;
window_state->SuspendStack.push_back(backup);
PushOverrideID(window->IDStack.back());
for (ImGuiStackLayout::ImGuiLayout* layout : window_state->LayoutStack)
{
if (layout != NULL)
{
window->DC.LayoutType = layout->BackupLayoutType;
break;
}
}
ImGuiStackLayout::PushLayout(NULL);
}
void ImGui::ResumeLayout()
{
ImGuiWindow* window = GetCurrentWindow();
ImGuiStackLayout::ImGuiLayoutWindowState* window_state = ImGuiStackLayout::GetCurrentWindowLayoutState();
IM_ASSERT_USER_ERROR_RET(window_state->SuspendStack.Size > 0, "Calling ResumeLayout() without SuspendLayout()!");
IM_ASSERT_USER_ERROR_RET(window_state->CurrentLayout == NULL, "Missing EndHorizontal() or EndVertical() before ResumeLayout()!");
ImGuiStackLayout::PopLayout(NULL);
const ImGuiStackLayout::ImGuiLayoutSuspendBackup& backup = window_state->SuspendStack.back();
IM_ASSERT_USER_ERROR(window->IDStack.Size == backup.IDStackSize + 1, "Mismatching PushID()/PopID() between SuspendLayout() and ResumeLayout()!");
while (window->IDStack.Size > backup.IDStackSize)
PopID();
window->DC.LayoutType = backup.LayoutType;
window->DC.CursorMaxPos = backup.CursorMaxPos;
window_state->SuspendStack.pop_back();
if (window_state->CurrentLayout)
ImGuiStackLayout::MarkLayoutLastItem(*window_state->CurrentLayout);
}
void ImGui::SetNextLayoutDebugFlags(ImGuiLayoutDebugFlags flags)
{
ImGuiStackLayout::GetContextState(*GImGui)->NextDebugFlags = flags;
}
//-----------------------------------------------------------------------------
#endif // #ifndef IMGUI_DISABLE