Skip to content

Commit e711e5c

Browse files
committed
Stack Layout implementation v2.1
1 parent f9cda1f commit e711e5c

7 files changed

+1479
-4
lines changed

imgui.cpp

+39-4
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,7 @@ ImGuiStyle::ImGuiStyle()
12641264
ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar
12651265
GrabMinSize = 12.0f; // Minimum width/height of a grab box for slider/scrollbar
12661266
GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
1267+
LayoutAlign = 0.5f; // Element alignment inside horizontal and vertical layouts (0.0f - left/top, 1.0f - right/bottom, 0.5f - center).
12671268
LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero.
12681269
TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.
12691270
TabBorderSize = 0.0f; // Thickness of border around tabs.
@@ -3271,6 +3272,7 @@ static const ImGuiDataVarInfo GStyleVarInfo[] =
32713272
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding
32723273
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize
32733274
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding
3275+
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, LayoutAlign) }, // ImGuiStyleVar_LayoutAlign
32743276
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding
32753277
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBorderSize) }, // ImGuiStyleVar_TabBorderSize
32763278
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize) }, // ImGuiStyleVar_TabBarBorderSize
@@ -10262,6 +10264,10 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu
1026210264
g.NextItemData.Flags = ImGuiNextItemDataFlags_None;
1026310265
g.NextItemData.ItemFlags = ImGuiItemFlags_None;
1026410266

10267+
#if IMGUI_HAS_STACK_LAYOUT
10268+
ImGuiInternal::UpdateItemRect(window->ID, bb.Min, bb.Max);
10269+
#endif
10270+
1026510271
#ifdef IMGUI_ENABLE_TEST_ENGINE
1026610272
if (id != 0)
1026710273
IMGUI_TEST_ENGINE_ITEM_ADD(id, g.LastItemData.NavRect, &g.LastItemData);
@@ -10345,6 +10351,38 @@ void ImGui::ItemSize(const ImVec2& size, float text_baseline_y)
1034510351
if (window->SkipItems)
1034610352
return;
1034710353

10354+
#if IMGUI_HAS_STACK_LAYOUT
10355+
ImGuiLayoutType layout_type = ImGuiInternal::GetCurrentLayoutType(window->ID);
10356+
#else
10357+
ImGuiLayoutType layout_type = window->DC.LayoutType;
10358+
#endif
10359+
10360+
//if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorPos, 3.0f, IM_COL32(255,255,0,255), 4); // [DEBUG] Widget position
10361+
10362+
// Stack Layouts: Handle horizontal case first to simplify merge in case code handling vertical changes.
10363+
if (layout_type == ImGuiLayoutType_Horizontal)
10364+
{
10365+
const float line_width = ImMax(window->DC.CurrLineSize.x, size.x);
10366+
10367+
// Always align ourselves on pixel boundaries
10368+
//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]
10369+
window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x;
10370+
window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y + size.y;
10371+
window->DC.CursorPos.x = IM_TRUNC(window->DC.CursorPos.x + line_width + g.Style.ItemSpacing.x);
10372+
window->DC.CursorPos.y = IM_TRUNC(window->DC.CursorPosPrevLine.y - size.y);
10373+
window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x - g.Style.ItemSpacing.x);
10374+
window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPosPrevLine.y);
10375+
//if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG]
10376+
10377+
window->DC.PrevLineSize.x = line_width;
10378+
window->DC.PrevLineSize.y = 0.0f;
10379+
window->DC.CurrLineSize.x = 0.0f;
10380+
window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y);
10381+
window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
10382+
window->DC.IsSameLine = window->DC.IsSetPos = false;
10383+
return;
10384+
}
10385+
1034810386
// We increase the height in this function to accommodate for baseline offset.
1034910387
// In theory we should be offsetting the starting position (window->DC.CursorPos), that will be the topic of a larger refactor,
1035010388
// but since ItemSize() is not yet an API that moves the cursor (to handle e.g. wrapping) enlarging the height has the same effect.
@@ -10363,15 +10401,12 @@ void ImGui::ItemSize(const ImVec2& size, float text_baseline_y)
1036310401
window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y);
1036410402
//if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG]
1036510403

10404+
window->DC.PrevLineSize.x = 0.0f;
1036610405
window->DC.PrevLineSize.y = line_height;
1036710406
window->DC.CurrLineSize.y = 0.0f;
1036810407
window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y);
1036910408
window->DC.CurrLineTextBaseOffset = 0.0f;
1037010409
window->DC.IsSameLine = window->DC.IsSetPos = false;
10371-
10372-
// Horizontal layout mode
10373-
if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
10374-
SameLine();
1037510410
}
1037610411
IM_MSVC_RUNTIME_CHECKS_RESTORE
1037710412

imgui.h

+7
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#define IMGUI_VERSION "1.91.0 WIP"
3131
#define IMGUI_VERSION_NUM 19097
3232
#define IMGUI_HAS_TABLE
33+
#define IMGUI_HAS_STACK_LAYOUT 1 // Stack-Layout PR #846
3334

3435
/*
3536
@@ -1708,6 +1709,7 @@ enum ImGuiStyleVar_
17081709
ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding
17091710
ImGuiStyleVar_GrabMinSize, // float GrabMinSize
17101711
ImGuiStyleVar_GrabRounding, // float GrabRounding
1712+
ImGuiStyleVar_LayoutAlign, // float LayoutAlign
17111713
ImGuiStyleVar_TabRounding, // float TabRounding
17121714
ImGuiStyleVar_TabBorderSize, // float TabBorderSize
17131715
ImGuiStyleVar_TabBarBorderSize, // float TabBarBorderSize
@@ -2149,6 +2151,7 @@ struct ImGuiStyle
21492151
float ScrollbarRounding; // Radius of grab corners for scrollbar.
21502152
float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar.
21512153
float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
2154+
float LayoutAlign; // Element alignment inside horizontal and vertical layouts (0.0f - left/top, 1.0f - right/bottom, 0.5f - center).
21522155
float LogSliderDeadzone; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero.
21532156
float TabRounding; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.
21542157
float TabBorderSize; // Thickness of border around tabs.
@@ -3626,6 +3629,10 @@ namespace ImGui
36263629
#pragma warning (pop)
36273630
#endif
36283631

3632+
#if IMGUI_HAS_STACK_LAYOUT
3633+
#include "imgui_stacklayout.h"
3634+
#endif
3635+
36293636
// Include imgui_user.h at the end of imgui.h
36303637
// May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included.
36313638
#ifdef IMGUI_INCLUDE_IMGUI_USER_H

imgui_demo.cpp

+150
Original file line numberDiff line numberDiff line change
@@ -4480,6 +4480,156 @@ static void ShowDemoWindowLayout()
44804480

44814481
ImGui::TreePop();
44824482
}
4483+
4484+
#if IMGUI_HAS_STACK_LAYOUT
4485+
IMGUI_DEMO_MARKER("Layout/Stack Layout");
4486+
if (ImGui::TreeNode("Stack Layout"))
4487+
{
4488+
static bool widget_a = true, widget_b = true, widget_c = true;
4489+
static bool spring_a = true, spring_ab = true, spring_bc = true, spring_c = true;
4490+
static bool minimize_width = false, minimize_height = true;
4491+
static bool horizontal = true, draw_springs = true;
4492+
static ImVec2 item_spacing = ImGui::GetStyle().ItemSpacing;
4493+
static float a_c_spring_weight = 0.0f;
4494+
static float ab_spring_weight = 0.5f;
4495+
static float alignment = 0.5f;
4496+
4497+
struct funcs
4498+
{
4499+
static void VisibleSpring(float spring_weight)
4500+
{
4501+
ImGui::Spring(spring_weight);
4502+
if (!draw_springs)
4503+
return;
4504+
4505+
ImVec2 rect_min = ImGui::GetItemRectMin();
4506+
ImVec2 rect_max = ImGui::GetItemRectMax();
4507+
4508+
ImVec2 rect_size = ImGui::GetItemRectSize();
4509+
if (rect_size.x <= 0.0f && rect_size.y <= 0.0f)
4510+
return;
4511+
4512+
// Draw zig-zag
4513+
float width = 0.0f, spacing = 0.0f;
4514+
ImVec2 direction, origin;
4515+
ImVec2 spacing_min, spring_max;
4516+
4517+
if (horizontal)
4518+
{
4519+
spacing = floorf(item_spacing.x);
4520+
width = rect_size.x - spacing;
4521+
origin = ImVec2(floorf(rect_min.x), floorf(rect_min.y + (rect_max.y - rect_min.y) / 2));
4522+
direction = ImVec2(1.0f, 0.0f);
4523+
spring_max = ImVec2(rect_min.x + width, rect_max.y);
4524+
spacing_min = ImVec2(rect_min.x + width, rect_min.y);
4525+
}
4526+
else
4527+
{
4528+
spacing = floorf(item_spacing.y);
4529+
width = rect_size.y - spacing;
4530+
origin = ImVec2(floorf(rect_min.x + (rect_max.x - rect_min.x) / 2), floorf(rect_min.y));
4531+
direction = ImVec2(0.0f, 1.0f);
4532+
spring_max = ImVec2(rect_max.x, rect_min.y + width);
4533+
spacing_min = ImVec2(rect_min.x, rect_min.y + width);
4534+
}
4535+
4536+
if (spring_weight <= 0.0f && spacing <= 0.0f)
4537+
return;
4538+
4539+
ImDrawList* draw_list = ImGui::GetWindowDrawList();
4540+
4541+
draw_list->PushClipRect(rect_min, rect_max, true);
4542+
4543+
draw_list->AddRectFilled(rect_min, spring_max, ImColor(80, 20, 80));
4544+
draw_list->AddRectFilled(spacing_min, rect_max, ImColor(80, 20, 20));
4545+
4546+
const float zig_zag_size = 3;
4547+
ImVec2 normal = ImVec2(-direction.y, direction.x);
4548+
4549+
draw_list->PathClear();
4550+
origin.x += 0.5f;
4551+
origin.y += 0.5f;
4552+
draw_list->PathLineTo(origin);
4553+
for (float x = zig_zag_size * 0.5f; x <= width; x += zig_zag_size)
4554+
{
4555+
ImVec2 p;
4556+
p.x = origin.x + direction.x * x + normal.x * zig_zag_size;
4557+
p.y = origin.y + direction.y * x + normal.y * zig_zag_size;
4558+
draw_list->PathLineTo(p);
4559+
normal = ImVec2(-normal.x, -normal.y);
4560+
}
4561+
draw_list->PathStroke(ImColor(255, 255, 255, 190), false, 1.0f);
4562+
4563+
draw_list->PopClipRect();
4564+
}
4565+
};
4566+
4567+
ImGui::Checkbox("Widget A", &widget_a); ImGui::SameLine();
4568+
ImGui::Checkbox("Widget B", &widget_b); ImGui::SameLine();
4569+
ImGui::Checkbox("Widget C", &widget_c);
4570+
ImGui::Checkbox("Spring A", &spring_a); ImGui::SameLine();
4571+
ImGui::Checkbox("Spring AB", &spring_ab); ImGui::SameLine();
4572+
ImGui::Checkbox("Spring BC", &spring_bc); ImGui::SameLine();
4573+
ImGui::Checkbox("Spring C", &spring_c);
4574+
ImGui::Checkbox("Horizontal", &horizontal); ImGui::SameLine();
4575+
ImGui::Checkbox("Minimize Width", &minimize_width); ImGui::SameLine();
4576+
ImGui::Checkbox("Minimize Height", &minimize_height);
4577+
ImGui::Checkbox("Draw Springs", &draw_springs); ImGui::SameLine();
4578+
ImGui::TextUnformatted(" "); ImGui::SameLine();
4579+
ImGui::ColorButton("- Spring", ImColor(80, 20, 80), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoPicker); ImGui::SameLine();
4580+
ImGui::TextUnformatted("Spring"); ImGui::SameLine();
4581+
ImGui::TextUnformatted(" "); ImGui::SameLine();
4582+
ImGui::ColorButton("- Spacing", ImColor(80, 20, 20), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoPicker); ImGui::SameLine();
4583+
ImGui::TextUnformatted("Item Spacing");
4584+
ImGui::DragFloat("Item Spacing", horizontal ? &item_spacing.x : &item_spacing.y, 0.1f, 0.0f, 50.0f);
4585+
ImGui::DragFloat("A & C Spring Weight", &a_c_spring_weight, 0.002f, 0.0f, 1.0f);
4586+
ImGui::DragFloat("AB Spring Weight", &ab_spring_weight, 0.002f, 0.0f, 1.0f);
4587+
if (ImGui::IsItemHovered()) ImGui::SetTooltip("BC Spring Weight = 1 - AB Spring Weight");
4588+
ImGui::DragFloat("Minor Axis Alignment", &alignment, 0.002f, 0.0f, 1.0f);
4589+
if (ImGui::IsItemHovered()) ImGui::SetTooltip("This is vertical alignment for horizontal layouts and horizontal alignment for vertical layouts.");
4590+
ImGui::Text("Layout widgets:");
4591+
ImGui::Text("| Spring A | Widget A | Spring AB | Widget B | Spring BC | Widget C | Spring C |");
4592+
4593+
ImGui::Spacing();
4594+
4595+
ImVec2 widget_size;
4596+
widget_size.x = floorf(ImGui::GetContentRegionAvail().x / 4);
4597+
widget_size.y = horizontal ? floorf(widget_size.x / 3) : widget_size.x;
4598+
4599+
ImVec2 small_widget_size = widget_size;
4600+
if (horizontal)
4601+
small_widget_size.y = floorf(small_widget_size.y / 2);
4602+
else
4603+
small_widget_size.x = floorf(small_widget_size.x / 2);
4604+
4605+
ImVec2 layout_size = ImVec2(widget_size.x * 4, widget_size.y * 4);
4606+
if (minimize_width) layout_size.x = 0.0f;
4607+
if (minimize_height) layout_size.y = 0.0f;
4608+
4609+
// Minor axis alignment can be set by style or directly in BeginHorizontal/BeginVertical
4610+
// Example:
4611+
// ImGui::PushStyleVar(ImGuiStyleVar_LayoutAlign, alignment);
4612+
4613+
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(floorf(item_spacing.x), floorf(item_spacing.y)));
4614+
4615+
if (horizontal) { ImGui::BeginHorizontal("h1", layout_size, alignment); } else { ImGui::BeginVertical("v1", layout_size, alignment); }
4616+
if (spring_a) { funcs::VisibleSpring(a_c_spring_weight); }
4617+
if (widget_a) { ImGui::Button("Widget A", widget_size); }
4618+
if (spring_ab) { funcs::VisibleSpring(ab_spring_weight); }
4619+
if (widget_b) { ImGui::Button("Widget B", small_widget_size); }
4620+
if (spring_bc) { funcs::VisibleSpring(1.0f - ab_spring_weight); }
4621+
if (widget_c) { ImGui::Button("Widget C", widget_size); }
4622+
if (spring_c) { funcs::VisibleSpring(a_c_spring_weight); }
4623+
if (horizontal) { ImGui::EndHorizontal(); } else { ImGui::EndVertical(); }
4624+
4625+
ImGui::PopStyleVar();
4626+
4627+
ImDrawList* draw_list = ImGui::GetWindowDrawList();
4628+
draw_list->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImGui::GetColorU32(ImGuiCol_Border));
4629+
4630+
ImGui::TreePop();
4631+
}
4632+
#endif // IMGUI_HAS_STACK_LAYOUT
44834633
}
44844634

44854635
//-----------------------------------------------------------------------------

imgui_internal.h

+9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Index of this file:
1111
// [SECTION] Forward declarations
1212
// [SECTION] Context pointer
1313
// [SECTION] STB libraries includes
14+
// [SECTION] Stack Layout includes
1415
// [SECTION] Macros
1516
// [SECTION] Generic helpers
1617
// [SECTION] ImDrawList support
@@ -215,6 +216,14 @@ namespace ImStb
215216

216217
} // namespace ImStb
217218

219+
//-------------------------------------------------------------------------
220+
// [SECTION] Stack Layout includes
221+
//-------------------------------------------------------------------------
222+
223+
#if IMGUI_HAS_STACK_LAYOUT
224+
# include "imgui_stacklayout_internal.h"
225+
#endif
226+
218227
//-----------------------------------------------------------------------------
219228
// [SECTION] Macros
220229
//-----------------------------------------------------------------------------

0 commit comments

Comments
 (0)