aboutsummaryrefslogtreecommitdiff
path: root/3rdparty/imgui/source/imgui_widgets.cpp
diff options
context:
space:
mode:
Diffstat (limited to '3rdparty/imgui/source/imgui_widgets.cpp')
-rw-r--r--3rdparty/imgui/source/imgui_widgets.cpp469
1 files changed, 323 insertions, 146 deletions
diff --git a/3rdparty/imgui/source/imgui_widgets.cpp b/3rdparty/imgui/source/imgui_widgets.cpp
index 56723cd..cf2baf1 100644
--- a/3rdparty/imgui/source/imgui_widgets.cpp
+++ b/3rdparty/imgui/source/imgui_widgets.cpp
@@ -1,4 +1,4 @@
-// dear imgui, v1.87
+// dear imgui, v1.88 WIP
// (widgets code)
/*
@@ -82,6 +82,7 @@ Index of this file:
#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
#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
+#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
#endif
//-------------------------------------------------------------------------
@@ -166,7 +167,21 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
const float wrap_pos_x = window->DC.TextWrapPos;
const bool wrap_enabled = (wrap_pos_x >= 0.0f);
- if (text_end - text > 2000 && !wrap_enabled)
+ if (text_end - text <= 2000 || wrap_enabled)
+ {
+ // Common case
+ const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
+ const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
+
+ ImRect bb(text_pos, text_pos + text_size);
+ ItemSize(text_size, 0.0f);
+ if (!ItemAdd(bb, 0))
+ return;
+
+ // Render (we don't hide text after ## in this end-user function)
+ RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
+ }
+ else
{
// Long text!
// Perform manual coarse clipping to optimize for long multi-line text
@@ -239,19 +254,6 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
ItemSize(text_size, 0.0f);
ItemAdd(bb, 0);
}
- else
- {
- const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
- const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
-
- ImRect bb(text_pos, text_pos + text_size);
- ItemSize(text_size, 0.0f);
- if (!ItemAdd(bb, 0))
- return;
-
- // Render (we don't hide text after ## in this end-user function)
- RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
- }
}
void ImGui::TextUnformatted(const char* text, const char* text_end)
@@ -502,7 +504,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
flags |= ImGuiButtonFlags_PressedOnDefault_;
ImGuiWindow* backup_hovered_window = g.HoveredWindow;
- const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindow == window;
+ const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindowDockTree == window->RootWindowDockTree;
if (flatten_hovered_children)
g.HoveredWindow = window;
@@ -829,7 +831,8 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)
return pressed;
}
-bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
+// The Collapse button also functions as a Dock Menu button.
+bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
@@ -840,23 +843,27 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
// Render
+ //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed);
ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
ImU32 text_col = GetColorU32(ImGuiCol_Text);
- ImVec2 center = bb.GetCenter();
if (hovered || held)
- window->DrawList->AddCircleFilled(center/*+ ImVec2(0.0f, -0.5f)*/, g.FontSize * 0.5f + 1.0f, bg_col, 12);
- RenderArrow(window->DrawList, bb.Min + g.Style.FramePadding, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
+ window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0,-0.5f), g.FontSize * 0.5f + 1.0f, bg_col, 12);
+
+ if (dock_node)
+ RenderArrowDockMenu(window->DrawList, bb.Min + g.Style.FramePadding, g.FontSize, text_col);
+ else
+ RenderArrow(window->DrawList, bb.Min + g.Style.FramePadding, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
// Switch to moving the window after mouse is moved beyond the initial drag threshold
if (IsItemActive() && IsMouseDragging(0))
- StartMouseMovingWindow(window);
+ StartMouseMovingWindowOrNode(window, dock_node, true);
return pressed;
}
ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)
{
- return window->GetIDNoKeepAlive(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
+ return window->GetID(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
}
// Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.
@@ -1285,7 +1292,7 @@ void ImGui::Bullet()
ImGuiContext& g = *GImGui;
const ImGuiStyle& style = g.Style;
- const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2), g.FontSize);
+ const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), g.FontSize);
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
ItemSize(bb);
if (!ItemAdd(bb, 0))
@@ -1341,6 +1348,7 @@ void ImGui::NewLine()
ImGuiContext& g = *GImGui;
const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
window->DC.LayoutType = ImGuiLayoutType_Vertical;
+ window->DC.IsSameLine = false;
if (window->DC.CurrLineSize.y > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height.
ItemSize(ImVec2(0, 0));
else
@@ -1443,7 +1451,7 @@ void ImGui::Separator()
}
// Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise.
-bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay)
+bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay, ImU32 bg_col)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
@@ -1495,7 +1503,9 @@ bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float
}
}
- // Render
+ // Render at new position
+ if (bg_col & IM_COL32_A_MASK)
+ window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, bg_col, 0.0f);
const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f);
@@ -1733,6 +1743,7 @@ bool ImGui::BeginComboPreview()
window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding;
window->DC.CursorMaxPos = window->DC.CursorPos;
window->DC.LayoutType = ImGuiLayoutType_Horizontal;
+ window->DC.IsSameLine = false;
PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true);
return true;
@@ -1758,6 +1769,7 @@ void ImGui::EndComboPreview()
window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine;
window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset;
window->DC.LayoutType = preview_data->BackupLayout;
+ window->DC.IsSameLine = false;
preview_data->PreviewRect = ImRect();
}
@@ -2007,23 +2019,20 @@ bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void
ImGuiDataTypeTempStorage data_backup;
memcpy(&data_backup, p_data, type_info->Size);
- if (format == NULL)
+ // Sanitize format
+ // For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf
+ char format_sanitized[32];
+ if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
format = type_info->ScanFmt;
-
- if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64 || data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
- {
- // For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf
- if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
- format = type_info->ScanFmt;
- if (sscanf(buf, format, p_data) < 1)
- return false;
- }
else
+ format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_ARRAYSIZE(format_sanitized));
+
+ // Small types need a 32-bit buffer to receive the result from scanf()
+ int v32 = 0;
+ if (sscanf(buf, format, type_info->Size >= 4 ? p_data : &v32) < 1)
+ return false;
+ if (type_info->Size < 4)
{
- // Small types need a 32-bit buffer to receive the result from scanf()
- int v32;
- if (sscanf(buf, format, &v32) < 1)
- return false;
if (data_type == ImGuiDataType_S8)
*(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX);
else if (data_type == ImGuiDataType_U8)
@@ -2105,45 +2114,17 @@ static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
}
template<typename TYPE>
-static const char* ImAtoi(const char* src, TYPE* output)
-{
- int negative = 0;
- if (*src == '-') { negative = 1; src++; }
- if (*src == '+') { src++; }
- TYPE v = 0;
- while (*src >= '0' && *src <= '9')
- v = (v * 10) + (*src++ - '0');
- *output = negative ? -v : v;
- return src;
-}
-
-// Sanitize format
-// - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi
-// - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.
-static void SanitizeFormatString(const char* fmt, char* fmt_out, size_t fmt_out_size)
-{
- IM_UNUSED(fmt_out_size);
- const char* fmt_end = ImParseFormatFindEnd(fmt);
- IM_ASSERT((size_t)(fmt_end - fmt + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
- while (fmt < fmt_end)
- {
- char c = *(fmt++);
- if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
- *(fmt_out++) = c;
- }
- *fmt_out = 0; // Zero-terminate
-}
-
-template<typename TYPE, typename SIGNEDTYPE>
TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
{
+ IM_UNUSED(data_type);
+ IM_ASSERT(data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
const char* fmt_start = ImParseFormatFindStart(format);
if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
return v;
// Sanitize format
char fmt_sanitized[32];
- SanitizeFormatString(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
+ ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
fmt_start = fmt_sanitized;
// Format value with our rounding, and read back
@@ -2152,10 +2133,8 @@ TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type,
const char* p = v_str;
while (*p == ' ')
p++;
- if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
- v = (TYPE)ImAtof(p);
- else
- ImAtoi(p, (SIGNEDTYPE*)&v);
+ v = (TYPE)ImAtof(p);
+
return v;
}
@@ -2259,8 +2238,8 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const
}
// Round to user desired precision based on format string
- if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
- v_cur = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_cur);
+ if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
+ v_cur = RoundScalarWithFormatT<TYPE>(format, data_type, v_cur);
// Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
g.DragCurrentAccumDirty = false;
@@ -2856,8 +2835,8 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ
// Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator
TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
- if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
- v_new = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_new);
+ if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
+ v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
float new_clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
if (delta > 0)
@@ -2875,8 +2854,8 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ
TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
// Round to user desired precision based on format string
- if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
- v_new = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_new);
+ if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
+ v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
// Apply result
if (*v != v_new)
@@ -3219,6 +3198,8 @@ bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, i
// - ImParseFormatFindStart() [Internal]
// - ImParseFormatFindEnd() [Internal]
// - ImParseFormatTrimDecorations() [Internal]
+// - ImParseFormatSanitizeForPrinting() [Internal]
+// - ImParseFormatSanitizeForScanning() [Internal]
// - ImParseFormatPrecision() [Internal]
// - TempInputTextScalar() [Internal]
// - InputScalar()
@@ -3282,6 +3263,57 @@ const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_
return buf;
}
+// Sanitize format
+// - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi
+// - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.
+void ImParseFormatSanitizeForPrinting(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
+{
+ const char* fmt_end = ImParseFormatFindEnd(fmt_in);
+ IM_UNUSED(fmt_out_size);
+ IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
+ while (fmt_in < fmt_end)
+ {
+ char c = *fmt_in++;
+ if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
+ *(fmt_out++) = c;
+ }
+ *fmt_out = 0; // Zero-terminate
+}
+
+// - For scanning we need to remove all width and precision fields "%3.7f" -> "%f". BUT don't strip types like "%I64d" which includes digits. ! "%07I64d" -> "%I64d"
+const char* ImParseFormatSanitizeForScanning(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
+{
+ const char* fmt_end = ImParseFormatFindEnd(fmt_in);
+ const char* fmt_out_begin = fmt_out;
+ IM_UNUSED(fmt_out_size);
+ IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
+ bool has_type = false;
+ while (fmt_in < fmt_end)
+ {
+ char c = *fmt_in++;
+ if (!has_type && ((c >= '0' && c <= '9') || c == '.'))
+ continue;
+ has_type |= ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); // Stop skipping digits
+ if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
+ *(fmt_out++) = c;
+ }
+ *fmt_out = 0; // Zero-terminate
+ return fmt_out_begin;
+}
+
+template<typename TYPE>
+static const char* ImAtoi(const char* src, TYPE* output)
+{
+ int negative = 0;
+ if (*src == '-') { negative = 1; src++; }
+ if (*src == '+') { src++; }
+ TYPE v = 0;
+ while (*src >= '0' && *src <= '9')
+ v = (v * 10) + (*src++ - '0');
+ *output = negative ? -v : v;
+ return src;
+}
+
// Parse display precision back from the display format string
// FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed.
int ImParseFormatPrecision(const char* fmt, int default_precision)
@@ -3328,6 +3360,14 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char*
return value_changed;
}
+static inline ImGuiInputTextFlags InputScalar_DefaultCharsFilter(ImGuiDataType data_type, const char* format)
+{
+ if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
+ return ImGuiInputTextFlags_CharsScientific;
+ const char format_last_char = format[0] ? format[strlen(format) - 1] : 0;
+ return (format_last_char == 'x' || format_last_char == 'X') ? ImGuiInputTextFlags_CharsHexadecimal : ImGuiInputTextFlags_CharsDecimal;
+}
+
// Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set!
// This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility.
// However this may not be ideal for all uses, as some user code may break on out of bound values.
@@ -3340,7 +3380,8 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG
ImStrTrimBlanks(data_buf);
ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited;
- flags |= ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal);
+ flags |= InputScalar_DefaultCharsFilter(data_type, format);
+
bool value_changed = false;
if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags))
{
@@ -3350,7 +3391,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG
memcpy(&data_backup, p_data, data_type_size);
// Apply new value (or operations) then clamp
- DataTypeApplyFromText(data_buf, data_type, p_data, NULL);
+ DataTypeApplyFromText(data_buf, data_type, p_data, format);
if (p_clamp_min || p_clamp_max)
{
if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0)
@@ -3383,12 +3424,12 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
char buf[64];
DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
- bool value_changed = false;
- if ((flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
- flags |= ImGuiInputTextFlags_CharsDecimal;
- flags |= ImGuiInputTextFlags_AutoSelectAll;
- flags |= ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
+ // Testing ActiveId as a minor optimization as filtering is not needed until active
+ if (g.ActiveId == 0 && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
+ flags |= InputScalar_DefaultCharsFilter(data_type, format);
+ flags |= ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
+ bool value_changed = false;
if (p_step != NULL)
{
const float button_size = GetFrameHeight();
@@ -3532,6 +3573,9 @@ bool ImGui::InputDouble(const char* label, double* v, double step, double step_f
// - InputText()
// - InputTextWithHint()
// - InputTextMultiline()
+// - InputTextGetCharInfo() [Internal]
+// - InputTextReindexLines() [Internal]
+// - InputTextReindexLinesRange() [Internal]
// - InputTextEx() [Internal]
//-------------------------------------------------------------------------
@@ -3812,7 +3856,7 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f
if (c < 0x20)
{
bool pass = false;
- pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
+ pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code)
pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
if (!pass)
return false;
@@ -4198,16 +4242,15 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (state->SelectedAllMouseLock && !io.MouseDown[0])
state->SelectedAllMouseLock = false;
- // It is ill-defined whether the backend needs to send a \t character when pressing the TAB keys.
- // Win32 and GLFW naturally do it but not SDL.
+ // We except backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336)
+ // (For Tab and Enter: Win32/SFML/Allegro are sending both keys and chars, GLFW and SDL are only sending keys. For Space they all send all threes)
const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressed(ImGuiKey_Tab) && !ignore_char_inputs && !io.KeyShift && !is_readonly)
- if (!io.InputQueueCharacters.contains('\t'))
- {
- unsigned int c = '\t'; // Insert TAB
- if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
- state->OnKeyPressed((int)c);
- }
+ {
+ unsigned int c = '\t'; // Insert TAB
+ if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
+ state->OnKeyPressed((int)c);
+ }
// Process regular text input (before we check for Return because using some IME will effectively send a Return?)
// We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
@@ -4218,7 +4261,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
{
// Insert character if they pass filtering
unsigned int c = (unsigned int)io.InputQueueCharacters[n];
- if (c == '\t' && io.KeyShift)
+ if (c == '\t') // Skip Tab, see above.
continue;
if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
state->OnKeyPressed((int)c);
@@ -4234,19 +4277,18 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
{
IM_ASSERT(state != NULL);
- IM_ASSERT(io.KeyMods == GetMergedKeyModFlags() && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); // We rarely do this check, but if anything let's do it here.
const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1);
state->Stb.row_count_per_page = row_count_per_page;
const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
const bool is_osx = io.ConfigMacOSXBehaviors;
- const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiKeyModFlags_Super | ImGuiKeyModFlags_Shift));
+ const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiModFlags_Super | ImGuiModFlags_Shift));
const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl
const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
- const bool is_ctrl_key_only = (io.KeyMods == ImGuiKeyModFlags_Ctrl);
- const bool is_shift_key_only = (io.KeyMods == ImGuiKeyModFlags_Shift);
- const bool is_shortcut_key = g.IO.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiKeyModFlags_Super) : (io.KeyMods == ImGuiKeyModFlags_Ctrl);
+ const bool is_ctrl_key_only = (io.KeyMods == ImGuiModFlags_Ctrl);
+ const bool is_shift_key_only = (io.KeyMods == ImGuiModFlags_Shift);
+ const bool is_shortcut_key = g.IO.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiModFlags_Super) : (io.KeyMods == ImGuiModFlags_Ctrl);
const bool is_cut = ((is_shortcut_key && IsKeyPressed(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressed(ImGuiKey_Delete))) && !is_readonly && !is_password && (!is_multiline || state->HasSelection());
const bool is_copy = ((is_shortcut_key && IsKeyPressed(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressed(ImGuiKey_Insert))) && !is_password && (!is_multiline || state->HasSelection());
@@ -4714,6 +4756,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
g.PlatformImeData.WantVisible = true;
g.PlatformImeData.InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
g.PlatformImeData.InputLineHeight = g.FontSize;
+ g.PlatformImeViewport = window->Viewport->ID;
}
}
}
@@ -4996,8 +5039,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
{
- const float text_offset_x = (flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x;
- window->DC.CursorPos = ImVec2(pos.x + text_offset_x, pos.y + style.FramePadding.y);
+ SameLine(0.0f, style.ItemInnerSpacing.x);
TextEx(label, label_display_end);
}
@@ -5453,7 +5495,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl
// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
// Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set.
-bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
+bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, const ImVec2& size_arg)
{
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
@@ -5461,11 +5503,8 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl
ImGuiContext& g = *GImGui;
const ImGuiID id = window->GetID(desc_id);
- float default_size = GetFrameHeight();
- if (size.x == 0.0f)
- size.x = default_size;
- if (size.y == 0.0f)
- size.y = default_size;
+ const float default_size = GetFrameHeight();
+ const ImVec2 size(size_arg.x == 0.0f ? default_size : size_arg.x, size_arg.y == 0.0f ? default_size : size_arg.y);
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
if (!ItemAdd(bb, id))
@@ -6703,6 +6742,7 @@ bool ImGui::BeginMenuBar()
// We overwrite CursorMaxPos because BeginGroup sets it to CursorPos (essentially the .EmitItem hack in EndMenuBar() would need something analogous here, maybe a BeginGroupEx() with flags).
window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
window->DC.LayoutType = ImGuiLayoutType_Horizontal;
+ window->DC.IsSameLine = false;
window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
window->DC.MenuBarAppending = true;
AlignTextToFramePadding();
@@ -6746,6 +6786,7 @@ void ImGui::EndMenuBar()
g.GroupStack.back().EmitItem = false;
EndGroup(); // Restore position on layer 0
window->DC.LayoutType = ImGuiLayoutType_Vertical;
+ window->DC.IsSameLine = false;
window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
window->DC.MenuBarAppending = false;
}
@@ -6758,10 +6799,10 @@ bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, Im
IM_ASSERT(dir != ImGuiDir_None);
ImGuiWindow* bar_window = FindWindowByName(name);
+ ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport());
if (bar_window == NULL || bar_window->BeginCount == 0)
{
// Calculate and set window size/position
- ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport());
ImRect avail_rect = viewport->GetBuildWorkRect();
ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;
ImVec2 pos = avail_rect.Min;
@@ -6779,7 +6820,8 @@ bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, Im
viewport->BuildWorkOffsetMax[axis] -= axis_size;
}
- window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
+ window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking;
+ SetNextWindowViewport(viewport->ID); // Enforce viewport so we don't create our own viewport when ImGuiConfigFlags_ViewportsNoMerge is set.
PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint
bool is_open = Begin(name, NULL, window_flags);
@@ -6793,6 +6835,9 @@ bool ImGui::BeginMainMenuBar()
ImGuiContext& g = *GImGui;
ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
+ // Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change
+ SetCurrentViewport(NULL, viewport);
+
// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
// FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea?
// FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings.
@@ -6924,7 +6969,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
if (!enabled)
EndDisabled();
- const bool hovered = (g.HoveredId == id) && enabled;
+ const bool hovered = (g.HoveredId == id) && enabled && !g.NavDisableMouseHover;
if (menuset_is_open)
g.NavWindow = backed_nav_window;
@@ -6934,7 +6979,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
{
// Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu
// Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
- bool moving_toward_other_child_menu = false;
+ bool moving_toward_child_menu = false;
ImGuiWindow* child_menu_window = (g.BeginPopupStack.Size < g.OpenPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) ? g.OpenPopupStack[g.BeginPopupStack.Size].Window : NULL;
if (g.HoveredWindow == window && child_menu_window != NULL && !(window->Flags & ImGuiWindowFlags_MenuBar))
{
@@ -6945,18 +6990,22 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, ref_unit * 0.5f, ref_unit * 2.5f); // add a bit of extra slack.
ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues (FIXME: ??)
- tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -ref_unit * 8.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale?
+ tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -ref_unit * 8.0f); // triangle has maximum height to limit the slope and the bias toward large sub-menus
tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +ref_unit * 8.0f);
- moving_toward_other_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
+ moving_toward_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
//GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_toward_other_child_menu ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG]
}
- if (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_toward_other_child_menu)
+
+ // The 'HovereWindow == window' check creates an inconsistency (e.g. moving away from menu slowly tends to hit same window, whereas moving away fast does not)
+ // But we also need to not close the top-menu menu when moving over void. Perhaps we should extend the triangle check to a larger polygon.
+ // (Remember to test this on BeginPopup("A")->BeginMenu("B") sequence which behaves slightly differently as B isn't a Child of A and hovering isn't shared.)
+ if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu)
want_close = true;
// Open
if (!menu_is_open && pressed) // Click/activate to open
want_open = true;
- else if (!menu_is_open && hovered && !moving_toward_other_child_menu) // Hover to open
+ else if (!menu_is_open && hovered && !moving_toward_child_menu) // Hover to open
want_open = true;
if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
{
@@ -7136,6 +7185,7 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected,
// - TabBarCalcTabID() [Internal]
// - TabBarCalcMaxTabWidth() [Internal]
// - TabBarFindTabById() [Internal]
+// - TabBarAddTab() [Internal]
// - TabBarRemoveTab() [Internal]
// - TabBarCloseTab() [Internal]
// - TabBarScrollClamp() [Internal]
@@ -7157,7 +7207,7 @@ struct ImGuiTabBarSection
namespace ImGui
{
static void TabBarLayout(ImGuiTabBar* tab_bar);
- static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label);
+ static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window);
static float TabBarCalcMaxTabWidth();
static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections);
@@ -7220,10 +7270,10 @@ bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->WorkRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2);
tab_bar->ID = id;
- return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused);
+ return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused, NULL);
}
-bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)
+bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags, ImGuiDockNode* dock_node)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
@@ -7248,7 +7298,8 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG
// Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable
if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable)))
- ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder);
+ if ((flags & ImGuiTabBarFlags_DockNode) == 0) // FIXME: TabBar with DockNode can now be hybrid
+ ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder);
tab_bar->TabsAddedNew = false;
// Flags
@@ -7273,6 +7324,13 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG
// Draw separator
const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive);
const float y = tab_bar->BarRect.Max.y - 1.0f;
+ if (dock_node != NULL)
+ {
+ const float separator_min_x = dock_node->Pos.x + window->WindowBorderSize;
+ const float separator_max_x = dock_node->Pos.x + dock_node->Size.x - window->WindowBorderSize;
+ window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f);
+ }
+ else
{
const float separator_min_x = tab_bar->BarRect.Min.x - IM_FLOOR(window->WindowPadding.x * 0.5f);
const float separator_max_x = tab_bar->BarRect.Max.x + IM_FLOOR(window->WindowPadding.x * 0.5f);
@@ -7423,7 +7481,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
// Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
// and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
const char* tab_name = tab_bar->GetTabName(tab);
- const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true;
+ const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0;
tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x;
int section_n = TabItemGetSectionIdx(tab);
@@ -7502,6 +7560,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
{
ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n];
tab->Offset = tab_offset;
+ tab->NameOffset = -1;
tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f);
}
tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f);
@@ -7509,6 +7568,9 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
section_tab_index += section->TabCount;
}
+ // Clear name buffers
+ tab_bar->TabsNames.Buf.resize(0);
+
// If we have lost the selected tab, select the next most recently active one
if (found_selected_tab_id == false)
tab_bar->SelectedTabId = 0;
@@ -7519,6 +7581,10 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
tab_bar->VisibleTabId = tab_bar->SelectedTabId;
tab_bar->VisibleTabWasSubmitted = false;
+ // CTRL+TAB can override visible tab temporarily
+ if (g.NavWindowingTarget != NULL && g.NavWindowingTarget->DockNode && g.NavWindowingTarget->DockNode->TabBar == tab_bar)
+ tab_bar->VisibleTabId = scroll_to_tab_id = g.NavWindowingTarget->ID;
+
// Update scrolling
if (scroll_to_tab_id != 0)
TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections);
@@ -7540,10 +7606,6 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing;
tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing;
- // Clear name buffers
- if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
- tab_bar->TabsNames.Buf.resize(0);
-
// Actual layout in host window (we don't do it in BeginTabBar() so as not to waste an extra frame)
ImGuiWindow* window = g.CurrentWindow;
window->DC.CursorPos = tab_bar->BarRect.Min;
@@ -7551,12 +7613,14 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal);
}
-// Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack.
-static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label)
+// Dockable uses Name/ID in the global namespace. Non-dockable items use the ID stack.
+static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window)
{
- if (tab_bar->Flags & ImGuiTabBarFlags_DockNode)
+ if (docked_window != NULL)
{
- ImGuiID id = ImHashStr(label);
+ IM_UNUSED(tab_bar);
+ IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode);
+ ImGuiID id = docked_window->TabId;
KeepAliveID(id);
return id;
}
@@ -7582,6 +7646,41 @@ ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
return NULL;
}
+// FIXME: See references to #2304 in TODO.txt
+ImGuiTabItem* ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow(ImGuiTabBar* tab_bar)
+{
+ ImGuiTabItem* most_recently_selected_tab = NULL;
+ for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
+ {
+ ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
+ if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected)
+ if (tab->Window && tab->Window->WasActive)
+ most_recently_selected_tab = tab;
+ }
+ return most_recently_selected_tab;
+}
+
+// The purpose of this call is to register tab in advance so we can control their order at the time they appear.
+// Otherwise calling this is unnecessary as tabs are appending as needed by the BeginTabItem() function.
+void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGuiWindow* window)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(TabBarFindTabByID(tab_bar, window->TabId) == NULL);
+ IM_ASSERT(g.CurrentTabBar != tab_bar); // Can't work while the tab bar is active as our tab doesn't have an X offset yet, in theory we could/should test something like (tab_bar->CurrFrameVisible < g.FrameCount) but we'd need to solve why triggers the commented early-out assert in BeginTabBarEx() (probably dock node going from implicit to explicit in same frame)
+
+ if (!window->HasCloseButton)
+ tab_flags |= ImGuiTabItemFlags_NoCloseButton; // Set _NoCloseButton immediately because it will be used for first-frame width calculation.
+
+ ImGuiTabItem new_tab;
+ new_tab.ID = window->TabId;
+ new_tab.Flags = tab_flags;
+ new_tab.LastFrameVisible = tab_bar->CurrFrameVisible; // Required so BeginTabBar() doesn't ditch the tab
+ if (new_tab.LastFrameVisible == -1)
+ new_tab.LastFrameVisible = g.FrameCount - 1;
+ new_tab.Window = window; // Required so tab bar layout can compute the tab width before tab submission
+ tab_bar->Tabs.push_back(new_tab);
+}
+
// The *TabId fields be already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.
void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
{
@@ -7856,9 +7955,9 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f
IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!");
return false;
}
- IM_ASSERT(!(flags & ImGuiTabItemFlags_Button)); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead!
+ IM_ASSERT((flags & ImGuiTabItemFlags_Button) == 0); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead!
- bool ret = TabItemEx(tab_bar, label, p_open, flags);
+ bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL);
if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
{
ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
@@ -7899,10 +7998,10 @@ bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags)
IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
return false;
}
- return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder);
+ return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL);
}
-bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags)
+bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window)
{
// Layout whole tab bar if not already done
if (tab_bar->WantLayout)
@@ -7914,7 +8013,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
return false;
const ImGuiStyle& style = g.Style;
- const ImGuiID id = TabBarCalcTabID(tab_bar, label);
+ const ImGuiID id = TabBarCalcTabID(tab_bar, label, docked_window);
// If the user called us with *p_open == false, we early out and don't render.
// We make a call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID.
@@ -7959,10 +8058,21 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0;
tab->LastFrameVisible = g.FrameCount;
tab->Flags = flags;
+ tab->Window = docked_window;
// Append name with zero-terminator
- tab->NameOffset = (ImS32)tab_bar->TabsNames.size();
- tab_bar->TabsNames.append(label, label + strlen(label) + 1);
+ // (regular tabs are permitted in a DockNode tab bar, but window tabs not permitted in a non-DockNode tab bar)
+ if (tab->Window != NULL)
+ {
+ IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode);
+ tab->NameOffset = -1;
+ }
+ else
+ {
+ IM_ASSERT(tab->Window == NULL);
+ tab->NameOffset = (ImS32)tab_bar->TabsNames.size();
+ tab_bar->TabsNames.append(label, label + strlen(label) + 1); // Append name _with_ the zero-terminator.
+ }
// Update selected tab
if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
@@ -7980,7 +8090,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
tab_bar->VisibleTabWasSubmitted = true;
// On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
- if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing)
+ if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing && docked_window == NULL)
if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
tab_contents_visible = true;
@@ -8029,32 +8139,84 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
// Click to Select a tab
ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowItemOverlap);
- if (g.DragDropActive)
+ if (g.DragDropActive && !g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW)) // FIXME: May be an opt-in property of the payload to disable this
button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
bool hovered, held;
bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
if (pressed && !is_tab_button)
tab_bar->NextSelectedTabId = id;
+ // Transfer active id window so the active id is not owned by the dock host (as StartMouseMovingWindow()
+ // will only do it on the drag). This allows FocusWindow() to be more conservative in how it clears active id.
+ if (held && docked_window && g.ActiveId == id && g.ActiveIdIsJustActivated)
+ g.ActiveIdWindow = docked_window;
+
// Allow the close button to overlap unless we are dragging (in which case we don't want any overlapping tabs to be hovered)
if (g.ActiveId != id)
SetItemAllowOverlap();
- // Drag and drop: re-order tabs
- if (held && !tab_appearing && IsMouseDragging(0))
+ // Drag and drop a single floating window node moves it
+ ImGuiDockNode* node = docked_window ? docked_window->DockNode : NULL;
+ const bool single_floating_window_node = node && node->IsFloatingNode() && (node->Windows.Size == 1);
+ if (held && single_floating_window_node && IsMouseDragging(0, 0.0f))
+ {
+ // Move
+ StartMouseMovingWindow(docked_window);
+ }
+ else if (held && !tab_appearing && IsMouseDragging(0))
{
- if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable))
+ // Drag and drop: re-order tabs
+ int drag_dir = 0;
+ float drag_distance_from_edge_x = 0.0f;
+ if (!g.DragDropActive && ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (docked_window != NULL)))
{
// While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
{
+ drag_dir = -1;
+ drag_distance_from_edge_x = bb.Min.x - g.IO.MousePos.x;
TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
}
else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
{
+ drag_dir = +1;
+ drag_distance_from_edge_x = g.IO.MousePos.x - bb.Max.x;
TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
}
}
+
+ // Extract a Dockable window out of it's tab bar
+ if (docked_window != NULL && !(docked_window->Flags & ImGuiWindowFlags_NoMove))
+ {
+ // We use a variable threshold to distinguish dragging tabs within a tab bar and extracting them out of the tab bar
+ bool undocking_tab = (g.DragDropActive && g.DragDropPayload.SourceId == id);
+ if (!undocking_tab) //&& (!g.IO.ConfigDockingWithShift || g.IO.KeyShift)
+ {
+ float threshold_base = g.FontSize;
+ float threshold_x = (threshold_base * 2.2f);
+ float threshold_y = (threshold_base * 1.5f) + ImClamp((ImFabs(g.IO.MouseDragMaxDistanceAbs[0].x) - threshold_base * 2.0f) * 0.20f, 0.0f, threshold_base * 4.0f);
+ //GetForegroundDrawList()->AddRect(ImVec2(bb.Min.x - threshold_x, bb.Min.y - threshold_y), ImVec2(bb.Max.x + threshold_x, bb.Max.y + threshold_y), IM_COL32_WHITE); // [DEBUG]
+
+ float distance_from_edge_y = ImMax(bb.Min.y - g.IO.MousePos.y, g.IO.MousePos.y - bb.Max.y);
+ if (distance_from_edge_y >= threshold_y)
+ undocking_tab = true;
+ if (drag_distance_from_edge_x > threshold_x)
+ if ((drag_dir < 0 && tab_bar->GetTabOrder(tab) == 0) || (drag_dir > 0 && tab_bar->GetTabOrder(tab) == tab_bar->Tabs.Size - 1))
+ undocking_tab = true;
+ }
+
+ if (undocking_tab)
+ {
+ // Undock
+ // FIXME: refactor to share more code with e.g. StartMouseMovingWindow
+ DockContextQueueUndockWindow(&g, docked_window);
+ g.MovingWindow = docked_window;
+ SetActiveID(g.MovingWindow->MoveId, g.MovingWindow);
+ g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min;
+ g.ActiveIdNoClearOnFocusLoss = true;
+ SetActiveIdUsingNavAndKeys();
+ }
+ }
}
#if 0
@@ -8083,7 +8245,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
// Render tab label, process close button
- const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, id) : 0;
+ const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, docked_window ? docked_window->ID : id) : 0;
bool just_closed;
bool text_clipped;
TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped);
@@ -8093,6 +8255,11 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
TabBarCloseTab(tab_bar, tab);
}
+ // Forward Hovered state so IsItemHovered() after Begin() can work (even though we are technically hovering our parent)
+ // That state is copied to window->DockTabItemStatusFlags by our caller.
+ if (docked_window && (hovered || g.HoveredId == close_button_id))
+ g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
+
// Restore main window position so user can draw there
if (want_clip_rect)
PopClipRect();
@@ -8123,10 +8290,20 @@ void ImGui::SetTabItemClosed(const char* label)
if (is_within_manual_tab_bar)
{
ImGuiTabBar* tab_bar = g.CurrentTabBar;
- ImGuiID tab_id = TabBarCalcTabID(tab_bar, label);
+ ImGuiID tab_id = TabBarCalcTabID(tab_bar, label, NULL);
if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
tab->WantClose = true; // Will be processed by next call to TabBarLayout()
}
+ else if (ImGuiWindow* window = FindWindowByName(label))
+ {
+ if (window->DockIsActive)
+ if (ImGuiDockNode* node = window->DockNode)
+ {
+ ImGuiID tab_id = TabBarCalcTabID(node->TabBar, label, window);
+ TabBarRemoveTab(node->TabBar, tab_id);
+ window->DockTabWantClose = true;
+ }
+ }
}
ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button)
@@ -8150,7 +8327,7 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI
IM_ASSERT(width > 0.0f);
const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f));
const float y1 = bb.Min.y + 1.0f;
- const float y2 = bb.Max.y - 1.0f;
+ const float y2 = bb.Max.y + ((flags & ImGuiTabItemFlags_Preview) ? 0.0f : -1.0f);
draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);