From 801e7b638ce1c04e30e9334cefae9047fea572f4 Mon Sep 17 00:00:00 2001 From: thedmd Date: Fri, 3 May 2019 11:59:34 +0200 Subject: [PATCH] thedmd: Stack Layout implementation v2.1 / pthom: revert clipping See https://github.com/ocornut/imgui/pull/846#issuecomment-2218360061 : clipping has an issue, when mixed with your node editor and springs. --- imgui.cpp | 43 +- imgui.h | 7 + imgui_demo.cpp | 150 +++++ imgui_internal.h | 9 + imgui_stacklayout.cpp | 1128 ++++++++++++++++++++++++++++++++++ imgui_stacklayout.h | 38 ++ imgui_stacklayout_internal.h | 30 + 7 files changed, 1401 insertions(+), 4 deletions(-) create mode 100644 imgui_stacklayout.cpp create mode 100644 imgui_stacklayout.h create mode 100644 imgui_stacklayout_internal.h diff --git a/imgui.cpp b/imgui.cpp index 8196cb12ba38..baa47a9afca0 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1334,6 +1334,7 @@ ImGuiStyle::ImGuiStyle() ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar GrabMinSize = 12.0f; // Minimum width/height of a grab box for slider/scrollbar GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + LayoutAlign = 0.5f; // Element alignment inside horizontal and vertical layouts (0.0f - left/top, 1.0f - right/bottom, 0.5f - center). LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. TabBorderSize = 0.0f; // Thickness of border around tabs. @@ -3437,6 +3438,7 @@ static const ImGuiDataVarInfo GStyleVarInfo[] = { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, LayoutAlign) }, // ImGuiStyleVar_LayoutAlign { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBorderSize) }, // ImGuiStyleVar_TabBorderSize { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize) }, // ImGuiStyleVar_TabBarBorderSize @@ -11633,6 +11635,10 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu g.NextItemData.HasFlags = ImGuiNextItemDataFlags_None; g.NextItemData.ItemFlags = ImGuiItemFlags_None; +#if IMGUI_HAS_STACK_LAYOUT + ImGuiInternal::UpdateItemRect(window->ID, bb.Min, bb.Max); +#endif + #ifdef IMGUI_ENABLE_TEST_ENGINE if (id != 0) IMGUI_TEST_ENGINE_ITEM_ADD(id, g.LastItemData.NavRect, &g.LastItemData); @@ -11714,6 +11720,38 @@ void ImGui::ItemSize(const ImVec2& size, float text_baseline_y) if (window->SkipItems) return; +#if IMGUI_HAS_STACK_LAYOUT + ImGuiLayoutType layout_type = ImGuiInternal::GetCurrentLayoutType(window->ID); +#else + ImGuiLayoutType layout_type = window->DC.LayoutType; +#endif + + //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorPos, 3.0f, IM_COL32(255,255,0,255), 4); // [DEBUG] Widget position + + // Stack Layouts: Handle horizontal case first to simplify merge in case code handling vertical changes. + if (layout_type == ImGuiLayoutType_Horizontal) + { + const float line_width = ImMax(window->DC.CurrLineSize.x, size.x); + + // Always align ourselves on pixel boundaries + //if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG] + window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x; + window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y + size.y; + window->DC.CursorPos.x = IM_TRUNC(window->DC.CursorPos.x + line_width + g.Style.ItemSpacing.x); + window->DC.CursorPos.y = IM_TRUNC(window->DC.CursorPosPrevLine.y - size.y); + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x - g.Style.ItemSpacing.x); + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPosPrevLine.y); + //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] + + window->DC.PrevLineSize.x = line_width; + window->DC.PrevLineSize.y = 0.0f; + window->DC.CurrLineSize.x = 0.0f; + window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y); + window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; + window->DC.IsSameLine = window->DC.IsSetPos = false; + return; + } + // We increase the height in this function to accommodate for baseline offset. // In theory we should be offsetting the starting position (window->DC.CursorPos), that will be the topic of a larger refactor, // but since ItemSize() is not yet an API that moves the cursor (to handle e.g. wrapping) enlarging the height has the same effect. @@ -11732,15 +11770,12 @@ void ImGui::ItemSize(const ImVec2& size, float text_baseline_y) window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y); //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] + window->DC.PrevLineSize.x = 0.0f; window->DC.PrevLineSize.y = line_height; window->DC.CurrLineSize.y = 0.0f; window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y); window->DC.CurrLineTextBaseOffset = 0.0f; window->DC.IsSameLine = window->DC.IsSetPos = false; - - // Horizontal layout mode - if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) - SameLine(); } IM_MSVC_RUNTIME_CHECKS_RESTORE diff --git a/imgui.h b/imgui.h index c0d1869e36f8..66fd32006817 100644 --- a/imgui.h +++ b/imgui.h @@ -33,6 +33,7 @@ #define IMGUI_HAS_TABLE #define IMGUI_HAS_VIEWPORT // Viewport WIP branch #define IMGUI_HAS_DOCK // Docking WIP branch +#define IMGUI_HAS_STACK_LAYOUT 1 // Stack-Layout PR #846 // // Adaptations for ImGui Bundle are noted with [ADAPT_IMGUI_BUNDLE] @@ -1864,6 +1865,7 @@ enum ImGuiStyleVar_ ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding ImGuiStyleVar_GrabMinSize, // float GrabMinSize ImGuiStyleVar_GrabRounding, // float GrabRounding + ImGuiStyleVar_LayoutAlign, // float LayoutAlign ImGuiStyleVar_TabRounding, // float TabRounding ImGuiStyleVar_TabBorderSize, // float TabBorderSize ImGuiStyleVar_TabBarBorderSize, // float TabBarBorderSize @@ -2346,6 +2348,7 @@ struct ImGuiStyle float ScrollbarRounding; // Radius of grab corners for scrollbar. float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar. float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + float LayoutAlign; // Element alignment inside horizontal and vertical layouts (0.0f - left/top, 1.0f - right/bottom, 0.5f - center). float LogSliderDeadzone; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. float TabRounding; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. float TabBorderSize; // Thickness of border around tabs. @@ -4162,6 +4165,10 @@ namespace ImGui #pragma warning (pop) #endif +#if IMGUI_HAS_STACK_LAYOUT +#include "imgui_stacklayout.h" +#endif + // Include imgui_user.h at the end of imgui.h // May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included. #ifdef IMGUI_INCLUDE_IMGUI_USER_H diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 85d0d36fc585..afc1df797bca 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -4877,6 +4877,156 @@ static void ShowDemoWindowLayout() ImGui::TreePop(); } + +#if IMGUI_HAS_STACK_LAYOUT + IMGUI_DEMO_MARKER("Layout/Stack Layout"); + if (ImGui::TreeNode("Stack Layout")) + { + static bool widget_a = true, widget_b = true, widget_c = true; + static bool spring_a = true, spring_ab = true, spring_bc = true, spring_c = true; + static bool minimize_width = false, minimize_height = true; + static bool horizontal = true, draw_springs = true; + static ImVec2 item_spacing = ImGui::GetStyle().ItemSpacing; + static float a_c_spring_weight = 0.0f; + static float ab_spring_weight = 0.5f; + static float alignment = 0.5f; + + struct funcs + { + static void VisibleSpring(float spring_weight) + { + ImGui::Spring(spring_weight); + if (!draw_springs) + return; + + ImVec2 rect_min = ImGui::GetItemRectMin(); + ImVec2 rect_max = ImGui::GetItemRectMax(); + + ImVec2 rect_size = ImGui::GetItemRectSize(); + if (rect_size.x <= 0.0f && rect_size.y <= 0.0f) + return; + + // Draw zig-zag + float width = 0.0f, spacing = 0.0f; + ImVec2 direction, origin; + ImVec2 spacing_min, spring_max; + + if (horizontal) + { + spacing = floorf(item_spacing.x); + width = rect_size.x - spacing; + origin = ImVec2(floorf(rect_min.x), floorf(rect_min.y + (rect_max.y - rect_min.y) / 2)); + direction = ImVec2(1.0f, 0.0f); + spring_max = ImVec2(rect_min.x + width, rect_max.y); + spacing_min = ImVec2(rect_min.x + width, rect_min.y); + } + else + { + spacing = floorf(item_spacing.y); + width = rect_size.y - spacing; + origin = ImVec2(floorf(rect_min.x + (rect_max.x - rect_min.x) / 2), floorf(rect_min.y)); + direction = ImVec2(0.0f, 1.0f); + spring_max = ImVec2(rect_max.x, rect_min.y + width); + spacing_min = ImVec2(rect_min.x, rect_min.y + width); + } + + if (spring_weight <= 0.0f && spacing <= 0.0f) + return; + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + draw_list->PushClipRect(rect_min, rect_max, true); + + draw_list->AddRectFilled(rect_min, spring_max, ImColor(80, 20, 80)); + draw_list->AddRectFilled(spacing_min, rect_max, ImColor(80, 20, 20)); + + const float zig_zag_size = 3; + ImVec2 normal = ImVec2(-direction.y, direction.x); + + draw_list->PathClear(); + origin.x += 0.5f; + origin.y += 0.5f; + draw_list->PathLineTo(origin); + for (float x = zig_zag_size * 0.5f; x <= width; x += zig_zag_size) + { + ImVec2 p; + p.x = origin.x + direction.x * x + normal.x * zig_zag_size; + p.y = origin.y + direction.y * x + normal.y * zig_zag_size; + draw_list->PathLineTo(p); + normal = ImVec2(-normal.x, -normal.y); + } + draw_list->PathStroke(ImColor(255, 255, 255, 190), false, 1.0f); + + draw_list->PopClipRect(); + } + }; + + ImGui::Checkbox("Widget A", &widget_a); ImGui::SameLine(); + ImGui::Checkbox("Widget B", &widget_b); ImGui::SameLine(); + ImGui::Checkbox("Widget C", &widget_c); + ImGui::Checkbox("Spring A", &spring_a); ImGui::SameLine(); + ImGui::Checkbox("Spring AB", &spring_ab); ImGui::SameLine(); + ImGui::Checkbox("Spring BC", &spring_bc); ImGui::SameLine(); + ImGui::Checkbox("Spring C", &spring_c); + ImGui::Checkbox("Horizontal", &horizontal); ImGui::SameLine(); + ImGui::Checkbox("Minimize Width", &minimize_width); ImGui::SameLine(); + ImGui::Checkbox("Minimize Height", &minimize_height); + ImGui::Checkbox("Draw Springs", &draw_springs); ImGui::SameLine(); + ImGui::TextUnformatted(" "); ImGui::SameLine(); + ImGui::ColorButton("- Spring", ImColor(80, 20, 80), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoPicker); ImGui::SameLine(); + ImGui::TextUnformatted("Spring"); ImGui::SameLine(); + ImGui::TextUnformatted(" "); ImGui::SameLine(); + ImGui::ColorButton("- Spacing", ImColor(80, 20, 20), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoPicker); ImGui::SameLine(); + ImGui::TextUnformatted("Item Spacing"); + ImGui::DragFloat("Item Spacing", horizontal ? &item_spacing.x : &item_spacing.y, 0.1f, 0.0f, 50.0f); + ImGui::DragFloat("A & C Spring Weight", &a_c_spring_weight, 0.002f, 0.0f, 1.0f); + ImGui::DragFloat("AB Spring Weight", &ab_spring_weight, 0.002f, 0.0f, 1.0f); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("BC Spring Weight = 1 - AB Spring Weight"); + ImGui::DragFloat("Minor Axis Alignment", &alignment, 0.002f, 0.0f, 1.0f); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("This is vertical alignment for horizontal layouts and horizontal alignment for vertical layouts."); + ImGui::Text("Layout widgets:"); + ImGui::Text("| Spring A | Widget A | Spring AB | Widget B | Spring BC | Widget C | Spring C |"); + + ImGui::Spacing(); + + ImVec2 widget_size; + widget_size.x = floorf(ImGui::GetContentRegionAvail().x / 4); + widget_size.y = horizontal ? floorf(widget_size.x / 3) : widget_size.x; + + ImVec2 small_widget_size = widget_size; + if (horizontal) + small_widget_size.y = floorf(small_widget_size.y / 2); + else + small_widget_size.x = floorf(small_widget_size.x / 2); + + ImVec2 layout_size = ImVec2(widget_size.x * 4, widget_size.y * 4); + if (minimize_width) layout_size.x = 0.0f; + if (minimize_height) layout_size.y = 0.0f; + + // Minor axis alignment can be set by style or directly in BeginHorizontal/BeginVertical + // Example: + // ImGui::PushStyleVar(ImGuiStyleVar_LayoutAlign, alignment); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(floorf(item_spacing.x), floorf(item_spacing.y))); + + if (horizontal) { ImGui::BeginHorizontal("h1", layout_size, alignment); } else { ImGui::BeginVertical("v1", layout_size, alignment); } + if (spring_a) { funcs::VisibleSpring(a_c_spring_weight); } + if (widget_a) { ImGui::Button("Widget A", widget_size); } + if (spring_ab) { funcs::VisibleSpring(ab_spring_weight); } + if (widget_b) { ImGui::Button("Widget B", small_widget_size); } + if (spring_bc) { funcs::VisibleSpring(1.0f - ab_spring_weight); } + if (widget_c) { ImGui::Button("Widget C", widget_size); } + if (spring_c) { funcs::VisibleSpring(a_c_spring_weight); } + if (horizontal) { ImGui::EndHorizontal(); } else { ImGui::EndVertical(); } + + ImGui::PopStyleVar(); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImGui::GetColorU32(ImGuiCol_Border)); + + ImGui::TreePop(); + } +#endif // IMGUI_HAS_STACK_LAYOUT } //----------------------------------------------------------------------------- diff --git a/imgui_internal.h b/imgui_internal.h index 389d7713e511..b804cfa31a22 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -11,6 +11,7 @@ Index of this file: // [SECTION] Forward declarations // [SECTION] Context pointer // [SECTION] STB libraries includes +// [SECTION] Stack Layout includes // [SECTION] Macros // [SECTION] Generic helpers // [SECTION] ImDrawList support @@ -221,6 +222,14 @@ using ImGuiErrorStringCallback = std::function; extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #endif +//------------------------------------------------------------------------- +// [SECTION] Stack Layout includes +//------------------------------------------------------------------------- + +#if IMGUI_HAS_STACK_LAYOUT +# include "imgui_stacklayout_internal.h" +#endif + //----------------------------------------------------------------------------- // [SECTION] Macros //----------------------------------------------------------------------------- diff --git a/imgui_stacklayout.cpp b/imgui_stacklayout.cpp new file mode 100644 index 000000000000..0b2108438c89 --- /dev/null +++ b/imgui_stacklayout.cpp @@ -0,0 +1,1128 @@ +// 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 // intptr_t +#else +#include // 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 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. + + 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 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 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; + + 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; + + 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); + + //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 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 diff --git a/imgui_stacklayout.h b/imgui_stacklayout.h new file mode 100644 index 000000000000..d9364dfbc48f --- /dev/null +++ b/imgui_stacklayout.h @@ -0,0 +1,38 @@ +// dear imgui, v1.86 WIP +// (stack layout headers) + +/* + +Index of this file: +// [SECTION] Stack Layout API + +*/ + +#pragma once + +#include "imgui.h" + +#ifndef IMGUI_DISABLE + +//----------------------------------------------------------------------------- +// [SECTION] Stack Layout API +//----------------------------------------------------------------------------- + +namespace ImGui +{ + IMGUI_API void BeginHorizontal(const char* str_id, const ImVec2& size = ImVec2(0, 0), float align = -1.0f); + IMGUI_API void BeginHorizontal(const void* ptr_id, const ImVec2& size = ImVec2(0, 0), float align = -1.0f); + IMGUI_API void BeginHorizontal(int id, const ImVec2& size = ImVec2(0, 0), float align = -1); + IMGUI_API void EndHorizontal(); + IMGUI_API void BeginVertical(const char* str_id, const ImVec2& size = ImVec2(0, 0), float align = -1.0f); + IMGUI_API void BeginVertical(const void* ptr_id, const ImVec2& size = ImVec2(0, 0), float align = -1.0f); + IMGUI_API void BeginVertical(int id, const ImVec2& size = ImVec2(0, 0), float align = -1); + IMGUI_API void EndVertical(); + IMGUI_API void Spring(float weight = 1.0f, float spacing = -1.0f); + IMGUI_API void SuspendLayout(); + IMGUI_API void ResumeLayout(); + +} // namespace ImGui + + +#endif // #ifndef IMGUI_DISABLE diff --git a/imgui_stacklayout_internal.h b/imgui_stacklayout_internal.h new file mode 100644 index 000000000000..4a5e9bd42813 --- /dev/null +++ b/imgui_stacklayout_internal.h @@ -0,0 +1,30 @@ +// dear imgui, v1.86 WIP +// (stack layout headers) + +/* + +Index of this file: +// [SECTION] Stack Layout Internal API + +*/ + +#pragma once + +#include "imgui_internal.h" +#include "imgui_stacklayout.h" + +#ifndef IMGUI_DISABLE + +//----------------------------------------------------------------------------- +// [SECTION] Stack Layout Internal API +//----------------------------------------------------------------------------- + +namespace ImGuiInternal +{ + IMGUI_API ImGuiLayoutType GetCurrentLayoutType(ImGuiID window_id); + IMGUI_API void UpdateItemRect(ImGuiID window_id, const ImVec2& min, const ImVec2& max); + +} // namespace ImGuiInternal + + +#endif // #ifndef IMGUI_DISABLE