Skip to content

Commit

Permalink
Stack Layout implementation v2.1
Browse files Browse the repository at this point in the history
  • Loading branch information
thedmd committed Jan 22, 2024
1 parent 2dc85e6 commit 90d7f7e
Show file tree
Hide file tree
Showing 7 changed files with 1,399 additions and 4 deletions.
43 changes: 39 additions & 4 deletions imgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,7 @@ ImGuiStyle::ImGuiStyle()
ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar
GrabMinSize = 12.0f; // Minimum width/height of a grab box for slider/scrollbar
GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
LayoutAlign = 0.5f; // Element alignment inside horizontal and vertical layouts (0.0f - left/top, 1.0f - right/bottom, 0.5f - center).
LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero.
TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.
TabBorderSize = 0.0f; // Thickness of border around tabs.
Expand Down Expand Up @@ -3182,6 +3183,7 @@ static const ImGuiDataVarInfo GStyleVarInfo[] =
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) }, // ImGuiStyleVar_SeparatorTextAlign
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) }, // ImGuiStyleVar_SeparatorTextPadding
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, DockingSeparatorSize) }, // ImGuiStyleVar_DockingSeparatorSize
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, LayoutAlign) }, // ImGuiStyleVar_LayoutAlign
};

const ImGuiDataVarInfo* ImGui::GetStyleVarInfo(ImGuiStyleVar idx)
Expand Down Expand Up @@ -10347,6 +10349,38 @@ void ImGui::ItemSize(const ImVec2& size, float text_baseline_y)
if (window->SkipItems)
return;

#if IMGUI_HAS_STACK_LAYOUT
ImGuiLayoutType layout_type = ImGuiInternal::GetCurrentLayoutType(window->ID);
#else
ImGuiLayoutType layout_type = window->DC.LayoutType;
#endif

//if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorPos, 3.0f, IM_COL32(255,255,0,255), 4); // [DEBUG] Widget position

// Stack Layouts: Handle horizontal case first to simplify merge in case code handling vertical changes.
if (layout_type == ImGuiLayoutType_Horizontal)
{
const float line_width = ImMax(window->DC.CurrLineSize.x, size.x);

// Always align ourselves on pixel boundaries
//if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG]
window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x;
window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y + size.y;
window->DC.CursorPos.x = IM_TRUNC(window->DC.CursorPos.x + line_width + g.Style.ItemSpacing.x);
window->DC.CursorPos.y = IM_TRUNC(window->DC.CursorPosPrevLine.y - size.y);
window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x - g.Style.ItemSpacing.x);
window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPosPrevLine.y);
//if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG]

window->DC.PrevLineSize.x = line_width;
window->DC.PrevLineSize.y = 0.0f;
window->DC.CurrLineSize.x = 0.0f;
window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y);
window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
window->DC.IsSameLine = window->DC.IsSetPos = false;
return;
}

// We increase the height in this function to accommodate for baseline offset.
// In theory we should be offsetting the starting position (window->DC.CursorPos), that will be the topic of a larger refactor,
// but since ItemSize() is not yet an API that moves the cursor (to handle e.g. wrapping) enlarging the height has the same effect.
Expand All @@ -10365,15 +10399,12 @@ void ImGui::ItemSize(const ImVec2& size, float text_baseline_y)
window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y);
//if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG]

window->DC.PrevLineSize.x = 0.0f;
window->DC.PrevLineSize.y = line_height;
window->DC.CurrLineSize.y = 0.0f;
window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y);
window->DC.CurrLineTextBaseOffset = 0.0f;
window->DC.IsSameLine = window->DC.IsSetPos = false;

// Horizontal layout mode
if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
SameLine();
}

// Declare item bounding box for clipping and interaction.
Expand Down Expand Up @@ -10423,6 +10454,10 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu
g.NextItemData.Flags = ImGuiNextItemDataFlags_None;
g.NextItemData.ItemFlags = ImGuiItemFlags_None;

#if IMGUI_HAS_STACK_LAYOUT
ImGuiInternal::UpdateItemRect(window->ID, bb.Min, bb.Max);
#endif

#ifdef IMGUI_ENABLE_TEST_ENGINE
if (id != 0)
IMGUI_TEST_ENGINE_ITEM_ADD(id, g.LastItemData.NavRect, &g.LastItemData);
Expand Down
7 changes: 7 additions & 0 deletions imgui.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#define IMGUI_HAS_TABLE
#define IMGUI_HAS_VIEWPORT // Viewport WIP branch
#define IMGUI_HAS_DOCK // Docking WIP branch
#define IMGUI_HAS_STACK_LAYOUT 1 // Stack-Layout PR #846

/*
Expand Down Expand Up @@ -1654,6 +1655,7 @@ enum ImGuiStyleVar_
ImGuiStyleVar_SeparatorTextAlign, // ImVec2 SeparatorTextAlign
ImGuiStyleVar_SeparatorTextPadding,// ImVec2 SeparatorTextPadding
ImGuiStyleVar_DockingSeparatorSize,// float DockingSeparatorSize
ImGuiStyleVar_LayoutAlign, // float LayoutAlign
ImGuiStyleVar_COUNT
};

Expand Down Expand Up @@ -2076,6 +2078,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.
Expand Down Expand Up @@ -3557,6 +3560,10 @@ enum ImGuiModFlags_ { ImGuiModFlags_None = 0, ImGuiModFlags_Ctrl = ImGuiMod_Ctrl
#pragma warning (pop)
#endif

#if IMGUI_HAS_STACK_LAYOUT
#include "imgui_stacklayout.h"
#endif

// Include imgui_user.h at the end of imgui.h
// May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included.
#ifdef IMGUI_INCLUDE_IMGUI_USER_H
Expand Down
150 changes: 150 additions & 0 deletions imgui_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3653,6 +3653,156 @@ static void ShowDemoWindowLayout()

ImGui::TreePop();
}

#if IMGUI_HAS_STACK_LAYOUT
IMGUI_DEMO_MARKER("Layout/Stack Layout");
if (ImGui::TreeNode("Stack Layout"))
{
static bool widget_a = true, widget_b = true, widget_c = true;
static bool spring_a = true, spring_ab = true, spring_bc = true, spring_c = true;
static bool minimize_width = false, minimize_height = true;
static bool horizontal = true, draw_springs = true;
static ImVec2 item_spacing = ImGui::GetStyle().ItemSpacing;
static float a_c_spring_weight = 0.0f;
static float ab_spring_weight = 0.5f;
static float alignment = 0.5f;

struct funcs
{
static void VisibleSpring(float spring_weight)
{
ImGui::Spring(spring_weight);
if (!draw_springs)
return;

ImVec2 rect_min = ImGui::GetItemRectMin();
ImVec2 rect_max = ImGui::GetItemRectMax();

ImVec2 rect_size = ImGui::GetItemRectSize();
if (rect_size.x <= 0.0f && rect_size.y <= 0.0f)
return;

// Draw zig-zag
float width = 0.0f, spacing = 0.0f;
ImVec2 direction, origin;
ImVec2 spacing_min, spring_max;

if (horizontal)
{
spacing = floorf(item_spacing.x);
width = rect_size.x - spacing;
origin = ImVec2(floorf(rect_min.x), floorf(rect_min.y + (rect_max.y - rect_min.y) / 2));
direction = ImVec2(1.0f, 0.0f);
spring_max = ImVec2(rect_min.x + width, rect_max.y);
spacing_min = ImVec2(rect_min.x + width, rect_min.y);
}
else
{
spacing = floorf(item_spacing.y);
width = rect_size.y - spacing;
origin = ImVec2(floorf(rect_min.x + (rect_max.x - rect_min.x) / 2), floorf(rect_min.y));
direction = ImVec2(0.0f, 1.0f);
spring_max = ImVec2(rect_max.x, rect_min.y + width);
spacing_min = ImVec2(rect_min.x, rect_min.y + width);
}

if (spring_weight <= 0.0f && spacing <= 0.0f)
return;

ImDrawList* draw_list = ImGui::GetWindowDrawList();

draw_list->PushClipRect(rect_min, rect_max, true);

draw_list->AddRectFilled(rect_min, spring_max, ImColor(80, 20, 80));
draw_list->AddRectFilled(spacing_min, rect_max, ImColor(80, 20, 20));

const float zig_zag_size = 3;
ImVec2 normal = ImVec2(-direction.y, direction.x);

draw_list->PathClear();
origin.x += 0.5f;
origin.y += 0.5f;
draw_list->PathLineTo(origin);
for (float x = zig_zag_size * 0.5f; x <= width; x += zig_zag_size)
{
ImVec2 p;
p.x = origin.x + direction.x * x + normal.x * zig_zag_size;
p.y = origin.y + direction.y * x + normal.y * zig_zag_size;
draw_list->PathLineTo(p);
normal = ImVec2(-normal.x, -normal.y);
}
draw_list->PathStroke(ImColor(255, 255, 255, 190), false, 1.0f);

draw_list->PopClipRect();
}
};

ImGui::Checkbox("Widget A", &widget_a); ImGui::SameLine();
ImGui::Checkbox("Widget B", &widget_b); ImGui::SameLine();
ImGui::Checkbox("Widget C", &widget_c);
ImGui::Checkbox("Spring A", &spring_a); ImGui::SameLine();
ImGui::Checkbox("Spring AB", &spring_ab); ImGui::SameLine();
ImGui::Checkbox("Spring BC", &spring_bc); ImGui::SameLine();
ImGui::Checkbox("Spring C", &spring_c);
ImGui::Checkbox("Horizontal", &horizontal); ImGui::SameLine();
ImGui::Checkbox("Minimize Width", &minimize_width); ImGui::SameLine();
ImGui::Checkbox("Minimize Height", &minimize_height);
ImGui::Checkbox("Draw Springs", &draw_springs); ImGui::SameLine();
ImGui::TextUnformatted(" "); ImGui::SameLine();
ImGui::ColorButton("- Spring", ImColor(80, 20, 80), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoPicker); ImGui::SameLine();
ImGui::TextUnformatted("Spring"); ImGui::SameLine();
ImGui::TextUnformatted(" "); ImGui::SameLine();
ImGui::ColorButton("- Spacing", ImColor(80, 20, 20), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoPicker); ImGui::SameLine();
ImGui::TextUnformatted("Item Spacing");
ImGui::DragFloat("Item Spacing", horizontal ? &item_spacing.x : &item_spacing.y, 0.1f, 0.0f, 50.0f);
ImGui::DragFloat("A & C Spring Weight", &a_c_spring_weight, 0.002f, 0.0f, 1.0f);
ImGui::DragFloat("AB Spring Weight", &ab_spring_weight, 0.002f, 0.0f, 1.0f);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("BC Spring Weight = 1 - AB Spring Weight");
ImGui::DragFloat("Minor Axis Alignment", &alignment, 0.002f, 0.0f, 1.0f);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("This is vertical alignment for horizontal layouts and horizontal alignment for vertical layouts.");
ImGui::Text("Layout widgets:");
ImGui::Text("| Spring A | Widget A | Spring AB | Widget B | Spring BC | Widget C | Spring C |");

ImGui::Spacing();

ImVec2 widget_size;
widget_size.x = floorf(ImGui::GetContentRegionAvail().x / 4);
widget_size.y = horizontal ? floorf(widget_size.x / 3) : widget_size.x;

ImVec2 small_widget_size = widget_size;
if (horizontal)
small_widget_size.y = floorf(small_widget_size.y / 2);
else
small_widget_size.x = floorf(small_widget_size.x / 2);

ImVec2 layout_size = ImVec2(widget_size.x * 4, widget_size.y * 4);
if (minimize_width) layout_size.x = 0.0f;
if (minimize_height) layout_size.y = 0.0f;

// Minor axis alignment can be set by style or directly in BeginHorizontal/BeginVertical
// Example:
// ImGui::PushStyleVar(ImGuiStyleVar_LayoutAlign, alignment);

ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(floorf(item_spacing.x), floorf(item_spacing.y)));

if (horizontal) { ImGui::BeginHorizontal("h1", layout_size, alignment); } else { ImGui::BeginVertical("v1", layout_size, alignment); }
if (spring_a) { funcs::VisibleSpring(a_c_spring_weight); }
if (widget_a) { ImGui::Button("Widget A", widget_size); }
if (spring_ab) { funcs::VisibleSpring(ab_spring_weight); }
if (widget_b) { ImGui::Button("Widget B", small_widget_size); }
if (spring_bc) { funcs::VisibleSpring(1.0f - ab_spring_weight); }
if (widget_c) { ImGui::Button("Widget C", widget_size); }
if (spring_c) { funcs::VisibleSpring(a_c_spring_weight); }
if (horizontal) { ImGui::EndHorizontal(); } else { ImGui::EndVertical(); }

ImGui::PopStyleVar();

ImDrawList* draw_list = ImGui::GetWindowDrawList();
draw_list->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImGui::GetColorU32(ImGuiCol_Border));

ImGui::TreePop();
}
#endif // IMGUI_HAS_STACK_LAYOUT
}

static void ShowDemoWindowPopups()
Expand Down
9 changes: 9 additions & 0 deletions imgui_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Index of this file:
// [SECTION] Forward declarations
// [SECTION] Context pointer
// [SECTION] STB libraries includes
// [SECTION] Stack Layout includes
// [SECTION] Macros
// [SECTION] Generic helpers
// [SECTION] ImDrawList support
Expand Down Expand Up @@ -212,6 +213,14 @@ namespace ImStb

} // namespace ImStb

//-------------------------------------------------------------------------
// [SECTION] Stack Layout includes
//-------------------------------------------------------------------------

#if IMGUI_HAS_STACK_LAYOUT
# include "imgui_stacklayout_internal.h"
#endif

//-----------------------------------------------------------------------------
// [SECTION] Macros
//-----------------------------------------------------------------------------
Expand Down
Loading

0 comments on commit 90d7f7e

Please sign in to comment.