1
0
mirror of https://github.com/wolfpld/tracy.git synced 2025-03-20 07:40:02 +08:00

Update imgui to 1.72.

This commit is contained in:
Bartosz Taudul
2019-07-30 22:53:52 +02:00
parent ca3571fd2b
commit 526f3a55bc
10 changed files with 1205 additions and 865 deletions
+473 -43
View File
@@ -1,4 +1,4 @@
// dear imgui, v1.71
// dear imgui, v1.72
// (widgets code)
/*
@@ -24,6 +24,7 @@ Index of this file:
// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
// [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc.
*/
@@ -63,10 +64,9 @@ Index of this file:
#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.
#endif
#elif defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
#if __GNUC__ >= 8
#pragma GCC diagnostic ignored "-Wclass-memaccess" // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
#endif
#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
#endif
//-------------------------------------------------------------------------
@@ -626,8 +626,6 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags
flags |= ImGuiButtonFlags_Repeat;
bool hovered, held;
bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
if (pressed)
MarkItemEdited(id);
// Render
const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
@@ -896,13 +894,13 @@ void ImGui::Scrollbar(ImGuiAxis axis)
ImRect bb;
if (axis == ImGuiAxis_X)
{
bb.Min = ImVec2(inner_rect.Min.x, outer_rect.Max.y - border_size - scrollbar_size);
bb.Min = ImVec2(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size));
bb.Max = ImVec2(inner_rect.Max.x, outer_rect.Max.y);
rounding_corners |= ImDrawCornerFlags_BotLeft;
}
else
{
bb.Min = ImVec2(outer_rect.Max.x - border_size - scrollbar_size, inner_rect.Min.y);
bb.Min = ImVec2(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y);
bb.Max = ImVec2(outer_rect.Max.x, window->InnerRect.Max.y);
rounding_corners |= ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0;
}
@@ -1002,10 +1000,17 @@ bool ImGui::Checkbox(const char* label, bool* v)
const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
RenderNavHighlight(total_bb, id);
RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
if (*v)
ImU32 check_col = GetColorU32(ImGuiCol_CheckMark);
if (window->DC.ItemFlags & ImGuiItemFlags_MixedValue)
{
// Undocumented tristate/mixed/indeterminate checkbox (#2644)
ImVec2 pad(ImMax(1.0f, (float)(int)(square_sz / 3.6f)), ImMax(1.0f, (float)(int)(square_sz / 3.6f)));
window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);
}
else if (*v)
{
const float pad = ImMax(1.0f, (float)(int)(square_sz / 6.0f));
RenderCheckMark(check_bb.Min + ImVec2(pad, pad), GetColorU32(ImGuiCol_CheckMark), square_sz - pad*2.0f);
RenderCheckMark(check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad*2.0f);
}
if (g.LogEnabled)
@@ -1083,6 +1088,7 @@ bool ImGui::RadioButton(const char* label, bool active)
return pressed;
}
// FIXME: This would work nicely if it was a public template, e.g. 'template<T> RadioButton(const char* label, T* v, T v_button)', but I'm not sure how we would expose it..
bool ImGui::RadioButton(const char* label, int* v, int v_button)
{
const bool pressed = RadioButton(label, *v == v_button);
@@ -1426,7 +1432,8 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF
ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
ImU32 text_col = GetColorU32(ImGuiCol_Text);
window->DrawList->AddRectFilled(ImVec2(value_x2, frame_bb.Min.y), frame_bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right);
RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down);
if (value_x2 + arrow_size - style.FramePadding.x <= frame_bb.Max.x)
RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f);
}
RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
@@ -3631,8 +3638,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_Delete) && !is_readonly) { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
@@ -3647,7 +3654,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
}
state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
}
else if (IsKeyPressedMap(ImGuiKey_Enter))
else if (IsKeyPressedMap(ImGuiKey_Enter) || IsKeyPressedMap(ImGuiKey_KeyPadEnter))
{
bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
@@ -3987,9 +3994,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
else if (cursor_offset.y - size.y >= scroll_y)
scroll_y = cursor_offset.y - size.y;
draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag
draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag
draw_window->Scroll.y = scroll_y;
draw_pos.y = draw_window->DC.CursorPos.y;
}
state->CursorFollow = false;
@@ -4130,8 +4136,9 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
ImGuiContext& g = *GImGui;
const ImGuiStyle& style = g.Style;
const float square_sz = GetFrameHeight();
const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
const float w_items_all = CalcItemWidth() - w_extra;
const float w_full = CalcItemWidth();
const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
const float w_inputs = w_full - w_button;
const char* label_display_end = FindRenderedTextEnd(label);
g.NextItemData.ClearFlags();
@@ -4175,11 +4182,15 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
bool value_changed = false;
bool value_changed_as_float = false;
const ImVec2 pos = window->DC.CursorPos;
const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f;
window->DC.CursorPos.x = pos.x + inputs_offset_x;
if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
{
// RGB/HSV 0..255 Sliders
const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
const float w_item_one = ImMax(1.0f, (float)(int)((w_inputs - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
const float w_item_last = ImMax(1.0f, (float)(int)(w_inputs - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
static const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
@@ -4223,7 +4234,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255), ImClamp(i[3],0,255));
else
ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255));
SetNextItemWidth(w_items_all);
SetNextItemWidth(w_inputs);
if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
{
value_changed = true;
@@ -4243,8 +4254,8 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
ImGuiWindow* picker_active_window = NULL;
if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
{
if (!(flags & ImGuiColorEditFlags_NoInputs))
SameLine(0, style.ItemInnerSpacing.x);
const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x;
window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y);
const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
if (ColorButton("##ColorButton", col_v4, flags))
@@ -4278,7 +4289,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
{
SameLine(0, style.ItemInnerSpacing.x);
window->DC.CursorPos = ImVec2(pos.x + w_full + style.ItemInnerSpacing.x, pos.y + style.FramePadding.y);
TextEx(label, label_display_end);
}
@@ -4827,9 +4838,6 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl
if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
if (pressed)
MarkItemEdited(id);
return pressed;
}
@@ -4983,7 +4991,6 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl
// - TreeNodeBehavior() [Internal]
// - TreePush()
// - TreePop()
// - TreeAdvanceToLabelPos()
// - GetTreeNodeToLabelSpacing()
// - SetNextItemOpen()
// - CollapsingHeader()
@@ -5231,13 +5238,13 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
// Render
const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
const ImU32 text_col = GetColorU32(ImGuiCol_Text);
const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y);
ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin;
if (display_frame)
{
// Framed type
const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding);
RenderNavHighlight(frame_bb, id, nav_highlight_flags);
RenderArrow(window->DrawList, frame_bb.Min + ImVec2(padding.x, text_base_offset_y), text_col, is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
@@ -5262,6 +5269,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
// Unframed typed for tree nodes
if (hovered || selected)
{
const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false);
RenderNavHighlight(frame_bb, id, nav_highlight_flags);
}
@@ -5324,12 +5332,6 @@ void ImGui::TreePop()
PopID();
}
void ImGui::TreeAdvanceToLabelPos()
{
ImGuiContext& g = *GImGui;
g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing();
}
// Horizontal distance preceding label when using TreeNode() or Bullet()
float ImGui::GetTreeNodeToLabelSpacing()
{
@@ -5488,6 +5490,8 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
// Render
if (held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld))
hovered = true;
if (hovered || selected)
{
const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
@@ -5931,6 +5935,10 @@ void ImGui::EndMainMenuBar()
End();
}
// FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere..
// Currently the main responsibility of this function being to setup clip-rect + horizontal layout + menu navigation layer.
// Ideally we also want this to be responsible for claiming space out of the main window scrolling rectangle, in which case ImGuiWindowFlags_MenuBar will become unnecessary.
// Then later the same system could be used for multiple menu-bars, scrollbars, side-bars.
bool ImGui::BeginMenuBar()
{
ImGuiWindow* window = GetCurrentWindow();
@@ -5940,7 +5948,7 @@ bool ImGui::BeginMenuBar()
return false;
IM_ASSERT(!window->DC.MenuBarAppending);
BeginGroup(); // Backup position on layer 0
BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore
PushID("##menubar");
// We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect.
@@ -6269,18 +6277,18 @@ static int IMGUI_CDECL TabItemComparerByVisibleOffset(const void* lhs, const voi
return (int)(a->Offset - b->Offset);
}
static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiTabBarRef& ref)
static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref)
{
ImGuiContext& g = *GImGui;
return ref.Ptr ? ref.Ptr : g.TabBars.GetByIndex(ref.IndexInMainPool);
return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index);
}
static ImGuiTabBarRef GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar)
static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar)
{
ImGuiContext& g = *GImGui;
if (g.TabBars.Contains(tab_bar))
return ImGuiTabBarRef(g.TabBars.GetIndex(tab_bar));
return ImGuiTabBarRef(tab_bar);
return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar));
return ImGuiPtrOrIndex(tab_bar);
}
bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
@@ -6339,7 +6347,7 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG
window->DC.CursorPos.x = tab_bar->BarRect.Min.x;
// Draw separator
const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_Tab);
const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive);
const float y = tab_bar->BarRect.Max.y - 1.0f;
{
const float separator_min_x = tab_bar->BarRect.Min.x - ImFloor(window->WindowPadding.x * 0.5f);
@@ -6635,7 +6643,7 @@ void ImGui::TabBarQueueChangeTabOrder(ImGuiTabBar* tab_bar, const ImGuiTabItem*
IM_ASSERT(dir == -1 || dir == +1);
IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
tab_bar->ReorderRequestTabId = tab->ID;
tab_bar->ReorderRequestDir = dir;
tab_bar->ReorderRequestDir = (ImS8)dir;
}
static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
@@ -7082,3 +7090,425 @@ bool ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb,
return close_button_pressed;
}
//-------------------------------------------------------------------------
// [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc.
// In the current version, Columns are very weak. Needs to be replaced with a more full-featured system.
//-------------------------------------------------------------------------
// - GetColumnIndex()
// - GetColumnCount()
// - GetColumnOffset()
// - GetColumnWidth()
// - SetColumnOffset()
// - SetColumnWidth()
// - PushColumnClipRect() [Internal]
// - PushColumnsBackground() [Internal]
// - PopColumnsBackground() [Internal]
// - FindOrCreateColumns() [Internal]
// - GetColumnsID() [Internal]
// - BeginColumns()
// - NextColumn()
// - EndColumns()
// - Columns()
//-------------------------------------------------------------------------
int ImGui::GetColumnIndex()
{
ImGuiWindow* window = GetCurrentWindowRead();
return window->DC.CurrentColumns ? window->DC.CurrentColumns->Current : 0;
}
int ImGui::GetColumnsCount()
{
ImGuiWindow* window = GetCurrentWindowRead();
return window->DC.CurrentColumns ? window->DC.CurrentColumns->Count : 1;
}
float ImGui::GetColumnOffsetFromNorm(const ImGuiColumns* columns, float offset_norm)
{
return offset_norm * (columns->OffMaxX - columns->OffMinX);
}
float ImGui::GetColumnNormFromOffset(const ImGuiColumns* columns, float offset)
{
return offset / (columns->OffMaxX - columns->OffMinX);
}
static const float COLUMNS_HIT_RECT_HALF_WIDTH = 4.0f;
static float GetDraggedColumnOffset(ImGuiColumns* columns, int column_index)
{
// Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing
// window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning.
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
IM_ASSERT(column_index > 0); // We are not supposed to drag column 0.
IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index));
float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + COLUMNS_HIT_RECT_HALF_WIDTH - window->Pos.x;
x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing);
if ((columns->Flags & ImGuiColumnsFlags_NoPreserveWidths))
x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing);
return x;
}
float ImGui::GetColumnOffset(int column_index)
{
ImGuiWindow* window = GetCurrentWindowRead();
ImGuiColumns* columns = window->DC.CurrentColumns;
if (columns == NULL)
return 0.0f;
if (column_index < 0)
column_index = columns->Current;
IM_ASSERT(column_index < columns->Columns.Size);
const float t = columns->Columns[column_index].OffsetNorm;
const float x_offset = ImLerp(columns->OffMinX, columns->OffMaxX, t);
return x_offset;
}
static float GetColumnWidthEx(ImGuiColumns* columns, int column_index, bool before_resize = false)
{
if (column_index < 0)
column_index = columns->Current;
float offset_norm;
if (before_resize)
offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize;
else
offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm;
return ImGui::GetColumnOffsetFromNorm(columns, offset_norm);
}
float ImGui::GetColumnWidth(int column_index)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiColumns* columns = window->DC.CurrentColumns;
if (columns == NULL)
return GetContentRegionAvail().x;
if (column_index < 0)
column_index = columns->Current;
return GetColumnOffsetFromNorm(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm);
}
void ImGui::SetColumnOffset(int column_index, float offset)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiColumns* columns = window->DC.CurrentColumns;
IM_ASSERT(columns != NULL);
if (column_index < 0)
column_index = columns->Current;
IM_ASSERT(column_index < columns->Columns.Size);
const bool preserve_width = !(columns->Flags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < columns->Count-1);
const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f;
if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow))
offset = ImMin(offset, columns->OffMaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index));
columns->Columns[column_index].OffsetNorm = GetColumnNormFromOffset(columns, offset - columns->OffMinX);
if (preserve_width)
SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width));
}
void ImGui::SetColumnWidth(int column_index, float width)
{
ImGuiWindow* window = GetCurrentWindowRead();
ImGuiColumns* columns = window->DC.CurrentColumns;
IM_ASSERT(columns != NULL);
if (column_index < 0)
column_index = columns->Current;
SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width);
}
void ImGui::PushColumnClipRect(int column_index)
{
ImGuiWindow* window = GetCurrentWindowRead();
ImGuiColumns* columns = window->DC.CurrentColumns;
if (column_index < 0)
column_index = columns->Current;
ImGuiColumnData* column = &columns->Columns[column_index];
PushClipRect(column->ClipRect.Min, column->ClipRect.Max, false);
}
// Get into the columns background draw command (which is generally the same draw command as before we called BeginColumns)
void ImGui::PushColumnsBackground()
{
ImGuiWindow* window = GetCurrentWindowRead();
ImGuiColumns* columns = window->DC.CurrentColumns;
if (columns->Count == 1)
return;
window->DrawList->ChannelsSetCurrent(0);
int cmd_size = window->DrawList->CmdBuffer.Size;
PushClipRect(columns->HostClipRect.Min, columns->HostClipRect.Max, false);
IM_UNUSED(cmd_size);
IM_ASSERT(cmd_size == window->DrawList->CmdBuffer.Size); // Being in channel 0 this should not have created an ImDrawCmd
}
void ImGui::PopColumnsBackground()
{
ImGuiWindow* window = GetCurrentWindowRead();
ImGuiColumns* columns = window->DC.CurrentColumns;
if (columns->Count == 1)
return;
window->DrawList->ChannelsSetCurrent(columns->Current + 1);
PopClipRect();
}
ImGuiColumns* ImGui::FindOrCreateColumns(ImGuiWindow* window, ImGuiID id)
{
// We have few columns per window so for now we don't need bother much with turning this into a faster lookup.
for (int n = 0; n < window->ColumnsStorage.Size; n++)
if (window->ColumnsStorage[n].ID == id)
return &window->ColumnsStorage[n];
window->ColumnsStorage.push_back(ImGuiColumns());
ImGuiColumns* columns = &window->ColumnsStorage.back();
columns->ID = id;
return columns;
}
ImGuiID ImGui::GetColumnsID(const char* str_id, int columns_count)
{
ImGuiWindow* window = GetCurrentWindow();
// Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget.
// In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer.
PushID(0x11223347 + (str_id ? 0 : columns_count));
ImGuiID id = window->GetID(str_id ? str_id : "columns");
PopID();
return id;
}
void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlags flags)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = GetCurrentWindow();
IM_ASSERT(columns_count >= 1);
IM_ASSERT(window->DC.CurrentColumns == NULL); // Nested columns are currently not supported
// Acquire storage for the columns set
ImGuiID id = GetColumnsID(str_id, columns_count);
ImGuiColumns* columns = FindOrCreateColumns(window, id);
IM_ASSERT(columns->ID == id);
columns->Current = 0;
columns->Count = columns_count;
columns->Flags = flags;
window->DC.CurrentColumns = columns;
columns->HostCursorPosY = window->DC.CursorPos.y;
columns->HostCursorMaxPosX = window->DC.CursorMaxPos.x;
columns->HostClipRect = window->ClipRect;
columns->HostWorkRect = window->WorkRect;
// Set state for first column
// We aim so that the right-most column will have the same clipping width as other after being clipped by parent ClipRect
const float column_padding = g.Style.ItemSpacing.x;
const float half_clip_extend_x = ImFloor(ImMax(window->WindowPadding.x * 0.5f, window->WindowBorderSize));
const float max_1 = window->WorkRect.Max.x + column_padding - ImMax(column_padding - window->WindowPadding.x, 0.0f);
const float max_2 = window->WorkRect.Max.x + half_clip_extend_x;
columns->OffMinX = window->DC.Indent.x - column_padding + ImMax(column_padding - window->WindowPadding.x, 0.0f);
columns->OffMaxX = ImMax(ImMin(max_1, max_2) - window->Pos.x, columns->OffMinX + 1.0f);
columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y;
// Clear data if columns count changed
if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1)
columns->Columns.resize(0);
// Initialize default widths
columns->IsFirstFrame = (columns->Columns.Size == 0);
if (columns->Columns.Size == 0)
{
columns->Columns.reserve(columns_count + 1);
for (int n = 0; n < columns_count + 1; n++)
{
ImGuiColumnData column;
column.OffsetNorm = n / (float)columns_count;
columns->Columns.push_back(column);
}
}
for (int n = 0; n < columns_count; n++)
{
// Compute clipping rectangle
ImGuiColumnData* column = &columns->Columns[n];
float clip_x1 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n));
float clip_x2 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n + 1) - 1.0f);
column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX);
column->ClipRect.ClipWith(window->ClipRect);
}
if (columns->Count > 1)
{
window->DrawList->ChannelsSplit(1 + columns->Count);
window->DrawList->ChannelsSetCurrent(1);
PushColumnClipRect(0);
}
// We don't generally store Indent.x inside ColumnsOffset because it may be manipulated by the user.
float offset_0 = GetColumnOffset(columns->Current);
float offset_1 = GetColumnOffset(columns->Current + 1);
float width = offset_1 - offset_0;
PushItemWidth(width * 0.65f);
window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f);
window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding;
}
void ImGui::NextColumn()
{
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems || window->DC.CurrentColumns == NULL)
return;
ImGuiContext& g = *GImGui;
ImGuiColumns* columns = window->DC.CurrentColumns;
if (columns->Count == 1)
{
window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
IM_ASSERT(columns->Current == 0);
return;
}
PopItemWidth();
PopClipRect();
const float column_padding = g.Style.ItemSpacing.x;
columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y);
if (++columns->Current < columns->Count)
{
// Columns 1+ ignore IndentX (by canceling it out)
// FIXME-COLUMNS: Unnecessary, could be locked?
window->DC.ColumnsOffset.x = GetColumnOffset(columns->Current) - window->DC.Indent.x + column_padding;
window->DrawList->ChannelsSetCurrent(columns->Current + 1);
}
else
{
// New row/line
// Column 0 honor IndentX
window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f);
window->DrawList->ChannelsSetCurrent(1);
columns->Current = 0;
columns->LineMinY = columns->LineMaxY;
}
window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
window->DC.CursorPos.y = columns->LineMinY;
window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
window->DC.CurrLineTextBaseOffset = 0.0f;
PushColumnClipRect(columns->Current); // FIXME-COLUMNS: Could it be an overwrite?
// FIXME-COLUMNS: Share code with BeginColumns() - move code on columns setup.
float offset_0 = GetColumnOffset(columns->Current);
float offset_1 = GetColumnOffset(columns->Current + 1);
float width = offset_1 - offset_0;
PushItemWidth(width * 0.65f);
window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding;
}
void ImGui::EndColumns()
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = GetCurrentWindow();
ImGuiColumns* columns = window->DC.CurrentColumns;
IM_ASSERT(columns != NULL);
PopItemWidth();
if (columns->Count > 1)
{
PopClipRect();
window->DrawList->ChannelsMerge();
}
const ImGuiColumnsFlags flags = columns->Flags;
columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y);
window->DC.CursorPos.y = columns->LineMaxY;
if (!(flags & ImGuiColumnsFlags_GrowParentContentsSize))
window->DC.CursorMaxPos.x = columns->HostCursorMaxPosX; // Restore cursor max pos, as columns don't grow parent
// Draw columns borders and handle resize
// The IsBeingResized flag ensure we preserve pre-resize columns width so back-and-forth are not lossy
bool is_being_resized = false;
if (!(flags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems)
{
// We clip Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.
const float y1 = ImMax(columns->HostCursorPosY, window->ClipRect.Min.y);
const float y2 = ImMin(window->DC.CursorPos.y, window->ClipRect.Max.y);
int dragging_column = -1;
for (int n = 1; n < columns->Count; n++)
{
ImGuiColumnData* column = &columns->Columns[n];
float x = window->Pos.x + GetColumnOffset(n);
const ImGuiID column_id = columns->ID + ImGuiID(n);
const float column_hit_hw = COLUMNS_HIT_RECT_HALF_WIDTH;
const ImRect column_hit_rect(ImVec2(x - column_hit_hw, y1), ImVec2(x + column_hit_hw, y2));
KeepAliveID(column_id);
if (IsClippedEx(column_hit_rect, column_id, false))
continue;
bool hovered = false, held = false;
if (!(flags & ImGuiColumnsFlags_NoResize))
{
ButtonBehavior(column_hit_rect, column_id, &hovered, &held);
if (hovered || held)
g.MouseCursor = ImGuiMouseCursor_ResizeEW;
if (held && !(column->Flags & ImGuiColumnsFlags_NoResize))
dragging_column = n;
}
// Draw column
const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
const float xi = (float)(int)x;
window->DrawList->AddLine(ImVec2(xi, y1 + 1.0f), ImVec2(xi, y2), col);
}
// Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame.
if (dragging_column != -1)
{
if (!columns->IsBeingResized)
for (int n = 0; n < columns->Count + 1; n++)
columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm;
columns->IsBeingResized = is_being_resized = true;
float x = GetDraggedColumnOffset(columns, dragging_column);
SetColumnOffset(dragging_column, x);
}
}
columns->IsBeingResized = is_being_resized;
window->WorkRect = columns->HostWorkRect;
window->DC.CurrentColumns = NULL;
window->DC.ColumnsOffset.x = 0.0f;
window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
}
// [2018-03: This is currently the only public API, while we are working on making BeginColumns/EndColumns user-facing]
void ImGui::Columns(int columns_count, const char* id, bool border)
{
ImGuiWindow* window = GetCurrentWindow();
IM_ASSERT(columns_count >= 1);
ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder);
//flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior
ImGuiColumns* columns = window->DC.CurrentColumns;
if (columns != NULL && columns->Count == columns_count && columns->Flags == flags)
return;
if (columns != NULL)
EndColumns();
if (columns_count != 1)
BeginColumns(id, columns_count, flags);
}
//-------------------------------------------------------------------------