// 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. ImDrawListSplitter Splitter; ImGuiLayout(ImGuiID id, ImGuiLayoutType type) { Id = id; Type = type; Live = false; Size = CurrentSize = MinimumSize = MeasuredSize = ImVec2(0, 0); CurrentItemIndex = 0; ParentItemIndex = 0; Parent = FirstChild = NextSibling = NULL; Align = -1.0f; Indent = 0.0f; StartPos = ImVec2(0, 0); StartCursorMaxPos = ImVec2(0, 0); } }; struct ImGuiLayoutWindowState { ImGuiWindow* Window; ImGuiLayout* CurrentLayout; ImGuiLayoutItem* CurrentLayoutItem; ImVector LayoutStack; ImGuiStorage Layouts; ImGuiLayoutWindowState() { Window = NULL; CurrentLayout = NULL; CurrentLayoutItem = NULL; } }; struct ImGuiLayoutState { ImGuiStorage LayoutWindowStates; ImGuiContext* Context; ImGuiID ContextID; ImGuiID NewFramePreID; ImGuiID EndFramePreID; ImGuiID ShutdownHookID; ImGuiLayoutState() { Context = NULL; ContextID = 0; NewFramePreID = 0; EndFramePreID = 0; ShutdownHookID = 0; } }; //----------------------------------------------------------------------------- // [SECTION] Stack Layout: Context forward declarations //----------------------------------------------------------------------------- static ImGuiStorage GStackLayoutStates; // Context management/hooks static ImGuiID GetContextID(ImGuiContext* context); // FIXME: ImGuiContext probably should have their own ID assigned static ImGuiLayoutState* GetCurrentLayoutState(); static ImGuiLayoutState* GetLayoutState(ImGuiContext* context); static ImGuiLayoutState* CreateLayoutState(ImGuiID context_id, ImGuiContext* context); static void StackLayout_NewFramePreCallback(ImGuiContext* ctx, ImGuiContextHook* hook); static void StackLayout_EndFramePreCallback(ImGuiContext* ctx, ImGuiContextHook* hook); static void StackLayout_ShutdownCallback(ImGuiContext* ctx, ImGuiContextHook* hook); static ImGuiLayoutWindowState* GetWindowLayoutState(ImGuiID window_id, ImGuiWindow* window = NULL); static ImGuiLayoutWindowState* GetCurrentWindowLayoutState(); static void WindowLayoutState_OnNewFrame(ImGuiLayoutWindowState* state); static void WindowLayoutState_OnEndFrame(ImGuiLayoutWindowState* state); static void WindowLayoutState_OnShutdown(ImGuiLayoutWindowState* state); //----------------------------------------------------------------------------- // [SECTION] Stack Layout: Internal code forward declarations //----------------------------------------------------------------------------- namespace ImGui { // Stack Layout static ImGuiLayout* FindLayout(ImGuiID id, ImGuiLayoutType type); static ImGuiLayout* CreateNewLayout(ImGuiID id, ImGuiLayoutType type, ImVec2 size); static void BeginLayout(ImGuiID id, ImGuiLayoutType type, ImVec2 size, float align); static void EndLayout(ImGuiLayoutType type); static ImVec2 CalculateLayoutSize(ImGuiLayout& layout, bool collapse_springs); static void PushLayout(ImGuiLayout* layout); static void PopLayout(ImGuiLayout* layout); static void BalanceLayoutSprings(ImGuiLayout& layout); static ImVec2 BalanceLayoutItemAlignment(ImGuiLayout& layout, ImGuiLayoutItem& item); static void BalanceLayoutItemsAlignment(ImGuiLayout& layout); static bool HasAnyNonZeroSpring(ImGuiLayout& layout); static void BalanceChildLayouts(ImGuiLayout& layout); static void BeginLayoutClipRect(ImGuiLayout& layout); static void EndLayoutClipRect(ImGuiLayout& layout); static void ApplyLayoutClipRect(ImGuiLayout& layout); static void MergeLayoutSplitters(ImGuiLayout& layout); static ImGuiLayoutItem* GenerateLayoutItem(ImGuiLayout& layout, ImGuiLayoutItemType type); static float CalculateLayoutItemAlignmentOffset(ImGuiLayout& layout, ImGuiLayoutItem& item); static void TranslateLayoutItem(ImGuiLayoutItem& item, const ImVec2& offset); static void SignedIndent(float indent); static void BeginLayoutItem(ImGuiLayout& layout); static void EndLayoutItem(ImGuiLayout& layout); static void AddLayoutSpring(ImGuiLayout& layout, float weight, float spacing); } //----------------------------------------------------------------------------- // [SECTION] Stack Layout: Context //----------------------------------------------------------------------------- static ImGuiID GetContextID(ImGuiContext* context) { // Hash pointer to ImGuiContext which is unique return ImHashData(&context, sizeof(context)); } static ImGuiLayoutState* GetCurrentLayoutState() { return GetLayoutState(ImGui::GetCurrentContext()); } static ImGuiLayoutState* GetLayoutState(ImGuiContext* context) { if (context == NULL) return NULL; ImGuiID context_id = GetContextID(context); ImGuiLayoutState* state = (ImGuiLayoutState*)GStackLayoutStates.GetVoidPtr(context_id); if (state == NULL) state = CreateLayoutState(context_id, context); return state; } static ImGuiLayoutState* CreateLayoutState(ImGuiID context_id, ImGuiContext* context) { ImGuiLayoutState* state = IM_NEW(ImGuiLayoutState); state->Context = context; state->ContextID = context_id; ImGuiContextHook new_frame_pre_hook; new_frame_pre_hook.Type = ImGuiContextHookType_NewFramePre; new_frame_pre_hook.UserData = state; new_frame_pre_hook.Callback = StackLayout_NewFramePreCallback; state->NewFramePreID = ImGui::AddContextHook(context, &new_frame_pre_hook); ImGuiContextHook end_frame_pre_hook; end_frame_pre_hook.Type = ImGuiContextHookType_EndFramePre; end_frame_pre_hook.UserData = state; end_frame_pre_hook.Callback = StackLayout_EndFramePreCallback; state->EndFramePreID = ImGui::AddContextHook(context, &end_frame_pre_hook); ImGuiContextHook shutdown_hook; shutdown_hook.Type = ImGuiContextHookType_Shutdown; shutdown_hook.UserData = state; shutdown_hook.Callback = StackLayout_ShutdownCallback; state->ShutdownHookID = ImGui::AddContextHook(context, &shutdown_hook); GStackLayoutStates.SetVoidPtr(context_id, state); return state; } static void StackLayout_NewFramePreCallback(ImGuiContext* ctx, ImGuiContextHook* hook) { ImGuiLayoutState* layout_state = (ImGuiLayoutState*)hook->UserData; for (int i = 0; i < layout_state->LayoutWindowStates.Data.Size; ++i) { ImGuiLayoutWindowState* layout_window_state = (ImGuiLayoutWindowState*)layout_state->LayoutWindowStates.Data[i].val_p; WindowLayoutState_OnNewFrame(layout_window_state); } } static void StackLayout_EndFramePreCallback(ImGuiContext* ctx, ImGuiContextHook* hook) { ImGuiLayoutState* layout_state = (ImGuiLayoutState*)hook->UserData; for (int i = 0; i < layout_state->LayoutWindowStates.Data.Size; ++i) { ImGuiLayoutWindowState* layout_window_state = (ImGuiLayoutWindowState*)layout_state->LayoutWindowStates.Data[i].val_p; WindowLayoutState_OnEndFrame(layout_window_state); } } static void StackLayout_ShutdownCallback(ImGuiContext* ctx, ImGuiContextHook* hook) { ImGuiLayoutState* layout_state = (ImGuiLayoutState*)hook->UserData; for (int i = 0; i < layout_state->LayoutWindowStates.Data.Size; ++i) { ImGuiLayoutWindowState* layout_window_state = (ImGuiLayoutWindowState*)layout_state->LayoutWindowStates.Data[i].val_p; WindowLayoutState_OnShutdown(layout_window_state); IM_DELETE(layout_window_state); } GStackLayoutStates.SetVoidPtr(layout_state->ContextID, nullptr); IM_DELETE(layout_state); } static ImGuiLayoutWindowState* GetWindowLayoutState(ImGuiID window_id, ImGuiWindow* window) { ImGuiLayoutState* layout_state = GetCurrentLayoutState(); if (layout_state == NULL) return NULL; ImGuiLayoutWindowState* window_layout_state = (ImGuiLayoutWindowState*)layout_state->LayoutWindowStates.GetVoidPtr(window_id); if (window_layout_state == NULL) { if (window == NULL) { window = ImGui::FindWindowByID(window_id); IM_ASSERT(window && "GetWindowLayoutState called with invalid Window ID."); } window_layout_state = IM_NEW(ImGuiLayoutWindowState); window_layout_state->Window = window; layout_state->LayoutWindowStates.SetVoidPtr(window_id, window_layout_state); } return window_layout_state; } static ImGuiLayoutWindowState* GetCurrentWindowLayoutState() { ImGuiWindow* current_window = ImGui::GetCurrentWindow(); if (current_window == NULL) return NULL; return GetWindowLayoutState(current_window->ID, current_window); } static void WindowLayoutState_OnNewFrame(ImGuiLayoutWindowState* state) { // Mark all layouts as dead. They may be revived in this frame. for (int i = 0; i < state->Layouts.Data.Size; i++) { ImGuiLayout* layout = (ImGuiLayout*)state->Layouts.Data[i].val_p; layout->Live = false; } } static void WindowLayoutState_OnEndFrame(ImGuiLayoutWindowState* state) { // Check stacks (like ImGuiStackSizes::CompareWithCurrentState() does) IM_ASSERT(0 == state->LayoutStack.Size && (!state->LayoutStack.Size || state->LayoutStack.back()->Type == ImGuiLayoutType_Horizontal) && "BeginHorizontal/EndHorizontal Mismatch!"); IM_ASSERT(0 == state->LayoutStack.Size && (!state->LayoutStack.Size || state->LayoutStack.back()->Type == ImGuiLayoutType_Vertical) && "BeginVertical/EndVertical Mismatch!"); } static void WindowLayoutState_OnShutdown(ImGuiLayoutWindowState* state) { for (int i = 0; i < state->Layouts.Data.Size; i++) { ImGuiLayout* layout = (ImGuiLayout*)state->Layouts.Data[i].val_p; IM_DELETE(layout); } } //----------------------------------------------------------------------------- // [SECTION] Stack Layout: Internal code //----------------------------------------------------------------------------- static ImGuiLayout* ImGui::FindLayout(ImGuiID id, ImGuiLayoutType type) { IM_ASSERT(type == ImGuiLayoutType_Horizontal || type == ImGuiLayoutType_Vertical); ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState(); ImGuiLayout* layout = (ImGuiLayout*)window_state->Layouts.GetVoidPtr(id); if (!layout) return NULL; if (layout->Type != type) { layout->Type = type; layout->MinimumSize = ImVec2(0.0f, 0.0f); layout->Items.clear(); } return layout; } static ImGuiLayout* ImGui::CreateNewLayout(ImGuiID id, ImGuiLayoutType type, ImVec2 size) { IM_ASSERT(type == ImGuiLayoutType_Horizontal || type == ImGuiLayoutType_Vertical); ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState(); ImGuiLayout* layout = IM_NEW(ImGuiLayout)(id, type); layout->Size = size; window_state->Layouts.SetVoidPtr(id, layout); return layout; } static void ImGui::BeginLayout(ImGuiID id, ImGuiLayoutType type, ImVec2 size, float align) { ImGuiWindow* window = GetCurrentWindow(); PushID(id); // Find or create ImGuiLayout* layout = FindLayout(id, type); if (!layout) layout = CreateNewLayout(id, type, size); IM_ASSERT(!layout->Live && "BeginHorizontal/BeginVertical with same ID is already live in this frame. Please use PushID() to make ID's unique or rename layout."); layout->Live = true; PushLayout(layout); if (layout->Size.x != size.x || layout->Size.y != size.y) layout->Size = size; if (align < 0.0f) layout->Align = -1.0f; else layout->Align = ImClamp(align, 0.0f, 1.0f); // Start capture layout->CurrentItemIndex = 0; layout->CurrentSize.x = layout->Size.x > 0.0f ? layout->Size.x : layout->MinimumSize.x; layout->CurrentSize.y = layout->Size.y > 0.0f ? layout->Size.y : layout->MinimumSize.y; layout->StartPos = window->DC.CursorPos; layout->StartCursorMaxPos = window->DC.CursorMaxPos; BeginLayoutClipRect(*layout); if (type == ImGuiLayoutType_Vertical) { // Push empty item to recalculate cursor position. PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); Dummy(ImVec2(0.0f, 0.0f)); PopStyleVar(); // Indent horizontal position to match edge of the layout. layout->Indent = layout->StartPos.x - window->DC.CursorPos.x; SignedIndent(layout->Indent); } BeginLayoutItem(*layout); } static void ImGui::EndLayout(ImGuiLayoutType type) { ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState(); IM_ASSERT(window_state->CurrentLayout); IM_ASSERT(window_state->CurrentLayout->Type == type); IM_UNUSED(type); ImGuiLayout* layout = window_state->CurrentLayout; EndLayoutItem(*layout); if (layout->CurrentItemIndex < layout->Items.Size) layout->Items.resize(layout->CurrentItemIndex); if (layout->Type == ImGuiLayoutType_Vertical) SignedIndent(-layout->Indent); PopLayout(layout); const bool auto_width = layout->Size.x <= 0.0f; const bool auto_height = layout->Size.y <= 0.0f; ImVec2 new_size = layout->Size; if (auto_width) new_size.x = layout->CurrentSize.x; if (auto_height) new_size.y = layout->CurrentSize.y; ImVec2 new_minimum_size = CalculateLayoutSize(*layout, true); if (new_minimum_size.x != layout->MinimumSize.x || new_minimum_size.y != layout->MinimumSize.y) { layout->MinimumSize = new_minimum_size; // Shrink if (auto_width) new_size.x = new_minimum_size.x; if (auto_height) new_size.y = new_minimum_size.y; } if (!auto_width) new_size.x = layout->Size.x; if (!auto_height) new_size.y = layout->Size.y; layout->CurrentSize = new_size; ImVec2 measured_size = new_size; if ((auto_width || auto_height) && layout->Parent) { if (layout->Type == ImGuiLayoutType_Horizontal && auto_width && layout->Parent->CurrentSize.x > 0) layout->CurrentSize.x = layout->Parent->CurrentSize.x; else if (layout->Type == ImGuiLayoutType_Vertical && auto_height && layout->Parent->CurrentSize.y > 0) layout->CurrentSize.y = layout->Parent->CurrentSize.y; BalanceLayoutSprings(*layout); measured_size = layout->CurrentSize; } layout->CurrentSize = new_size; layout->MeasuredSize = measured_size; PopID(); ImVec2 current_layout_item_max = ImVec2(0.0f, 0.0f); if (window_state->CurrentLayoutItem) current_layout_item_max = ImMax(window_state->CurrentLayoutItem->MeasuredBounds.Max, layout->StartPos + new_size); window_state->Window->DC.CursorPos = layout->StartPos; window_state->Window->DC.CursorMaxPos = layout->StartCursorMaxPos; ItemSize(new_size); ItemAdd(ImRect(layout->StartPos, layout->StartPos + measured_size), 0); if (window_state->CurrentLayoutItem) window_state->CurrentLayoutItem->MeasuredBounds.Max = current_layout_item_max; if (layout->Parent == NULL) BalanceChildLayouts(*layout); EndLayoutClipRect(*layout); //window->DrawList->AddRect(layout->StartPos, layout->StartPos + measured_size, IM_COL32(0,255,0,255)); // [DEBUG] //window->DrawList->AddRect(window->DC.LastItemRect.Min, window->DC.LastItemRect.Max, IM_COL32(255,255,0,255)); // [DEBUG] } static ImVec2 ImGui::CalculateLayoutSize(ImGuiLayout& layout, bool collapse_springs) { ImVec2 bounds = ImVec2(0.0f, 0.0f); if (layout.Type == ImGuiLayoutType_Vertical) { for (int i = 0; i < layout.Items.Size; i++) { ImGuiLayoutItem& item = layout.Items[i]; ImVec2 item_size = item.MeasuredBounds.GetSize(); if (item.Type == ImGuiLayoutItemType_Item) { bounds.x = ImMax(bounds.x, item_size.x); bounds.y += item_size.y; } else { bounds.y += ImFloor(item.SpringSpacing); if (!collapse_springs) bounds.y += item.SpringSize; } } } else { for (int i = 0; i < layout.Items.Size; i++) { ImGuiLayoutItem& item = layout.Items[i]; ImVec2 item_size = item.MeasuredBounds.GetSize(); if (item.Type == ImGuiLayoutItemType_Item) { bounds.x += item_size.x; bounds.y = ImMax(bounds.y, item_size.y); } else { bounds.x += ImFloor(item.SpringSpacing); if (!collapse_springs) bounds.x += item.SpringSize; } } } return bounds; } static void ImGui::PushLayout(ImGuiLayout* layout) { ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState(); if (layout) { layout->Parent = window_state->CurrentLayout; if (layout->Parent != NULL) layout->ParentItemIndex = layout->Parent->CurrentItemIndex; if (window_state->CurrentLayout) { layout->NextSibling = window_state->CurrentLayout->FirstChild; layout->FirstChild = NULL; window_state->CurrentLayout->FirstChild = layout; } else { layout->NextSibling = NULL; layout->FirstChild = NULL; } } window_state->LayoutStack.push_back(layout); window_state->CurrentLayout = layout; window_state->CurrentLayoutItem = NULL; } static void ImGui::PopLayout(ImGuiLayout* layout) { ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState(); IM_ASSERT(!window_state->LayoutStack.empty()); IM_ASSERT(window_state->LayoutStack.back() == layout); IM_UNUSED(layout); window_state->LayoutStack.pop_back(); if (!window_state->LayoutStack.empty()) { window_state->CurrentLayout = window_state->LayoutStack.back(); window_state->CurrentLayoutItem = &window_state->CurrentLayout->Items[window_state->CurrentLayout->CurrentItemIndex]; } else { window_state->CurrentLayout = NULL; window_state->CurrentLayoutItem = NULL; } } static void ImGui::BalanceLayoutSprings(ImGuiLayout& layout) { // Accumulate amount of occupied space and springs weights float total_spring_weight = 0.0f; int last_spring_item_index = -1; for (int i = 0; i < layout.Items.Size; i++) { ImGuiLayoutItem& item = layout.Items[i]; if (item.Type == ImGuiLayoutItemType_Spring) { total_spring_weight += item.SpringWeight; last_spring_item_index = i; } } // Determine occupied space and available space depending on layout type const bool is_horizontal = (layout.Type == ImGuiLayoutType_Horizontal); const bool is_auto_sized = ((is_horizontal ? layout.Size.x : layout.Size.y) <= 0.0f) && (layout.Parent == NULL); const float occupied_space = is_horizontal ? layout.MinimumSize.x : layout.MinimumSize.y; const float available_space = is_auto_sized ? occupied_space : (is_horizontal ? layout.CurrentSize.x : layout.CurrentSize.y); const float free_space = ImMax(available_space - occupied_space, 0.0f); float span_start = 0.0f; float current_weight = 0.0f; for (int i = 0; i < layout.Items.Size; i++) { ImGuiLayoutItem& item = layout.Items[i]; if (item.Type != ImGuiLayoutItemType_Spring) continue; float last_spring_size = item.SpringSize; if (free_space > 0.0f && total_spring_weight > 0.0f) { float next_weight = current_weight + item.SpringWeight; float span_end = ImFloor((i == last_spring_item_index) ? free_space : (free_space * next_weight / total_spring_weight)); float spring_size = span_end - span_start; item.SpringSize = spring_size; span_start = span_end; current_weight = next_weight; } else { item.SpringSize = 0.0f; } // If spring changed its size, fix positioning of following items to avoid one frame visual bugs. if (last_spring_size != item.SpringSize) { float difference = item.SpringSize - last_spring_size; ImVec2 offset = is_horizontal ? ImVec2(difference, 0.0f) : ImVec2(0.0f, difference); item.MeasuredBounds.Max += offset; for (int j = i + 1; j < layout.Items.Size; j++) { ImGuiLayoutItem& translated_item = layout.Items[j]; TranslateLayoutItem(translated_item, offset); translated_item.MeasuredBounds.Min += offset; translated_item.MeasuredBounds.Max += offset; } } } } static ImVec2 ImGui::BalanceLayoutItemAlignment(ImGuiLayout& layout, ImGuiLayoutItem& item) { // Fixup item alignment if necessary. ImVec2 position_correction = ImVec2(0.0f, 0.0f); if (item.CurrentAlign > 0.0f) { float item_align_offset = CalculateLayoutItemAlignmentOffset(layout, item); if (item.CurrentAlignOffset != item_align_offset) { float offset = item_align_offset - item.CurrentAlignOffset; if (layout.Type == ImGuiLayoutType_Horizontal) position_correction.y = offset; else position_correction.x = offset; TranslateLayoutItem(item, position_correction); item.CurrentAlignOffset = item_align_offset; } } return position_correction; } static void ImGui::BalanceLayoutItemsAlignment(ImGuiLayout& layout) { for (int i = 0; i < layout.Items.Size; ++i) { ImGuiLayoutItem& item = layout.Items[i]; BalanceLayoutItemAlignment(layout, item); } } static bool ImGui::HasAnyNonZeroSpring(ImGuiLayout& layout) { for (int i = 0; i < layout.Items.Size; ++i) { ImGuiLayoutItem& item = layout.Items[i]; if (item.Type != ImGuiLayoutItemType_Spring) continue; if (item.SpringWeight > 0) return true; } return false; } static void ImGui::BalanceChildLayouts(ImGuiLayout& layout) { for (ImGuiLayout* child = layout.FirstChild; child != NULL; child = child->NextSibling) { //ImVec2 child_layout_size = child->CurrentSize; // Propagate layout size down to child layouts. // // TODO: Distribution assume inner layout is only // element inside parent item and assigns // all available space to it. // // Investigate how to split space between // adjacent layouts. // // Investigate how to measure non-layout items // to treat them as fixed size blocks. // if (child->Type == ImGuiLayoutType_Horizontal && child->Size.x <= 0.0f) child->CurrentSize.x = layout.CurrentSize.x; else if (child->Type == ImGuiLayoutType_Vertical && child->Size.y <= 0.0f) child->CurrentSize.y = layout.CurrentSize.y; BalanceChildLayouts(*child); //child->CurrentSize = child_layout_size; if (HasAnyNonZeroSpring(*child)) { // Expand item measured bounds to make alignment correct. ImGuiLayoutItem& item = layout.Items[child->ParentItemIndex]; if (child->Type == ImGuiLayoutType_Horizontal && child->Size.x <= 0.0f) item.MeasuredBounds.Max.x = ImMax(item.MeasuredBounds.Max.x, item.MeasuredBounds.Min.x + layout.CurrentSize.x); else if (child->Type == ImGuiLayoutType_Vertical && child->Size.y <= 0.0f) item.MeasuredBounds.Max.y = ImMax(item.MeasuredBounds.Max.y, item.MeasuredBounds.Min.y + layout.CurrentSize.y); } } BalanceLayoutSprings(layout); BalanceLayoutItemsAlignment(layout); } static void ImGui::BeginLayoutClipRect(ImGuiLayout& layout) { ImGuiWindow* window = GetCurrentWindow(); // Use splitter to collect draw commands in separate channel, // so we can clip them to the layout bounds. layout.Splitter.Split(window->DrawList, 2); layout.Splitter.SetCurrentChannel(window->DrawList, 1); // Clip to layout bounds, unrestricted and not measured bounds span // all the way to the edge of the window. ImVec2 clip_rect_min = layout.StartPos; ImVec2 clip_rect_max; clip_rect_max.x = layout.Size.x > 0.0f ? layout.StartPos.x + layout.Size.x : FLT_MAX; clip_rect_max.y = layout.Size.y > 0.0f ? layout.StartPos.y + layout.Size.y : FLT_MAX; PushClipRect(clip_rect_min, clip_rect_max, true); } static void ImGui::EndLayoutClipRect(ImGuiLayout& layout) { PopClipRect(); if (layout.Parent != NULL) return; ApplyLayoutClipRect(layout); MergeLayoutSplitters(layout); } static void ImGui::ApplyLayoutClipRect(ImGuiLayout& layout) { for (ImGuiLayout* child = layout.FirstChild; child != NULL; child = child->NextSibling) ApplyLayoutClipRect(*child); ImGuiWindow* window = GetCurrentWindow(); ImVec4 current_clip_rect; current_clip_rect.x = layout.StartPos.x; current_clip_rect.y = layout.StartPos.y; current_clip_rect.z = layout.StartPos.x + layout.MeasuredSize.x; current_clip_rect.w = layout.StartPos.y + layout.MeasuredSize.y; layout.Splitter.SetCurrentChannel(window->DrawList, 0); for (ImDrawCmd& cmd : layout.Splitter._Channels[1]._CmdBuffer) { if (cmd.ClipRect.x < current_clip_rect.x) cmd.ClipRect.x = current_clip_rect.x; if (cmd.ClipRect.y < current_clip_rect.y) cmd.ClipRect.y = current_clip_rect.y; if (cmd.ClipRect.z > current_clip_rect.z) cmd.ClipRect.z = current_clip_rect.z; if (cmd.ClipRect.w > current_clip_rect.w) cmd.ClipRect.w = current_clip_rect.w; } //GetForegroundDrawList()->AddRect(layout.StartPos, layout.StartPos + layout.MeasuredSize, IM_COL32(255,0,0,128)); // [DEBUG] } static void ImGui::MergeLayoutSplitters(ImGuiLayout& layout) { for (ImGuiLayout* child = layout.FirstChild; child != NULL; child = child->NextSibling) MergeLayoutSplitters(*child); ImGuiWindow* window = GetCurrentWindow(); layout.Splitter.Merge(window->DrawList); } static ImGuiLayoutItem* ImGui::GenerateLayoutItem(ImGuiLayout& layout, ImGuiLayoutItemType type) { ImGuiContext& g = *GImGui; IM_ASSERT(layout.CurrentItemIndex <= layout.Items.Size); if (layout.CurrentItemIndex < layout.Items.Size) { ImGuiLayoutItem& item = layout.Items[layout.CurrentItemIndex]; if (item.Type != type) item = ImGuiLayoutItem(type); } else { layout.Items.push_back(ImGuiLayoutItem(type)); } ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState(); window_state->CurrentLayoutItem = &layout.Items[layout.CurrentItemIndex]; return &layout.Items[layout.CurrentItemIndex]; } // Calculate how many pixels from top/left layout edge item need to be moved to match // layout alignment. static float ImGui::CalculateLayoutItemAlignmentOffset(ImGuiLayout& layout, ImGuiLayoutItem& item) { if (item.CurrentAlign <= 0.0f) return 0.0f; ImVec2 item_size = item.MeasuredBounds.GetSize(); float layout_extent = (layout.Type == ImGuiLayoutType_Horizontal) ? layout.CurrentSize.y : layout.CurrentSize.x; float item_extent = (layout.Type == ImGuiLayoutType_Horizontal) ? item_size.y : item_size.x; if (item_extent <= 0/* || layout_extent <= item_extent*/) return 0.0f; float align_offset = ImFloor(item.CurrentAlign * (layout_extent - item_extent)); return align_offset; } static void ImGui::TranslateLayoutItem(ImGuiLayoutItem& item, const ImVec2& offset) { if ((offset.x == 0.0f && offset.y == 0.0f) || (item.VertexIndexBegin == item.VertexIndexEnd)) return; //IMGUI_DEBUG_LOG("TranslateLayoutItem by %f,%f\n", offset.x, offset.y); ImDrawList* draw_list = GetWindowDrawList(); ImDrawVert* begin = draw_list->VtxBuffer.Data + item.VertexIndexBegin; ImDrawVert* end = draw_list->VtxBuffer.Data + item.VertexIndexEnd; for (ImDrawVert* vtx = begin; vtx < end; ++vtx) { vtx->pos.x += offset.x; vtx->pos.y += offset.y; } } static void ImGui::SignedIndent(float indent) { if (indent > 0.0f) Indent(indent); else if (indent < 0.0f) Unindent(-indent); } static void ImGui::BeginLayoutItem(ImGuiLayout& layout) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiLayoutItem& item = *GenerateLayoutItem(layout, ImGuiLayoutItemType_Item); item.CurrentAlign = layout.Align; if (item.CurrentAlign < 0.0f) item.CurrentAlign = ImClamp(g.Style.LayoutAlign, 0.0f, 1.0f); // Align item according to data from previous frame. // If layout changes in current frame alignment will // be corrected in EndLayout() to it visualy coherent. item.CurrentAlignOffset = CalculateLayoutItemAlignmentOffset(layout, item); if (item.CurrentAlign > 0.0f) { if (layout.Type == ImGuiLayoutType_Horizontal) { window->DC.CursorPos.y += item.CurrentAlignOffset; } else { float new_position = window->DC.CursorPos.x + item.CurrentAlignOffset; // Make placement behave like in horizontal case when next // widget is placed at very same Y position. This indent // make sure for vertical layout placed widgets has same X position. SignedIndent(item.CurrentAlignOffset); window->DC.CursorPos.x = new_position; } } item.MeasuredBounds.Min = item.MeasuredBounds.Max = window->DC.CursorPos; item.VertexIndexBegin = item.VertexIndexEnd = window->DrawList->_VtxCurrentIdx; } static void ImGui::EndLayoutItem(ImGuiLayout& layout) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(layout.CurrentItemIndex < layout.Items.Size); ImGuiLayoutItem& item = layout.Items[layout.CurrentItemIndex]; ImDrawList* draw_list = window->DrawList; item.VertexIndexEnd = draw_list->_VtxCurrentIdx; if (item.CurrentAlign > 0.0f && layout.Type == ImGuiLayoutType_Vertical) SignedIndent(-item.CurrentAlignOffset); // Fixup item alignment in case item size changed in current frame. ImVec2 position_correction = BalanceLayoutItemAlignment(layout, item); item.MeasuredBounds.Min += position_correction; item.MeasuredBounds.Max += position_correction; if (layout.Type == ImGuiLayoutType_Horizontal) window->DC.CursorPos.y = layout.StartPos.y; else window->DC.CursorPos.x = layout.StartPos.x; layout.CurrentItemIndex++; } static void ImGui::AddLayoutSpring(ImGuiLayout& layout, float weight, float spacing) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiLayoutItem* previous_item = &layout.Items[layout.CurrentItemIndex]; // Undo item padding, spring should consume all space between items. if (layout.Type == ImGuiLayoutType_Horizontal) window->DC.CursorPos.x = previous_item->MeasuredBounds.Max.x; else window->DC.CursorPos.y = previous_item->MeasuredBounds.Max.y; previous_item = NULL; // may be invalid after call to GenerateLayoutItem() EndLayoutItem(layout); ImGuiLayoutItem* spring_item = GenerateLayoutItem(layout, ImGuiLayoutItemType_Spring); spring_item->MeasuredBounds.Min = spring_item->MeasuredBounds.Max = window->DC.CursorPos; if (weight < 0.0f) weight = 0.0f; if (spring_item->SpringWeight != weight) spring_item->SpringWeight = weight; if (spacing < 0.0f) { ImVec2 style_spacing = g.Style.ItemSpacing; if (layout.Type == ImGuiLayoutType_Horizontal) spacing = style_spacing.x; else spacing = style_spacing.y; } if (spring_item->SpringSpacing != spacing) spring_item->SpringSpacing = spacing; if (spring_item->SpringSize > 0.0f || spacing > 0.0f) { ImVec2 spring_size, spring_spacing; if (layout.Type == ImGuiLayoutType_Horizontal) { spring_spacing = ImVec2(0.0f, g.Style.ItemSpacing.y); spring_size = ImVec2(spacing + spring_item->SpringSize, layout.CurrentSize.y); } else { spring_spacing = ImVec2(g.Style.ItemSpacing.x, 0.0f); spring_size = ImVec2(layout.CurrentSize.x, spacing + spring_item->SpringSize); } PushStyleVar(ImGuiStyleVar_ItemSpacing, ImFloor(spring_spacing)); Dummy(ImFloor(spring_size)); PopStyleVar(); } layout.CurrentItemIndex++; BeginLayoutItem(layout); } //----------------------------------------------------------------------------- // [SECTION] Stack Layout: Internal API //----------------------------------------------------------------------------- ImGuiLayoutType ImGuiInternal::GetCurrentLayoutType(ImGuiID window_id) { ImGuiLayoutWindowState* state = GetWindowLayoutState(window_id); ImGuiLayoutType layout_type = state->Window->DC.LayoutType; if (state->CurrentLayout) layout_type = state->CurrentLayout->Type; return layout_type; } void ImGuiInternal::UpdateItemRect(ImGuiID window_id, const ImVec2& min, const ImVec2& max) { ImGuiLayoutWindowState* state = GetWindowLayoutState(window_id); if (state->CurrentLayoutItem) state->CurrentLayoutItem->MeasuredBounds.Max = ImMax(state->CurrentLayoutItem->MeasuredBounds.Max, max); IM_UNUSED(min); } //----------------------------------------------------------------------------- // [SECTION] Stack Layout: Public API //----------------------------------------------------------------------------- void ImGui::BeginHorizontal(const char* str_id, const ImVec2& size/* = ImVec2(0, 0)*/, float align/* = -1*/) { ImGuiWindow* window = GetCurrentWindow(); BeginLayout(window->GetID(str_id), ImGuiLayoutType_Horizontal, size, align); } void ImGui::BeginHorizontal(const void* ptr_id, const ImVec2& size/* = ImVec2(0, 0)*/, float align/* = -1*/) { ImGuiWindow* window = GetCurrentWindow(); BeginLayout(window->GetID(ptr_id), ImGuiLayoutType_Horizontal, size, align); } void ImGui::BeginHorizontal(int id, const ImVec2& size/* = ImVec2(0, 0)*/, float align/* = -1*/) { ImGuiWindow* window = GetCurrentWindow(); BeginLayout(window->GetID((void*)(intptr_t)id), ImGuiLayoutType_Horizontal, size, align); } void ImGui::EndHorizontal() { EndLayout(ImGuiLayoutType_Horizontal); } void ImGui::BeginVertical(const char* str_id, const ImVec2& size/* = ImVec2(0, 0)*/, float align/* = -1*/) { ImGuiWindow* window = GetCurrentWindow(); BeginLayout(window->GetID(str_id), ImGuiLayoutType_Vertical, size, align); } void ImGui::BeginVertical(const void* ptr_id, const ImVec2& size/* = ImVec2(0, 0)*/, float align/* = -1*/) { ImGuiWindow* window = GetCurrentWindow(); BeginLayout(window->GetID(ptr_id), ImGuiLayoutType_Vertical, size, align); } void ImGui::BeginVertical(int id, const ImVec2& size/* = ImVec2(0, 0)*/, float align/* = -1*/) { ImGuiWindow* window = GetCurrentWindow(); BeginLayout(window->GetID((void*)(intptr_t)id), ImGuiLayoutType_Vertical, size, align); } void ImGui::EndVertical() { EndLayout(ImGuiLayoutType_Vertical); } // Inserts spring separator in layout // weight <= 0 : spring will always have zero size // weight > 0 : power of current spring // spacing < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0 // spacing >= 0 : enforce spacing amount void ImGui::Spring(float weight/* = 1.0f*/, float spacing/* = -1.0f*/) { ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState(); IM_ASSERT(window_state->CurrentLayout); AddLayoutSpring(*window_state->CurrentLayout, weight, spacing); } void ImGui::SuspendLayout() { PushLayout(NULL); } void ImGui::ResumeLayout() { ImGuiLayoutWindowState* window_state = GetCurrentWindowLayoutState(); IM_ASSERT(!window_state->CurrentLayout); IM_ASSERT(!window_state->LayoutStack.empty()); IM_UNUSED(window_state); PopLayout(NULL); } //----------------------------------------------------------------------------- #endif // #ifndef IMGUI_DISABLE