From a961958bdc7cab43c85d0f561259f964176e4e70 Mon Sep 17 00:00:00 2001 From: thedmd Date: Fri, 3 May 2019 11:59:34 +0200 Subject: [PATCH] Cherry-pick 99d4b0f44 from thedmd/imgui/layouts --- imgui.cpp | 804 +++++++++++++++++++++++++++++++++++++++++++++-- imgui.h | 15 + imgui_demo.cpp | 133 ++++++++ imgui_internal.h | 77 +++++ 4 files changed, 1005 insertions(+), 24 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 6700a2ae..d1a97284 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -69,6 +69,7 @@ CODE // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) // [SECTION] ERROR CHECKING // [SECTION] LAYOUT +// [SECTION] STACK LAYOUT // [SECTION] SCROLLING // [SECTION] TOOLTIPS // [SECTION] POPUPS @@ -933,6 +934,26 @@ static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window); static void NavRestoreLayer(ImGuiNavLayer layer); static int FindWindowFocusIndex(ImGuiWindow* window); +// 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 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 void BalanceChildLayouts(ImGuiLayout& layout); +static ImVec2 CalculateLayoutSize(ImGuiLayout& layout, bool collapse_springs); +static ImGuiLayoutItem* GenerateLayoutItem(ImGuiLayout& layout, ImGuiLayoutItemType type); +static float CalculateLayoutItemAlignmentOffset(ImGuiLayout& layout, ImGuiLayoutItem& item); +static void TranslateLayoutItem(ImGuiLayoutItem& item, const ImVec2& offset); +static void BeginLayoutItem(ImGuiLayout& layout); +static void EndLayoutItem(ImGuiLayout& layout); +static void AddLayoutSpring(ImGuiLayout& layout, float weight, float spacing); +static void SignedIndent(float indent); + // Error Checking static void ErrorCheckNewFrameSanityChecks(); static void ErrorCheckEndFrameSanityChecks(); @@ -1038,6 +1059,7 @@ ImGuiStyle::ImGuiStyle() ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar GrabMinSize = 10.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. @@ -2599,6 +2621,7 @@ static const ImGuiStyleVarInfo GStyleVarInfo[] = { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, LayoutAlign) }, // ImGuiStyleVar_LayoutAlign }; static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx) @@ -3001,6 +3024,12 @@ ImGuiWindow::~ImGuiWindow() IM_ASSERT(DrawList == &DrawListInst); IM_DELETE(Name); ColumnsStorage.clear_destruct(); + + for (int i = 0; i < DC.Layouts.Data.Size; i++) + { + ImGuiLayout* layout = (ImGuiLayout*)DC.Layouts.Data[i].val_p; + IM_DELETE(layout); + } } ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) @@ -6849,6 +6878,13 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) else SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, title_bar_rect); + // Mark all layouts as dead. They may be revived in this frame. + for (int i = 0; i < window->DC.Layouts.Data.Size; i++) + { + ImGuiLayout* layout = (ImGuiLayout*)window->DC.Layouts.Data[i].val_p; + layout->Live = false; + } + #ifdef IMGUI_ENABLE_TEST_ENGINE if (!(window->Flags & ImGuiWindowFlags_NoTitleBar)) IMGUI_TEST_ENGINE_ITEM_ADD(g.LastItemData.Rect, g.LastItemData.ID); @@ -8009,6 +8045,8 @@ void ImGuiStackSizes::CompareWithCurrentState() // Window stacks // NOT checking: DC.ItemWidth, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin) IM_ASSERT(SizeOfIDStack == window->IDStack.Size && "PushID/PopID or TreeNode/TreePop Mismatch!"); + IM_ASSERT(0 == window->DC.LayoutStack.Size && (!window->DC.LayoutStack.Size || window->DC.LayoutStack.back()->Type == ImGuiLayoutType_Horizontal) && "BeginHorizontal/EndHorizontal Mismatch!"); + IM_ASSERT(0 == window->DC.LayoutStack.Size && (!window->DC.LayoutStack.Size || window->DC.LayoutStack.back()->Type == ImGuiLayoutType_Vertical) && "BeginVertical/EndVertical Mismatch!"); // Global stacks // For color, style and font stacks there is an incentive to use Push/Begin/Pop/.../End patterns, so we relax our checks a little to allow them. @@ -8064,30 +8102,54 @@ void ImGui::ItemSize(const ImVec2& size, float text_baseline_y) if (window->SkipItems) 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. - const float offset_to_match_baseline_y = (text_baseline_y >= 0) ? ImMax(0.0f, window->DC.CurrLineTextBaseOffset - text_baseline_y) : 0.0f; - const float line_height = ImMax(window->DC.CurrLineSize.y, size.y + offset_to_match_baseline_y); - - // 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 + size.x; - window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y; - window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); // Next line - window->DC.CursorPos.y = IM_FLOOR(window->DC.CursorPos.y + line_height + g.Style.ItemSpacing.y); // Next line - window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); - 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.y = line_height; - window->DC.CurrLineSize.y = 0.0f; - window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y); - window->DC.CurrLineTextBaseOffset = 0.0f; - - // Horizontal layout mode - if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) - SameLine(); + ImGuiLayoutType layout_type = window->DC.LayoutType; + if (window->DC.CurrentLayout) + layout_type = window->DC.CurrentLayout->Type; + + if (layout_type == ImGuiLayoutType_Vertical) + { + // 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. + const float offset_to_match_baseline_y = (text_baseline_y >= 0) ? ImMax(0.0f, window->DC.CurrLineTextBaseOffset - text_baseline_y) : 0.0f; + const float line_height = ImMax(window->DC.CurrLineSize.y, size.y + offset_to_match_baseline_y); + + // 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 + size.x; + window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y; + window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); // Next line + window->DC.CursorPos.y = IM_FLOOR(window->DC.CursorPos.y + line_height + g.Style.ItemSpacing.y); // Next line + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); + 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; + } + else + { + 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_FLOOR(window->DC.CursorPos.x + line_width + g.Style.ItemSpacing.x); + window->DC.CursorPos.y = IM_FLOOR(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; + } } void ImGui::ItemSize(const ImRect& bb, float text_baseline_y) @@ -8138,6 +8200,9 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu } g.NextItemData.Flags = ImGuiNextItemDataFlags_None; + if (window->DC.CurrentLayoutItem) + window->DC.CurrentLayoutItem->MeasuredBounds.Max = ImMax(window->DC.CurrentLayoutItem->MeasuredBounds.Max, bb.Max); + #ifdef IMGUI_ENABLE_TEST_ENGINE if (id != 0) IMGUI_TEST_ENGINE_ITEM_ADD(nav_bb_arg ? *nav_bb_arg : bb, id); @@ -8512,6 +8577,697 @@ void ImGui::EndGroup() } +//----------------------------------------------------------------------------- +// [SECTION] STACK LAYOUT +//----------------------------------------------------------------------------- + +static ImGuiLayout* ImGui::FindLayout(ImGuiID id, ImGuiLayoutType type) +{ + IM_ASSERT(type == ImGuiLayoutType_Horizontal || type == ImGuiLayoutType_Vertical); + + ImGuiWindow* window = GetCurrentWindow(); + ImGuiLayout* layout = (ImGuiLayout*)window->DC.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); + + ImGuiWindow* window = GetCurrentWindow(); + + ImGuiLayout* layout = IM_NEW(ImGuiLayout)(id, type); + layout->Size = size; + + window->DC.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); + + 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) +{ + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(window->DC.CurrentLayout); + IM_ASSERT(window->DC.CurrentLayout->Type == type); + + ImGuiLayout* layout = window->DC.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->DC.CurrentLayoutItem) + current_layout_item_max = ImMax(window->DC.CurrentLayoutItem->MeasuredBounds.Max, layout->StartPos + new_size); + + window->DC.CursorPos = layout->StartPos; + window->DC.CursorMaxPos = layout->StartCursorMaxPos; + ItemSize(new_size); + ItemAdd(ImRect(layout->StartPos, layout->StartPos + measured_size), 0); + + if (window->DC.CurrentLayoutItem) + window->DC.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) +{ + ImGuiWindow* window = GetCurrentWindow(); + + if (layout) + { + layout->Parent = window->DC.CurrentLayout; + if (layout->Parent != NULL) + layout->ParentItemIndex = layout->Parent->CurrentItemIndex; + if (window->DC.CurrentLayout) + { + layout->NextSibling = window->DC.CurrentLayout->FirstChild; + layout->FirstChild = NULL; + window->DC.CurrentLayout->FirstChild = layout; + } + else + { + layout->NextSibling = NULL; + layout->FirstChild = NULL; + } + } + + window->DC.LayoutStack.push_back(layout); + window->DC.CurrentLayout = layout; + window->DC.CurrentLayoutItem = NULL; +} + +static void ImGui::PopLayout(ImGuiLayout* layout) +{ + ImGuiWindow* window = GetCurrentWindow(); + + IM_ASSERT(!window->DC.LayoutStack.empty()); + IM_ASSERT(window->DC.LayoutStack.back() == layout); + + window->DC.LayoutStack.pop_back(); + + if (!window->DC.LayoutStack.empty()) + { + window->DC.CurrentLayout = window->DC.LayoutStack.back(); + window->DC.CurrentLayoutItem = &window->DC.CurrentLayout->Items[window->DC.CurrentLayout->CurrentItemIndex]; + } + else + { + window->DC.CurrentLayout = NULL; + window->DC.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 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)); + } + + g.CurrentWindow->DC.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); +} + +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*/) +{ + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(window->DC.CurrentLayout); + + AddLayoutSpring(*window->DC.CurrentLayout, weight, spacing); +} + +void ImGui::SuspendLayout() +{ + PushLayout(NULL); +} + +void ImGui::ResumeLayout() +{ + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(!window->DC.CurrentLayout); + IM_ASSERT(!window->DC.LayoutStack.empty()); + PopLayout(NULL); +} + + //----------------------------------------------------------------------------- // [SECTION] SCROLLING //----------------------------------------------------------------------------- diff --git a/imgui.h b/imgui.h index 99106b57..8f0b6f37 100644 --- a/imgui.h +++ b/imgui.h @@ -67,6 +67,7 @@ Index of this file: #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 // Define attributes of all API symbols declarations (e.g. for DLL under Windows) // IMGUI_API is used for core imgui functions, IMGUI_IMPL_API is used for the default backends files (imgui_impl_xxx.h) @@ -456,6 +457,18 @@ namespace ImGui IMGUI_API float GetFrameHeight(); // ~ FontSize + style.FramePadding.y * 2 IMGUI_API float GetFrameHeightWithSpacing(); // ~ FontSize + style.FramePadding.y * 2 + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of framed widgets) + 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(); + // ID stack/scopes // Read the FAQ (docs/FAQ.md or http://dearimgui.org/faq) for more details about how ID are handled in dear imgui. // - Those questions are answered and impacted by understanding of the ID stack system: @@ -1598,6 +1611,7 @@ enum ImGuiStyleVar_ ImGuiStyleVar_TabRounding, // float TabRounding ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign + ImGuiStyleVar_LayoutAlign, // float LayoutAlign ImGuiStyleVar_COUNT }; @@ -1845,6 +1859,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. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 10f72839..53b98445 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3173,6 +3173,139 @@ static void ShowDemoWindowLayout() ImGui::TreePop(); } + + 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) + { + ImVec2 start_cursor_pos = ImGui::GetCursorScreenPos(); + ImGui::Spring(spring_weight); + ImVec2 end_cursor_pos = ImGui::GetCursorScreenPos(); + + if (!draw_springs) + return; + + if (spring_weight <= 0.0f) + return; + + if (fabsf(start_cursor_pos.x - end_cursor_pos.x) < 1.0f && fabsf(start_cursor_pos.y - end_cursor_pos.y) < 1.0f) + return; + + // Draw zig-zag + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 rect_min = ImGui::GetItemRectMin(); + ImVec2 rect_max = ImGui::GetItemRectMax(); + + draw_list->PushClipRect(rect_min, rect_max, true); + + float width = 0.0f; + ImVec2 direction, origin; + + if (horizontal) + { + width = rect_max.x - rect_min.x; + origin = ImVec2(floorf(rect_min.x), floorf(rect_min.y + (rect_max.y - rect_min.y) / 2)); + direction = ImVec2(1.0f, 0.0f); + } + else + { + width = rect_max.y - rect_min.y; + origin = ImVec2(floorf(rect_min.x + (rect_max.x - rect_min.x) / 2), floorf(rect_min.y)); + direction = ImVec2(0.0f, 1.0f); + } + + draw_list->AddRectFilled(rect_min, rect_max, ImColor(255, 128, 255, 40)); + + 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::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 = 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, item_spacing); + + 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(); + } } static void ShowDemoWindowPopups() diff --git a/imgui_internal.h b/imgui_internal.h index 28a5b458..359d1a77 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -153,6 +153,7 @@ struct ImGuiWindowSettings; // Storage for a window .ini settings (we ke // Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. typedef int ImGuiDataAuthority; // -> enum ImGuiDataAuthority_ // Enum: for storing the source authority (dock node vs window) of a field typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // Enum: Horizontal or vertical +typedef int ImGuiLayoutItemType; // -> enum ImGuiLayoutItemType_ // Enum: Item or Spring typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag() typedef int ImGuiItemAddFlags; // -> enum ImGuiItemAddFlags_ // Flags: for ItemAdd() typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for DC.LastItemStatusFlags @@ -888,6 +889,12 @@ enum ImGuiLayoutType_ ImGuiLayoutType_Vertical = 1 }; +enum ImGuiLayoutItemType_ +{ + ImGuiLayoutItemType_Item, + ImGuiLayoutItemType_Spring +}; + enum ImGuiLogType { ImGuiLogType_None = 0, @@ -1233,6 +1240,72 @@ struct ImGuiPtrOrIndex ImGuiPtrOrIndex(int index) { Ptr = NULL; Index = index; } }; +// 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); + } +}; + //----------------------------------------------------------------------------- // [SECTION] Columns support //----------------------------------------------------------------------------- @@ -2052,6 +2125,10 @@ struct IMGUI_API ImGuiWindowTempData int CurrentTableIdx; // Current table index (into g.Tables) ImGuiLayoutType LayoutType; ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() + ImGuiLayout* CurrentLayout; + ImGuiLayoutItem* CurrentLayoutItem; + ImVector LayoutStack; + ImGuiStorage Layouts; int FocusCounterRegular; // (Legacy Focus/Tabbing system) Sequential counter, start at -1 and increase as assigned via FocusableItemRegister() (FIXME-NAV: Needs redesign) int FocusCounterTabStop; // (Legacy Focus/Tabbing system) Same, but only count widgets which you can Tab through.