AnimTestbed/3rdparty/imgui/imgui_stacklayout.cpp

1207 lines
41 KiB
C++

// dear imgui, v1.86 WIP
// (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"
#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
#include <stddef.h> // intptr_t
#else
#include <stdint.h> // intptr_t
#endif
//-----------------------------------------------------------------------------
// [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;
unsigned int VertexIndexBegin;
unsigned 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 = (ImDrawIdx)0;
}
};
struct ImGuiLayout
{
ImGuiID Id;
ImGuiLayoutType Type;
bool Live;
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.
float Indent; // Indent used to align items in vertical layout.
ImVec2 StartPos; // Initial cursor position when BeginLayout is called.
ImVec2 StartCursorMaxPos; // Maximum cursor position when BeginLayout is called.
ImDrawListSplitter Splitter;
ImGuiLayout(ImGuiID id, ImGuiLayoutType type)
{
Id = id;
Type = type;
Live = false;
Size = CurrentSize = MinimumSize = MeasuredSize = ImVec2(0, 0);
CurrentItemIndex = 0;
ParentItemIndex = 0;
Parent = FirstChild = NextSibling = NULL;
Align = -1.0f;
Indent = 0.0f;
StartPos = ImVec2(0, 0);
StartCursorMaxPos = ImVec2(0, 0);
}
};
struct ImGuiLayoutWindowState
{
ImGuiWindow* Window;
ImGuiLayout* CurrentLayout;
ImGuiLayoutItem* CurrentLayoutItem;
ImVector<ImGuiLayout*> LayoutStack;
ImGuiStorage Layouts;
ImGuiLayoutWindowState()
{
Window = NULL;
CurrentLayout = NULL;
CurrentLayoutItem = NULL;
}
};
struct ImGuiLayoutState
{
ImGuiStorage LayoutWindowStates;
ImGuiContext* Context;
ImGuiID ContextID;
ImGuiID NewFramePreID;
ImGuiID EndFramePreID;
ImGuiID ShutdownHookID;
ImGuiLayoutState()
{
Context = NULL;
ContextID = 0;
NewFramePreID = 0;
EndFramePreID = 0;
ShutdownHookID = 0;
}
};
//-----------------------------------------------------------------------------
// [SECTION] Stack Layout: Context forward declarations
//-----------------------------------------------------------------------------
static ImGuiStorage GStackLayoutStates;
// Context management/hooks
static ImGuiID GetContextID(ImGuiContext* context); // FIXME: ImGuiContext probably should have their own ID assigned
static ImGuiLayoutState* GetCurrentLayoutState();
static ImGuiLayoutState* GetLayoutState(ImGuiContext* context);
static ImGuiLayoutState* CreateLayoutState(ImGuiID context_id, ImGuiContext* context);
static void StackLayout_NewFramePreCallback(ImGuiContext* ctx, ImGuiContextHook* hook);
static void StackLayout_EndFramePreCallback(ImGuiContext* ctx, ImGuiContextHook* hook);
static void StackLayout_ShutdownCallback(ImGuiContext* ctx, ImGuiContextHook* hook);
static ImGuiLayoutWindowState* GetWindowLayoutState(ImGuiID window_id, ImGuiWindow* window = NULL);
static ImGuiLayoutWindowState* GetCurrentWindowLayoutState();
static void WindowLayoutState_OnNewFrame(ImGuiLayoutWindowState* state);
static void WindowLayoutState_OnEndFrame(ImGuiLayoutWindowState* state);
static void WindowLayoutState_OnShutdown(ImGuiLayoutWindowState* state);
//-----------------------------------------------------------------------------
// [SECTION] Stack Layout: Internal code forward declarations
//-----------------------------------------------------------------------------
namespace ImGui
{
// 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(ImGuiLayoutType type);
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);
static void EndLayoutClipRect(ImGuiLayout& layout);
static void ApplyLayoutClipRect(ImGuiLayout& layout);
static void MergeLayoutSplitters(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 BeginLayoutItem(ImGuiLayout& layout);
static void EndLayoutItem(ImGuiLayout& layout);
static void AddLayoutSpring(ImGuiLayout& layout, float weight, float spacing);
}
//-----------------------------------------------------------------------------
// [SECTION] Stack Layout: Context
//-----------------------------------------------------------------------------
static ImGuiID GetContextID(ImGuiContext* context)
{
// Hash pointer to ImGuiContext which is unique
return ImHashData(&context, sizeof(context));
}
static ImGuiLayoutState* GetCurrentLayoutState()
{
return GetLayoutState(ImGui::GetCurrentContext());
}
static ImGuiLayoutState* GetLayoutState(ImGuiContext* context)
{
if (context == NULL)
return NULL;
ImGuiID context_id = GetContextID(context);
ImGuiLayoutState* state = (ImGuiLayoutState*)GStackLayoutStates.GetVoidPtr(context_id);
if (state == NULL)
state = CreateLayoutState(context_id, context);
return state;
}
static ImGuiLayoutState* CreateLayoutState(ImGuiID context_id, ImGuiContext* context)
{
ImGuiLayoutState* state = IM_NEW(ImGuiLayoutState);
state->Context = context;
state->ContextID = context_id;
ImGuiContextHook new_frame_pre_hook;
new_frame_pre_hook.Type = ImGuiContextHookType_NewFramePre;
new_frame_pre_hook.UserData = state;
new_frame_pre_hook.Callback = StackLayout_NewFramePreCallback;
state->NewFramePreID = ImGui::AddContextHook(context, &new_frame_pre_hook);
ImGuiContextHook end_frame_pre_hook;
end_frame_pre_hook.Type = ImGuiContextHookType_EndFramePre;
end_frame_pre_hook.UserData = state;
end_frame_pre_hook.Callback = StackLayout_EndFramePreCallback;
state->EndFramePreID = ImGui::AddContextHook(context, &end_frame_pre_hook);
ImGuiContextHook shutdown_hook;
shutdown_hook.Type = ImGuiContextHookType_Shutdown;
shutdown_hook.UserData = state;
shutdown_hook.Callback = StackLayout_ShutdownCallback;
state->ShutdownHookID = ImGui::AddContextHook(context, &shutdown_hook);
GStackLayoutStates.SetVoidPtr(context_id, state);
return state;
}
static void StackLayout_NewFramePreCallback(ImGuiContext* ctx, ImGuiContextHook* hook)
{
ImGuiLayoutState* layout_state = (ImGuiLayoutState*)hook->UserData;
for (int i = 0; i < layout_state->LayoutWindowStates.Data.Size; ++i)
{
ImGuiLayoutWindowState* layout_window_state = (ImGuiLayoutWindowState*)layout_state->LayoutWindowStates.Data[i].val_p;
WindowLayoutState_OnNewFrame(layout_window_state);
}
}
static void StackLayout_EndFramePreCallback(ImGuiContext* ctx, ImGuiContextHook* hook)
{
ImGuiLayoutState* layout_state = (ImGuiLayoutState*)hook->UserData;
for (int i = 0; i < layout_state->LayoutWindowStates.Data.Size; ++i)
{
ImGuiLayoutWindowState* layout_window_state = (ImGuiLayoutWindowState*)layout_state->LayoutWindowStates.Data[i].val_p;
WindowLayoutState_OnEndFrame(layout_window_state);
}
}
static void StackLayout_ShutdownCallback(ImGuiContext* ctx, ImGuiContextHook* hook)
{
ImGuiLayoutState* layout_state = (ImGuiLayoutState*)hook->UserData;
for (int i = 0; i < layout_state->LayoutWindowStates.Data.Size; ++i)
{
ImGuiLayoutWindowState* layout_window_state = (ImGuiLayoutWindowState*)layout_state->LayoutWindowStates.Data[i].val_p;
WindowLayoutState_OnShutdown(layout_window_state);
IM_DELETE(layout_window_state);
}
GStackLayoutStates.SetVoidPtr(layout_state->ContextID, nullptr);
IM_DELETE(layout_state);
}
static ImGuiLayoutWindowState* GetWindowLayoutState(ImGuiID window_id, ImGuiWindow* window)
{
ImGuiLayoutState* layout_state = GetCurrentLayoutState();
if (layout_state == NULL)
return NULL;
ImGuiLayoutWindowState* window_layout_state = (ImGuiLayoutWindowState*)layout_state->LayoutWindowStates.GetVoidPtr(window_id);
if (window_layout_state == NULL)
{
if (window == NULL)
{
window = ImGui::FindWindowByID(window_id);
IM_ASSERT(window && "GetWindowLayoutState called with invalid Window ID.");
}
window_layout_state = IM_NEW(ImGuiLayoutWindowState);
window_layout_state->Window = window;
layout_state->LayoutWindowStates.SetVoidPtr(window_id, window_layout_state);
}
return window_layout_state;
}
static ImGuiLayoutWindowState* GetCurrentWindowLayoutState()
{
ImGuiWindow* current_window = ImGui::GetCurrentWindow();
if (current_window == NULL)
return NULL;
return GetWindowLayoutState(current_window->ID, current_window);
}
static void WindowLayoutState_OnNewFrame(ImGuiLayoutWindowState* state)
{
// Mark all layouts as dead. They may be revived in this frame.
for (int i = 0; i < state->Layouts.Data.Size; i++)
{
ImGuiLayout* layout = (ImGuiLayout*)state->Layouts.Data[i].val_p;
layout->Live = false;
}
}
static void WindowLayoutState_OnEndFrame(ImGuiLayoutWindowState* state)
{
// Check stacks (like ImGuiStackSizes::CompareWithCurrentState() does)
IM_ASSERT(0 == state->LayoutStack.Size && (!state->LayoutStack.Size || state->LayoutStack.back()->Type == ImGuiLayoutType_Horizontal) && "BeginHorizontal/EndHorizontal Mismatch!");
IM_ASSERT(0 == state->LayoutStack.Size && (!state->LayoutStack.Size || state->LayoutStack.back()->Type == ImGuiLayoutType_Vertical) && "BeginVertical/EndVertical Mismatch!");
}
static void WindowLayoutState_OnShutdown(ImGuiLayoutWindowState* state)
{
for (int i = 0; i < state->Layouts.Data.Size; i++)
{
ImGuiLayout* layout = (ImGuiLayout*)state->Layouts.Data[i].val_p;
IM_DELETE(layout);
}
}
//-----------------------------------------------------------------------------
// [SECTION] Stack Layout: Internal code
//-----------------------------------------------------------------------------
static ImGuiLayout* ImGui::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* ImGui::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 ImGui::BeginLayout(ImGuiID id, ImGuiLayoutType type, ImVec2 size, float align)
{
ImGuiWindow* window = GetCurrentWindow();
PushID(id);
// Find or create
ImGuiLayout* layout = FindLayout(id, type);
if (!layout)
layout = CreateNewLayout(id, type, size);
IM_ASSERT(!layout->Live && "BeginHorizontal/BeginVertical with same ID is already live in this frame. Please use PushID() to make ID's unique or rename layout.");
layout->Live = true;
PushLayout(layout);
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;
layout->StartPos = window->DC.CursorPos;
layout->StartCursorMaxPos = window->DC.CursorMaxPos;
BeginLayoutClipRect(*layout);
if (type == ImGuiLayoutType_Vertical)
{
// Push empty item to recalculate cursor position.
PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
Dummy(ImVec2(0.0f, 0.0f));
PopStyleVar();
// Indent horizontal position to match edge of the layout.
layout->Indent = layout->StartPos.x - window->DC.CursorPos.x;
SignedIndent(layout->Indent);
}
BeginLayoutItem(*layout);
}
static void ImGui::EndLayout(ImGuiLayoutType type)
{
ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState();
IM_ASSERT(window_state->CurrentLayout);
IM_ASSERT(window_state->CurrentLayout->Type == type);
IM_UNUSED(type);
ImGuiLayout* layout = window_state->CurrentLayout;
EndLayoutItem(*layout);
if (layout->CurrentItemIndex < layout->Items.Size)
layout->Items.resize(layout->CurrentItemIndex);
if (layout->Type == ImGuiLayoutType_Vertical)
SignedIndent(-layout->Indent);
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;
if ((auto_width || auto_height) && layout->Parent)
{
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;
PopID();
ImVec2 current_layout_item_max = ImVec2(0.0f, 0.0f);
if (window_state->CurrentLayoutItem)
current_layout_item_max = ImMax(window_state->CurrentLayoutItem->MeasuredBounds.Max, layout->StartPos + new_size);
window_state->Window->DC.CursorPos = layout->StartPos;
window_state->Window->DC.CursorMaxPos = layout->StartCursorMaxPos;
ItemSize(new_size);
ItemAdd(ImRect(layout->StartPos, layout->StartPos + measured_size), 0);
if (window_state->CurrentLayoutItem)
window_state->CurrentLayoutItem->MeasuredBounds.Max = current_layout_item_max;
if (layout->Parent == NULL)
BalanceChildLayouts(*layout);
EndLayoutClipRect(*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 ImGui::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 ImGui::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;
window_state->CurrentLayoutItem = NULL;
}
static void ImGui::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();
if (!window_state->LayoutStack.empty())
{
window_state->CurrentLayout = window_state->LayoutStack.back();
window_state->CurrentLayoutItem = &window_state->CurrentLayout->Items[window_state->CurrentLayout->CurrentItemIndex];
}
else
{
window_state->CurrentLayout = NULL;
window_state->CurrentLayoutItem = NULL;
}
}
static void ImGui::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);
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)
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;
}
// If spring changed its size, fix positioning of following items to avoid one frame visual bugs.
if (last_spring_size != item.SpringSize)
{
float difference = item.SpringSize - last_spring_size;
ImVec2 offset = is_horizontal ? ImVec2(difference, 0.0f) : ImVec2(0.0f, difference);
item.MeasuredBounds.Max += offset;
for (int j = i + 1; j < layout.Items.Size; j++)
{
ImGuiLayoutItem& translated_item = layout.Items[j];
TranslateLayoutItem(translated_item, offset);
translated_item.MeasuredBounds.Min += offset;
translated_item.MeasuredBounds.Max += offset;
}
}
}
}
static ImVec2 ImGui::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 ImGui::BalanceLayoutItemsAlignment(ImGuiLayout& layout)
{
for (int i = 0; i < layout.Items.Size; ++i)
{
ImGuiLayoutItem& item = layout.Items[i];
BalanceLayoutItemAlignment(layout, item);
}
}
static bool ImGui::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 ImGui::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);
}
static void ImGui::BeginLayoutClipRect(ImGuiLayout& layout)
{
ImGuiWindow* window = GetCurrentWindow();
// Use splitter to collect draw commands in separate channel,
// so we can clip them to the layout bounds.
layout.Splitter.Split(window->DrawList, 2);
layout.Splitter.SetCurrentChannel(window->DrawList, 1);
// Clip to layout bounds, unrestricted and not measured bounds span
// all the way to the edge of the window.
ImVec2 clip_rect_min = layout.StartPos;
ImVec2 clip_rect_max;
clip_rect_max.x = layout.Size.x > 0.0f ? layout.StartPos.x + layout.Size.x : FLT_MAX;
clip_rect_max.y = layout.Size.y > 0.0f ? layout.StartPos.y + layout.Size.y : FLT_MAX;
PushClipRect(clip_rect_min, clip_rect_max, true);
}
static void ImGui::EndLayoutClipRect(ImGuiLayout& layout)
{
PopClipRect();
if (layout.Parent != NULL)
return;
ApplyLayoutClipRect(layout);
MergeLayoutSplitters(layout);
}
static void ImGui::ApplyLayoutClipRect(ImGuiLayout& layout)
{
for (ImGuiLayout* child = layout.FirstChild; child != NULL; child = child->NextSibling)
ApplyLayoutClipRect(*child);
ImGuiWindow* window = GetCurrentWindow();
ImVec4 current_clip_rect;
current_clip_rect.x = layout.StartPos.x;
current_clip_rect.y = layout.StartPos.y;
current_clip_rect.z = layout.StartPos.x + layout.MeasuredSize.x;
current_clip_rect.w = layout.StartPos.y + layout.MeasuredSize.y;
layout.Splitter.SetCurrentChannel(window->DrawList, 0);
for (ImDrawCmd& cmd : layout.Splitter._Channels[1]._CmdBuffer)
{
if (cmd.ClipRect.x < current_clip_rect.x) cmd.ClipRect.x = current_clip_rect.x;
if (cmd.ClipRect.y < current_clip_rect.y) cmd.ClipRect.y = current_clip_rect.y;
if (cmd.ClipRect.z > current_clip_rect.z) cmd.ClipRect.z = current_clip_rect.z;
if (cmd.ClipRect.w > current_clip_rect.w) cmd.ClipRect.w = current_clip_rect.w;
}
//GetForegroundDrawList()->AddRect(layout.StartPos, layout.StartPos + layout.MeasuredSize, IM_COL32(255,0,0,128)); // [DEBUG]
}
static void ImGui::MergeLayoutSplitters(ImGuiLayout& layout)
{
for (ImGuiLayout* child = layout.FirstChild; child != NULL; child = child->NextSibling)
MergeLayoutSplitters(*child);
ImGuiWindow* window = GetCurrentWindow();
layout.Splitter.Merge(window->DrawList);
}
static ImGuiLayoutItem* ImGui::GenerateLayoutItem(ImGuiLayout& layout, ImGuiLayoutItemType type)
{
ImGuiContext& g = *GImGui;
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));
}
ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState();
window_state->CurrentLayoutItem = &layout.Items[layout.CurrentItemIndex];
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 ImGui::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 ImGui::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 = 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 ImGui::SignedIndent(float indent)
{
if (indent > 0.0f)
Indent(indent);
else if (indent < 0.0f)
Unindent(-indent);
}
static void ImGui::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 (item.CurrentAlign > 0.0f)
{
if (layout.Type == ImGuiLayoutType_Horizontal)
{
window->DC.CursorPos.y += item.CurrentAlignOffset;
}
else
{
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;
}
}
item.MeasuredBounds.Min = item.MeasuredBounds.Max = window->DC.CursorPos;
item.VertexIndexBegin = item.VertexIndexEnd = window->DrawList->_VtxCurrentIdx;
}
static void ImGui::EndLayoutItem(ImGuiLayout& layout)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
IM_ASSERT(layout.CurrentItemIndex < layout.Items.Size);
ImGuiLayoutItem& item = layout.Items[layout.CurrentItemIndex];
ImDrawList* draw_list = window->DrawList;
item.VertexIndexEnd = draw_list->_VtxCurrentIdx;
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 ImGui::AddLayoutSpring(ImGuiLayout& layout, float weight, float spacing)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiLayoutItem* previous_item = &layout.Items[layout.CurrentItemIndex];
// Undo item padding, spring should consume all space between items.
if (layout.Type == ImGuiLayoutType_Horizontal)
window->DC.CursorPos.x = previous_item->MeasuredBounds.Max.x;
else
window->DC.CursorPos.y = previous_item->MeasuredBounds.Max.y;
previous_item = NULL; // may be invalid after call to GenerateLayoutItem()
EndLayoutItem(layout);
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;
if (spring_item->SpringSize > 0.0f || spacing > 0.0f)
{
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);
}
PushStyleVar(ImGuiStyleVar_ItemSpacing, ImFloor(spring_spacing));
Dummy(ImFloor(spring_size));
PopStyleVar();
}
layout.CurrentItemIndex++;
BeginLayoutItem(layout);
}
//-----------------------------------------------------------------------------
// [SECTION] Stack Layout: Internal API
//-----------------------------------------------------------------------------
ImGuiLayoutType ImGuiInternal::GetCurrentLayoutType(ImGuiID window_id)
{
ImGuiLayoutWindowState* state = GetWindowLayoutState(window_id);
ImGuiLayoutType layout_type = state->Window->DC.LayoutType;
if (state->CurrentLayout)
layout_type = state->CurrentLayout->Type;
return layout_type;
}
void ImGuiInternal::UpdateItemRect(ImGuiID window_id, const ImVec2& min, const ImVec2& max)
{
ImGuiLayoutWindowState* state = GetWindowLayoutState(window_id);
if (state->CurrentLayoutItem)
state->CurrentLayoutItem->MeasuredBounds.Max = ImMax(state->CurrentLayoutItem->MeasuredBounds.Max, max);
IM_UNUSED(min);
}
//-----------------------------------------------------------------------------
// [SECTION] Stack Layout: Public API
//-----------------------------------------------------------------------------
void ImGui::BeginHorizontal(const char* str_id, const ImVec2& size/* = ImVec2(0, 0)*/, float align/* = -1*/)
{
ImGuiWindow* window = GetCurrentWindow();
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();
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();
BeginLayout(window->GetID((void*)(intptr_t)id), ImGuiLayoutType_Horizontal, size, align);
}
void ImGui::EndHorizontal()
{
EndLayout(ImGuiLayoutType_Horizontal);
}
void ImGui::BeginVertical(const char* str_id, const ImVec2& size/* = ImVec2(0, 0)*/, float align/* = -1*/)
{
ImGuiWindow* window = GetCurrentWindow();
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();
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();
BeginLayout(window->GetID((void*)(intptr_t)id), ImGuiLayoutType_Vertical, size, align);
}
void ImGui::EndVertical()
{
EndLayout(ImGuiLayoutType_Vertical);
}
// 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*/)
{
ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState();
IM_ASSERT(window_state->CurrentLayout);
AddLayoutSpring(*window_state->CurrentLayout, weight, spacing);
}
void ImGui::SuspendLayout()
{
PushLayout(NULL);
}
void ImGui::ResumeLayout()
{
ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState();
IM_ASSERT(!window_state->CurrentLayout);
IM_ASSERT(!window_state->LayoutStack.empty());
IM_UNUSED(window_state);
PopLayout(NULL);
}
//-----------------------------------------------------------------------------
#endif // #ifndef IMGUI_DISABLE