|
@@ -1,4 +1,4 @@
|
|
|
-// dear imgui, v1.88 WIP
|
|
|
|
|
|
|
+// dear imgui, v1.89 WIP
|
|
|
// (widgets code)
|
|
// (widgets code)
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -276,9 +276,9 @@ void ImGui::TextV(const char* fmt, va_list args)
|
|
|
return;
|
|
return;
|
|
|
|
|
|
|
|
// FIXME-OPT: Handle the %s shortcut?
|
|
// FIXME-OPT: Handle the %s shortcut?
|
|
|
- ImGuiContext& g = *GImGui;
|
|
|
|
|
- const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
|
|
|
|
|
- TextEx(g.TempBuffer, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
|
|
|
|
|
|
|
+ const char* text, *text_end;
|
|
|
|
|
+ ImFormatStringToTempBufferV(&text, &text_end, fmt, args);
|
|
|
|
|
+ TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
|
|
void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
|
|
@@ -359,8 +359,8 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
|
|
|
const ImGuiStyle& style = g.Style;
|
|
const ImGuiStyle& style = g.Style;
|
|
|
const float w = CalcItemWidth();
|
|
const float w = CalcItemWidth();
|
|
|
|
|
|
|
|
- const char* value_text_begin = &g.TempBuffer[0];
|
|
|
|
|
- const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
|
|
|
|
|
|
|
+ const char* value_text_begin, *value_text_end;
|
|
|
|
|
+ ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args);
|
|
|
const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false);
|
|
const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false);
|
|
|
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
|
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
|
|
|
|
|
|
@@ -395,8 +395,8 @@ void ImGui::BulletTextV(const char* fmt, va_list args)
|
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiContext& g = *GImGui;
|
|
|
const ImGuiStyle& style = g.Style;
|
|
const ImGuiStyle& style = g.Style;
|
|
|
|
|
|
|
|
- const char* text_begin = g.TempBuffer;
|
|
|
|
|
- const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
|
|
|
|
|
|
|
+ const char* text_begin, *text_end;
|
|
|
|
|
+ ImFormatStringToTempBufferV(&text_begin, &text_end, fmt, args);
|
|
|
const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
|
|
const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
|
|
|
const ImVec2 total_size = ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x * 2) : 0.0f), label_size.y); // Empty text doesn't add padding
|
|
const ImVec2 total_size = ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x * 2) : 0.0f), label_size.y); // Empty text doesn't add padding
|
|
|
ImVec2 pos = window->DC.CursorPos;
|
|
ImVec2 pos = window->DC.CursorPos;
|
|
@@ -611,7 +611,15 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
|
|
|
if (g.NavActivateDownId == id)
|
|
if (g.NavActivateDownId == id)
|
|
|
{
|
|
{
|
|
|
bool nav_activated_by_code = (g.NavActivateId == id);
|
|
bool nav_activated_by_code = (g.NavActivateId == id);
|
|
|
- bool nav_activated_by_inputs = IsNavInputTest(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiNavReadMode_Repeat : ImGuiNavReadMode_Pressed);
|
|
|
|
|
|
|
+ bool nav_activated_by_inputs = (g.NavActivatePressedId == id);
|
|
|
|
|
+ if (!nav_activated_by_inputs && (flags & ImGuiButtonFlags_Repeat))
|
|
|
|
|
+ {
|
|
|
|
|
+ // Avoid pressing both keys from triggering double amount of repeat events
|
|
|
|
|
+ const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space);
|
|
|
|
|
+ const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_NavGamepadActivate);
|
|
|
|
|
+ const float t1 = ImMax(key1->DownDuration, key2->DownDuration);
|
|
|
|
|
+ nav_activated_by_inputs = CalcTypematicRepeatAmount(t1 - g.IO.DeltaTime, t1, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;
|
|
|
|
|
+ }
|
|
|
if (nav_activated_by_code || nav_activated_by_inputs)
|
|
if (nav_activated_by_code || nav_activated_by_inputs)
|
|
|
{
|
|
{
|
|
|
// Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
|
|
// Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
|
|
@@ -880,9 +888,7 @@ void ImGui::Scrollbar(ImGuiAxis axis)
|
|
|
{
|
|
{
|
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiContext& g = *GImGui;
|
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
-
|
|
|
|
|
const ImGuiID id = GetWindowScrollbarID(window, axis);
|
|
const ImGuiID id = GetWindowScrollbarID(window, axis);
|
|
|
- KeepAliveID(id);
|
|
|
|
|
|
|
|
|
|
// Calculate scrollbar bounding box
|
|
// Calculate scrollbar bounding box
|
|
|
ImRect bb = GetWindowScrollbarRect(window, axis);
|
|
ImRect bb = GetWindowScrollbarRect(window, axis);
|
|
@@ -920,6 +926,8 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6
|
|
|
if (window->SkipItems)
|
|
if (window->SkipItems)
|
|
|
return false;
|
|
return false;
|
|
|
|
|
|
|
|
|
|
+ KeepAliveID(id);
|
|
|
|
|
+
|
|
|
const float bb_frame_width = bb_frame.GetWidth();
|
|
const float bb_frame_width = bb_frame.GetWidth();
|
|
|
const float bb_frame_height = bb_frame.GetHeight();
|
|
const float bb_frame_height = bb_frame.GetHeight();
|
|
|
if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)
|
|
if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)
|
|
@@ -1540,7 +1548,7 @@ void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_exc
|
|
|
width_excess -= width_to_remove_per_item * count_same_width;
|
|
width_excess -= width_to_remove_per_item * count_same_width;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Round width and redistribute remainder left-to-right (could make it an option of the function?)
|
|
|
|
|
|
|
+ // Round width and redistribute remainder
|
|
|
// Ensure that e.g. the right-most tab of a shrunk tab-bar always reaches exactly at the same distance from the right-most edge of the tab bar separator.
|
|
// Ensure that e.g. the right-most tab of a shrunk tab-bar always reaches exactly at the same distance from the right-most edge of the tab bar separator.
|
|
|
width_excess = 0.0f;
|
|
width_excess = 0.0f;
|
|
|
for (int n = 0; n < count; n++)
|
|
for (int n = 0; n < count; n++)
|
|
@@ -1549,10 +1557,13 @@ void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_exc
|
|
|
width_excess += items[n].Width - width_rounded;
|
|
width_excess += items[n].Width - width_rounded;
|
|
|
items[n].Width = width_rounded;
|
|
items[n].Width = width_rounded;
|
|
|
}
|
|
}
|
|
|
- if (width_excess > 0.0f)
|
|
|
|
|
|
|
+ while (width_excess > 0.0f)
|
|
|
for (int n = 0; n < count; n++)
|
|
for (int n = 0; n < count; n++)
|
|
|
- if (items[n].Index < (int)(width_excess + 0.01f))
|
|
|
|
|
|
|
+ if (items[n].Width + 1.0f <= items[n].InitialWidth)
|
|
|
|
|
+ {
|
|
|
items[n].Width += 1.0f;
|
|
items[n].Width += 1.0f;
|
|
|
|
|
+ width_excess -= 1.0f;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
//-------------------------------------------------------------------------
|
|
@@ -1871,6 +1882,7 @@ bool ImGui::Combo(const char* label, int* current_item, const char* items_separa
|
|
|
// - DataTypeFormatString()
|
|
// - DataTypeFormatString()
|
|
|
// - DataTypeApplyOp()
|
|
// - DataTypeApplyOp()
|
|
|
// - DataTypeApplyOpFromText()
|
|
// - DataTypeApplyOpFromText()
|
|
|
|
|
+// - DataTypeCompare()
|
|
|
// - DataTypeClamp()
|
|
// - DataTypeClamp()
|
|
|
// - GetMinimumStepAtDecimalPrecision
|
|
// - GetMinimumStepAtDecimalPrecision
|
|
|
// - RoundScalarWithFormat<>()
|
|
// - RoundScalarWithFormat<>()
|
|
@@ -1910,9 +1922,9 @@ static const char* PatchFormatStringFloatToInt(const char* fmt)
|
|
|
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
|
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
|
|
if (fmt_start == fmt && fmt_end[0] == 0)
|
|
if (fmt_start == fmt && fmt_end[0] == 0)
|
|
|
return "%d";
|
|
return "%d";
|
|
|
- ImGuiContext& g = *GImGui;
|
|
|
|
|
- ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision.
|
|
|
|
|
- return g.TempBuffer;
|
|
|
|
|
|
|
+ const char* tmp_format;
|
|
|
|
|
+ ImFormatStringToTempBuffer(&tmp_format, NULL, "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision.
|
|
|
|
|
+ return tmp_format;
|
|
|
#else
|
|
#else
|
|
|
IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
|
|
IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
|
|
|
#endif
|
|
#endif
|
|
@@ -2178,7 +2190,10 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const
|
|
|
else if (g.ActiveIdSource == ImGuiInputSource_Nav)
|
|
else if (g.ActiveIdSource == ImGuiInputSource_Nav)
|
|
|
{
|
|
{
|
|
|
const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
|
|
const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
|
|
|
- adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiNavReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis];
|
|
|
|
|
|
|
+ const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
|
|
|
|
|
+ const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
|
|
|
|
|
+ const float tweak_factor = tweak_slow ? 1.0f / 1.0f : tweak_fast ? 10.0f : 1.0f;
|
|
|
|
|
+ adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor;
|
|
|
v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
|
|
v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
|
|
|
}
|
|
}
|
|
|
adjust_delta *= v_speed;
|
|
adjust_delta *= v_speed;
|
|
@@ -2276,6 +2291,7 @@ bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v
|
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiContext& g = *GImGui;
|
|
|
if (g.ActiveId == id)
|
|
if (g.ActiveId == id)
|
|
|
{
|
|
{
|
|
|
|
|
+ // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation.
|
|
|
if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
|
|
if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
|
|
|
ClearActiveID();
|
|
ClearActiveID();
|
|
|
else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
|
|
else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
|
|
@@ -2332,26 +2348,20 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
|
|
|
else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
|
|
else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
|
|
|
format = PatchFormatStringFloatToInt(format);
|
|
format = PatchFormatStringFloatToInt(format);
|
|
|
|
|
|
|
|
- // Tabbing or CTRL-clicking on Drag turns it into an InputText
|
|
|
|
|
const bool hovered = ItemHoverable(frame_bb, id);
|
|
const bool hovered = ItemHoverable(frame_bb, id);
|
|
|
bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
|
|
bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
|
|
|
if (!temp_input_is_active)
|
|
if (!temp_input_is_active)
|
|
|
{
|
|
{
|
|
|
|
|
+ // Tabbing or CTRL-clicking on Drag turns it into an InputText
|
|
|
const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0;
|
|
const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0;
|
|
|
const bool clicked = (hovered && g.IO.MouseClicked[0]);
|
|
const bool clicked = (hovered && g.IO.MouseClicked[0]);
|
|
|
const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2);
|
|
const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2);
|
|
|
- if (input_requested_by_tabbing || clicked || double_clicked || g.NavActivateId == id || g.NavActivateInputId == id)
|
|
|
|
|
- {
|
|
|
|
|
- SetActiveID(id, window);
|
|
|
|
|
- SetFocusID(id, window);
|
|
|
|
|
- FocusWindow(window);
|
|
|
|
|
- g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
|
|
|
|
|
- if (temp_input_allowed)
|
|
|
|
|
- if (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || double_clicked || g.NavActivateInputId == id)
|
|
|
|
|
- temp_input_is_active = true;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const bool make_active = (input_requested_by_tabbing || clicked || double_clicked || g.NavActivateId == id || g.NavActivateInputId == id);
|
|
|
|
|
+ if (make_active && temp_input_allowed)
|
|
|
|
|
+ if (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || double_clicked || g.NavActivateInputId == id)
|
|
|
|
|
+ temp_input_is_active = true;
|
|
|
|
|
|
|
|
- // Experimental: simple click (without moving) turns Drag into an InputText
|
|
|
|
|
|
|
+ // (Optional) simple click (without moving) turns Drag into an InputText
|
|
|
if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)
|
|
if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)
|
|
|
if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
|
|
if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
|
|
|
{
|
|
{
|
|
@@ -2359,6 +2369,14 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
|
|
|
g.NavActivateFlags = ImGuiActivateFlags_PreferInput;
|
|
g.NavActivateFlags = ImGuiActivateFlags_PreferInput;
|
|
|
temp_input_is_active = true;
|
|
temp_input_is_active = true;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ if (make_active && !temp_input_is_active)
|
|
|
|
|
+ {
|
|
|
|
|
+ SetActiveID(id, window);
|
|
|
|
|
+ SetFocusID(id, window);
|
|
|
|
|
+ FocusWindow(window);
|
|
|
|
|
+ g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (temp_input_is_active)
|
|
if (temp_input_is_active)
|
|
@@ -2613,7 +2631,6 @@ float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, T
|
|
|
v_max_fudged = -logarithmic_zero_epsilon;
|
|
v_max_fudged = -logarithmic_zero_epsilon;
|
|
|
|
|
|
|
|
float result;
|
|
float result;
|
|
|
-
|
|
|
|
|
if (v_clamped <= v_min_fudged)
|
|
if (v_clamped <= v_min_fudged)
|
|
|
result = 0.0f; // Workaround for values that are in-range but below our fudge
|
|
result = 0.0f; // Workaround for values that are in-range but below our fudge
|
|
|
else if (v_clamped >= v_max_fudged)
|
|
else if (v_clamped >= v_max_fudged)
|
|
@@ -2637,84 +2654,74 @@ float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, T
|
|
|
|
|
|
|
|
return flipped ? (1.0f - result) : result;
|
|
return flipped ? (1.0f - result) : result;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // Linear slider
|
|
|
|
|
- return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min));
|
|
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ // Linear slider
|
|
|
|
|
+ return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min));
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT)
|
|
// Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT)
|
|
|
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
|
|
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
|
|
|
TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
|
|
TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
|
|
|
{
|
|
{
|
|
|
- if (v_min == v_max)
|
|
|
|
|
|
|
+ // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct"
|
|
|
|
|
+ // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler.
|
|
|
|
|
+ if (t <= 0.0f || v_min == v_max)
|
|
|
return v_min;
|
|
return v_min;
|
|
|
- const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
|
|
|
|
|
|
|
+ if (t >= 1.0f)
|
|
|
|
|
+ return v_max;
|
|
|
|
|
|
|
|
- TYPE result;
|
|
|
|
|
|
|
+ TYPE result = (TYPE)0;
|
|
|
if (is_logarithmic)
|
|
if (is_logarithmic)
|
|
|
{
|
|
{
|
|
|
- // We special-case the extents because otherwise our fudging can lead to "mathematically correct" but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value
|
|
|
|
|
- if (t <= 0.0f)
|
|
|
|
|
- result = v_min;
|
|
|
|
|
- else if (t >= 1.0f)
|
|
|
|
|
- result = v_max;
|
|
|
|
|
- else
|
|
|
|
|
- {
|
|
|
|
|
- bool flipped = v_max < v_min; // Check if range is "backwards"
|
|
|
|
|
-
|
|
|
|
|
- // Fudge min/max to avoid getting silly results close to zero
|
|
|
|
|
- FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
|
|
|
|
|
- FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
|
|
|
|
|
|
|
+ // Fudge min/max to avoid getting silly results close to zero
|
|
|
|
|
+ FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
|
|
|
|
|
+ FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
|
|
|
|
|
|
|
|
- if (flipped)
|
|
|
|
|
- ImSwap(v_min_fudged, v_max_fudged);
|
|
|
|
|
|
|
+ const bool flipped = v_max < v_min; // Check if range is "backwards"
|
|
|
|
|
+ if (flipped)
|
|
|
|
|
+ ImSwap(v_min_fudged, v_max_fudged);
|
|
|
|
|
|
|
|
- // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
|
|
|
|
|
- if ((v_max == 0.0f) && (v_min < 0.0f))
|
|
|
|
|
- v_max_fudged = -logarithmic_zero_epsilon;
|
|
|
|
|
|
|
+ // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
|
|
|
|
|
+ if ((v_max == 0.0f) && (v_min < 0.0f))
|
|
|
|
|
+ v_max_fudged = -logarithmic_zero_epsilon;
|
|
|
|
|
|
|
|
- float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range
|
|
|
|
|
|
|
+ float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range
|
|
|
|
|
|
|
|
- if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts
|
|
|
|
|
- {
|
|
|
|
|
- float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space
|
|
|
|
|
- float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
|
|
|
|
|
- float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
|
|
|
|
|
- if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R)
|
|
|
|
|
- result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise)
|
|
|
|
|
- else if (t_with_flip < zero_point_center)
|
|
|
|
|
- result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L))));
|
|
|
|
|
- else
|
|
|
|
|
- result = (TYPE)(logarithmic_zero_epsilon * ImPow(v_max_fudged / logarithmic_zero_epsilon, (FLOATTYPE)((t_with_flip - zero_point_snap_R) / (1.0f - zero_point_snap_R))));
|
|
|
|
|
- }
|
|
|
|
|
- else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
|
|
|
|
|
- result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip)));
|
|
|
|
|
|
|
+ if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts
|
|
|
|
|
+ {
|
|
|
|
|
+ float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space
|
|
|
|
|
+ float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
|
|
|
|
|
+ float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
|
|
|
|
|
+ if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R)
|
|
|
|
|
+ result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise)
|
|
|
|
|
+ else if (t_with_flip < zero_point_center)
|
|
|
|
|
+ result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L))));
|
|
|
else
|
|
else
|
|
|
- result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip));
|
|
|
|
|
|
|
+ result = (TYPE)(logarithmic_zero_epsilon * ImPow(v_max_fudged / logarithmic_zero_epsilon, (FLOATTYPE)((t_with_flip - zero_point_snap_R) / (1.0f - zero_point_snap_R))));
|
|
|
}
|
|
}
|
|
|
|
|
+ else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
|
|
|
|
|
+ result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip)));
|
|
|
|
|
+ else
|
|
|
|
|
+ result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip));
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
// Linear slider
|
|
// Linear slider
|
|
|
|
|
+ const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
|
|
|
if (is_floating_point)
|
|
if (is_floating_point)
|
|
|
{
|
|
{
|
|
|
result = ImLerp(v_min, v_max, t);
|
|
result = ImLerp(v_min, v_max, t);
|
|
|
}
|
|
}
|
|
|
- else
|
|
|
|
|
|
|
+ else if (t < 1.0)
|
|
|
{
|
|
{
|
|
|
// - For integer values we want the clicking position to match the grab box so we round above
|
|
// - For integer values we want the clicking position to match the grab box so we round above
|
|
|
// This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
|
|
// This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
|
|
|
// - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64
|
|
// - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64
|
|
|
// range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits.
|
|
// range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits.
|
|
|
- if (t < 1.0)
|
|
|
|
|
- {
|
|
|
|
|
- FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t;
|
|
|
|
|
- result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5)));
|
|
|
|
|
- }
|
|
|
|
|
- else
|
|
|
|
|
- {
|
|
|
|
|
- result = v_max;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t;
|
|
|
|
|
+ result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5)));
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -2736,7 +2743,7 @@ float SliderCalcRatioFromValueFloat(ImGuiDataType data_type, float v, float v_mi
|
|
|
|
|
|
|
|
} // namespace ImGui
|
|
} // namespace ImGui
|
|
|
|
|
|
|
|
-// FIXME: Move more of the code into SliderBehavior()
|
|
|
|
|
|
|
+// FIXME: Try to move more of the code into shared SliderBehavior()
|
|
|
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
|
|
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
|
|
|
bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb)
|
|
bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb)
|
|
|
{
|
|
{
|
|
@@ -2746,13 +2753,14 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ
|
|
|
const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
|
|
const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
|
|
|
const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
|
|
const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
|
|
|
const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
|
|
const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
|
|
|
|
|
+ const SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);
|
|
|
|
|
|
|
|
- const float grab_padding = 2.0f;
|
|
|
|
|
|
|
+ // Calculate bounds
|
|
|
|
|
+ const float grab_padding = 2.0f; // FIXME: Should be part of style.
|
|
|
const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
|
|
const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
|
|
|
float grab_sz = style.GrabMinSize;
|
|
float grab_sz = style.GrabMinSize;
|
|
|
- SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);
|
|
|
|
|
- if (!is_floating_point && v_range >= 0) // v_range < 0 may happen on integer overflows
|
|
|
|
|
- grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
|
|
|
|
|
|
|
+ if (!is_floating_point && v_range >= 0) // v_range < 0 may happen on integer overflows
|
|
|
|
|
+ grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
|
|
|
grab_sz = ImMin(grab_sz, slider_sz);
|
|
grab_sz = ImMin(grab_sz, slider_sz);
|
|
|
const float slider_usable_sz = slider_sz - grab_sz;
|
|
const float slider_usable_sz = slider_sz - grab_sz;
|
|
|
const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f;
|
|
const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f;
|
|
@@ -2783,7 +2791,17 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
const float mouse_abs_pos = g.IO.MousePos[axis];
|
|
const float mouse_abs_pos = g.IO.MousePos[axis];
|
|
|
- clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
|
|
|
|
|
|
|
+ if (g.ActiveIdIsJustActivated)
|
|
|
|
|
+ {
|
|
|
|
|
+ float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
|
|
|
|
|
+ if (axis == ImGuiAxis_Y)
|
|
|
|
|
+ grab_t = 1.0f - grab_t;
|
|
|
|
|
+ const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
|
|
|
|
|
+ const bool clicked_around_grab = (mouse_abs_pos >= grab_pos - grab_sz * 0.5f - 1.0f) && (mouse_abs_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here.
|
|
|
|
|
+ g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (slider_usable_sz > 0.0f)
|
|
|
|
|
+ clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz);
|
|
|
if (axis == ImGuiAxis_Y)
|
|
if (axis == ImGuiAxis_Y)
|
|
|
clicked_t = 1.0f - clicked_t;
|
|
clicked_t = 1.0f - clicked_t;
|
|
|
set_new_value = true;
|
|
set_new_value = true;
|
|
@@ -2797,25 +2815,26 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ
|
|
|
g.SliderCurrentAccumDirty = false;
|
|
g.SliderCurrentAccumDirty = false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const ImVec2 input_delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiNavReadMode_RepeatFast, 0.0f, 0.0f);
|
|
|
|
|
- float input_delta = (axis == ImGuiAxis_X) ? input_delta2.x : -input_delta2.y;
|
|
|
|
|
|
|
+ float input_delta = (axis == ImGuiAxis_X) ? GetNavTweakPressedAmount(axis) : -GetNavTweakPressedAmount(axis);
|
|
|
if (input_delta != 0.0f)
|
|
if (input_delta != 0.0f)
|
|
|
{
|
|
{
|
|
|
|
|
+ const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
|
|
|
|
|
+ const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
|
|
|
const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
|
|
const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
|
|
|
if (decimal_precision > 0)
|
|
if (decimal_precision > 0)
|
|
|
{
|
|
{
|
|
|
input_delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds
|
|
input_delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds
|
|
|
- if (IsNavInputDown(ImGuiNavInput_TweakSlow))
|
|
|
|
|
|
|
+ if (tweak_slow)
|
|
|
input_delta /= 10.0f;
|
|
input_delta /= 10.0f;
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
- if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow))
|
|
|
|
|
|
|
+ if ((v_range >= -100.0f && v_range <= 100.0f) || tweak_slow)
|
|
|
input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
|
|
input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
|
|
|
else
|
|
else
|
|
|
input_delta /= 100.0f;
|
|
input_delta /= 100.0f;
|
|
|
}
|
|
}
|
|
|
- if (IsNavInputDown(ImGuiNavInput_TweakFast))
|
|
|
|
|
|
|
+ if (tweak_fast)
|
|
|
input_delta *= 10.0f;
|
|
input_delta *= 10.0f;
|
|
|
|
|
|
|
|
g.SliderCurrentAccum += input_delta;
|
|
g.SliderCurrentAccum += input_delta;
|
|
@@ -2903,6 +2922,7 @@ bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type
|
|
|
// Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
|
|
// Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
|
|
|
IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flag! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.");
|
|
IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flag! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.");
|
|
|
|
|
|
|
|
|
|
+ // Those are the things we can do easily outside the SliderBehaviorT<> template, saves code generation.
|
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiContext& g = *GImGui;
|
|
|
if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
|
|
if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
|
|
|
return false;
|
|
return false;
|
|
@@ -2965,21 +2985,24 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
|
|
|
else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
|
|
else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
|
|
|
format = PatchFormatStringFloatToInt(format);
|
|
format = PatchFormatStringFloatToInt(format);
|
|
|
|
|
|
|
|
- // Tabbing or CTRL-clicking on Slider turns it into an input box
|
|
|
|
|
const bool hovered = ItemHoverable(frame_bb, id);
|
|
const bool hovered = ItemHoverable(frame_bb, id);
|
|
|
bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
|
|
bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
|
|
|
if (!temp_input_is_active)
|
|
if (!temp_input_is_active)
|
|
|
{
|
|
{
|
|
|
|
|
+ // Tabbing or CTRL-clicking on Slider turns it into an input box
|
|
|
const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0;
|
|
const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0;
|
|
|
const bool clicked = (hovered && g.IO.MouseClicked[0]);
|
|
const bool clicked = (hovered && g.IO.MouseClicked[0]);
|
|
|
- if (input_requested_by_tabbing || clicked || g.NavActivateId == id || g.NavActivateInputId == id)
|
|
|
|
|
|
|
+ const bool make_active = (input_requested_by_tabbing || clicked || g.NavActivateId == id || g.NavActivateInputId == id);
|
|
|
|
|
+ if (make_active && temp_input_allowed)
|
|
|
|
|
+ if (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || g.NavActivateInputId == id)
|
|
|
|
|
+ temp_input_is_active = true;
|
|
|
|
|
+
|
|
|
|
|
+ if (make_active && !temp_input_is_active)
|
|
|
{
|
|
{
|
|
|
SetActiveID(id, window);
|
|
SetActiveID(id, window);
|
|
|
SetFocusID(id, window);
|
|
SetFocusID(id, window);
|
|
|
FocusWindow(window);
|
|
FocusWindow(window);
|
|
|
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
|
|
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
|
|
|
- if (temp_input_allowed && (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || g.NavActivateInputId == id))
|
|
|
|
|
- temp_input_is_active = true;
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -3586,6 +3609,7 @@ bool ImGui::InputDouble(const char* label, double* v, double step, double step_f
|
|
|
// - InputTextReindexLines() [Internal]
|
|
// - InputTextReindexLines() [Internal]
|
|
|
// - InputTextReindexLinesRange() [Internal]
|
|
// - InputTextReindexLinesRange() [Internal]
|
|
|
// - InputTextEx() [Internal]
|
|
// - InputTextEx() [Internal]
|
|
|
|
|
+// - DebugNodeInputTextState() [Internal]
|
|
|
//-------------------------------------------------------------------------
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
|
|
bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
|
bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
|
@@ -3601,7 +3625,7 @@ bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, co
|
|
|
|
|
|
|
|
bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
|
bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
|
|
{
|
|
{
|
|
|
- IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
|
|
|
|
|
|
|
+ IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() or InputTextEx() manually if you need multi-line + hint.
|
|
|
return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
|
|
return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -3692,13 +3716,10 @@ static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)
|
|
|
static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (!is_separator(obj->TextW[idx - 1]) && is_separator(obj->TextW[idx])) : 1; }
|
|
static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (!is_separator(obj->TextW[idx - 1]) && is_separator(obj->TextW[idx])) : 1; }
|
|
|
static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
|
|
static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
|
|
|
static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
|
|
static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
|
|
|
-#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
|
|
|
|
|
-#ifdef __APPLE__ // FIXME: Move setting to IO structure
|
|
|
|
|
-#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_MAC
|
|
|
|
|
-#else
|
|
|
|
|
static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
|
|
static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
|
|
|
-#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_WIN
|
|
|
|
|
-#endif
|
|
|
|
|
|
|
+static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { if (ImGui::GetIO().ConfigMacOSXBehaviors) return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx); else return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); }
|
|
|
|
|
+#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
|
|
|
|
|
+#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
|
|
|
|
|
|
|
|
static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
|
|
static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
|
|
|
{
|
|
{
|
|
@@ -3943,6 +3964,41 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// Find the shortest single replacement we can make to get the new text from the old text.
|
|
|
|
|
+// Important: needs to be run before TextW is rewritten with the new characters because calling STB_TEXTEDIT_GETCHAR() at the end.
|
|
|
|
|
+// FIXME: Ideally we should transition toward (1) making InsertChars()/DeleteChars() update undo-stack (2) discourage (and keep reconcile) or obsolete (and remove reconcile) accessing buffer directly.
|
|
|
|
|
+static void InputTextReconcileUndoStateAfterUserCallback(ImGuiInputTextState* state, const char* new_buf_a, int new_length_a)
|
|
|
|
|
+{
|
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
|
+ const ImWchar* old_buf = state->TextW.Data;
|
|
|
|
|
+ const int old_length = state->CurLenW;
|
|
|
|
|
+ const int new_length = ImTextCountCharsFromUtf8(new_buf_a, new_buf_a + new_length_a);
|
|
|
|
|
+ g.TempBuffer.reserve_discard((new_length + 1) * sizeof(ImWchar));
|
|
|
|
|
+ ImWchar* new_buf = (ImWchar*)(void*)g.TempBuffer.Data;
|
|
|
|
|
+ ImTextStrFromUtf8(new_buf, new_length + 1, new_buf_a, new_buf_a + new_length_a);
|
|
|
|
|
+
|
|
|
|
|
+ const int shorter_length = ImMin(old_length, new_length);
|
|
|
|
|
+ int first_diff;
|
|
|
|
|
+ for (first_diff = 0; first_diff < shorter_length; first_diff++)
|
|
|
|
|
+ if (old_buf[first_diff] != new_buf[first_diff])
|
|
|
|
|
+ break;
|
|
|
|
|
+ if (first_diff == old_length && first_diff == new_length)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ int old_last_diff = old_length - 1;
|
|
|
|
|
+ int new_last_diff = new_length - 1;
|
|
|
|
|
+ for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--)
|
|
|
|
|
+ if (old_buf[old_last_diff] != new_buf[new_last_diff])
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ const int insert_len = new_last_diff - first_diff + 1;
|
|
|
|
|
+ const int delete_len = old_last_diff - first_diff + 1;
|
|
|
|
|
+ if (insert_len > 0 || delete_len > 0)
|
|
|
|
|
+ if (STB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->Stb.undostate, first_diff, delete_len, insert_len))
|
|
|
|
|
+ for (int i = 0; i < delete_len; i++)
|
|
|
|
|
+ p[i] = ImStb::STB_TEXTEDIT_GETCHAR(state, first_diff + i);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// Edit a string of text
|
|
// Edit a string of text
|
|
|
// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
|
|
// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
|
|
|
// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
|
|
// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
|
|
@@ -4063,17 +4119,21 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
|
|
state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
|
|
|
memcpy(state->InitialTextA.Data, buf, buf_len + 1);
|
|
memcpy(state->InitialTextA.Data, buf, buf_len + 1);
|
|
|
|
|
|
|
|
|
|
+ // Preserve cursor position and undo/redo stack if we come back to same widget
|
|
|
|
|
+ // FIXME: Since we reworked this on 2022/06, may want to differenciate recycle_cursor vs recycle_undostate?
|
|
|
|
|
+ bool recycle_state = (state->ID == id && !init_changed_specs);
|
|
|
|
|
+ if (recycle_state && (state->CurLenA != buf_len || (state->TextAIsValid && strncmp(state->TextA.Data, buf, buf_len) != 0)))
|
|
|
|
|
+ recycle_state = false;
|
|
|
|
|
+
|
|
|
// Start edition
|
|
// Start edition
|
|
|
const char* buf_end = NULL;
|
|
const char* buf_end = NULL;
|
|
|
|
|
+ state->ID = id;
|
|
|
state->TextW.resize(buf_size + 1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string.
|
|
state->TextW.resize(buf_size + 1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string.
|
|
|
state->TextA.resize(0);
|
|
state->TextA.resize(0);
|
|
|
state->TextAIsValid = false; // TextA is not valid yet (we will display buf until then)
|
|
state->TextAIsValid = false; // TextA is not valid yet (we will display buf until then)
|
|
|
state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end);
|
|
state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end);
|
|
|
state->CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8.
|
|
state->CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8.
|
|
|
|
|
|
|
|
- // Preserve cursor position and undo/redo stack if we come back to same widget
|
|
|
|
|
- // FIXME: For non-readonly widgets we might be able to require that TextAIsValid && TextA == buf ? (untested) and discard undo stack if user buffer has changed.
|
|
|
|
|
- const bool recycle_state = (state->ID == id && !init_changed_specs);
|
|
|
|
|
if (recycle_state)
|
|
if (recycle_state)
|
|
|
{
|
|
{
|
|
|
// Recycle existing cursor/selection/undo stack but clamp position
|
|
// Recycle existing cursor/selection/undo stack but clamp position
|
|
@@ -4082,7 +4142,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
- state->ID = id;
|
|
|
|
|
state->ScrollX = 0.0f;
|
|
state->ScrollX = 0.0f;
|
|
|
stb_textedit_initialize_state(&state->Stb, !is_multiline);
|
|
stb_textedit_initialize_state(&state->Stb, !is_multiline);
|
|
|
}
|
|
}
|
|
@@ -4109,11 +4168,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
FocusWindow(window);
|
|
FocusWindow(window);
|
|
|
|
|
|
|
|
// Declare our inputs
|
|
// Declare our inputs
|
|
|
- IM_ASSERT(ImGuiNavInput_COUNT < 32);
|
|
|
|
|
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
|
|
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
|
|
|
if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
|
|
if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
|
|
|
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
|
|
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
|
|
|
- g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);
|
|
|
|
|
|
|
+ SetActiveIdUsingKey(ImGuiKey_Escape);
|
|
|
|
|
+ SetActiveIdUsingKey(ImGuiKey_NavGamepadCancel);
|
|
|
SetActiveIdUsingKey(ImGuiKey_Home);
|
|
SetActiveIdUsingKey(ImGuiKey_Home);
|
|
|
SetActiveIdUsingKey(ImGuiKey_End);
|
|
SetActiveIdUsingKey(ImGuiKey_End);
|
|
|
if (is_multiline)
|
|
if (is_multiline)
|
|
@@ -4139,7 +4198,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
const bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
|
|
const bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
|
|
|
bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
|
|
bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
|
|
|
bool value_changed = false;
|
|
bool value_changed = false;
|
|
|
- bool enter_pressed = false;
|
|
|
|
|
|
|
+ bool validated = false;
|
|
|
|
|
|
|
|
// When read-only we always use the live data passed to the function
|
|
// When read-only we always use the live data passed to the function
|
|
|
// FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :(
|
|
// FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :(
|
|
@@ -4251,7 +4310,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
if (state->SelectedAllMouseLock && !io.MouseDown[0])
|
|
if (state->SelectedAllMouseLock && !io.MouseDown[0])
|
|
|
state->SelectedAllMouseLock = false;
|
|
state->SelectedAllMouseLock = false;
|
|
|
|
|
|
|
|
- // We except backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336)
|
|
|
|
|
|
|
+ // We expect 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)
|
|
// (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);
|
|
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 ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressed(ImGuiKey_Tab) && !ignore_char_inputs && !io.KeyShift && !is_readonly)
|
|
@@ -4304,11 +4363,12 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
const bool is_paste = ((is_shortcut_key && IsKeyPressed(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressed(ImGuiKey_Insert))) && !is_readonly;
|
|
const bool is_paste = ((is_shortcut_key && IsKeyPressed(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressed(ImGuiKey_Insert))) && !is_readonly;
|
|
|
const bool is_undo = ((is_shortcut_key && IsKeyPressed(ImGuiKey_Z)) && !is_readonly && is_undoable);
|
|
const bool is_undo = ((is_shortcut_key && IsKeyPressed(ImGuiKey_Z)) && !is_readonly && is_undoable);
|
|
|
const bool is_redo = ((is_shortcut_key && IsKeyPressed(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressed(ImGuiKey_Z))) && !is_readonly && is_undoable;
|
|
const bool is_redo = ((is_shortcut_key && IsKeyPressed(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressed(ImGuiKey_Z))) && !is_readonly && is_undoable;
|
|
|
|
|
+ const bool is_select_all = is_shortcut_key && IsKeyPressed(ImGuiKey_A);
|
|
|
|
|
|
|
|
// We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful.
|
|
// We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful.
|
|
|
- const bool is_validate_enter = IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_KeypadEnter);
|
|
|
|
|
- const bool is_validate_nav = (IsNavInputTest(ImGuiNavInput_Activate, ImGuiNavReadMode_Pressed) && !IsKeyPressed(ImGuiKey_Space)) || IsNavInputTest(ImGuiNavInput_Input, ImGuiNavReadMode_Pressed);
|
|
|
|
|
- const bool is_cancel = IsKeyPressed(ImGuiKey_Escape) || IsNavInputTest(ImGuiNavInput_Cancel, ImGuiNavReadMode_Pressed);
|
|
|
|
|
|
|
+ const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true);
|
|
|
|
|
+ const bool is_gamepad_validate = IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false);
|
|
|
|
|
+ const bool is_cancel = IsKeyPressed(ImGuiKey_Escape, false) || IsKeyPressed(ImGuiKey_NavGamepadCancel, false);
|
|
|
|
|
|
|
|
if (IsKeyPressed(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); }
|
|
if (IsKeyPressed(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 (IsKeyPressed(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 (IsKeyPressed(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); }
|
|
@@ -4330,12 +4390,17 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
}
|
|
}
|
|
|
state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
|
|
state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
|
|
|
}
|
|
}
|
|
|
- else if (is_validate_enter)
|
|
|
|
|
|
|
+ else if (is_enter_pressed || is_gamepad_validate)
|
|
|
{
|
|
{
|
|
|
|
|
+ // Determine if we turn Enter into a \n character
|
|
|
bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
|
|
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))
|
|
|
|
|
|
|
+ if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
|
|
|
{
|
|
{
|
|
|
- enter_pressed = clear_active_id = true;
|
|
|
|
|
|
|
+ validated = true;
|
|
|
|
|
+ if (io.ConfigInputTextEnterKeepActive && !is_multiline)
|
|
|
|
|
+ state->SelectAll(); // No need to scroll
|
|
|
|
|
+ else
|
|
|
|
|
+ clear_active_id = true;
|
|
|
}
|
|
}
|
|
|
else if (!is_readonly)
|
|
else if (!is_readonly)
|
|
|
{
|
|
{
|
|
@@ -4344,11 +4409,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
state->OnKeyPressed((int)c);
|
|
state->OnKeyPressed((int)c);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- else if (is_validate_nav)
|
|
|
|
|
- {
|
|
|
|
|
- IM_ASSERT(!is_validate_enter);
|
|
|
|
|
- enter_pressed = clear_active_id = true;
|
|
|
|
|
- }
|
|
|
|
|
else if (is_cancel)
|
|
else if (is_cancel)
|
|
|
{
|
|
{
|
|
|
clear_active_id = cancel_edit = true;
|
|
clear_active_id = cancel_edit = true;
|
|
@@ -4358,7 +4418,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
|
|
state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
|
|
|
state->ClearSelection();
|
|
state->ClearSelection();
|
|
|
}
|
|
}
|
|
|
- else if (is_shortcut_key && IsKeyPressed(ImGuiKey_A))
|
|
|
|
|
|
|
+ else if (is_select_all)
|
|
|
{
|
|
{
|
|
|
state->SelectAll();
|
|
state->SelectAll();
|
|
|
state->CursorFollow = true;
|
|
state->CursorFollow = true;
|
|
@@ -4440,22 +4500,24 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Apply ASCII value
|
|
|
|
|
+ if (!is_readonly)
|
|
|
|
|
+ {
|
|
|
|
|
+ state->TextAIsValid = true;
|
|
|
|
|
+ state->TextA.resize(state->TextW.Size * 4 + 1);
|
|
|
|
|
+ ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
|
|
// When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
|
|
|
// If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail.
|
|
// If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail.
|
|
|
// This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize).
|
|
// This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize).
|
|
|
- bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
|
|
|
|
|
|
|
+ const bool apply_edit_back_to_user_buffer = !cancel_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
|
|
|
if (apply_edit_back_to_user_buffer)
|
|
if (apply_edit_back_to_user_buffer)
|
|
|
{
|
|
{
|
|
|
// Apply new value immediately - copy modified buffer back
|
|
// Apply new value immediately - copy modified buffer back
|
|
|
// Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
|
|
// Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
|
|
|
// FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
|
|
// FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
|
|
|
// FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
|
|
// FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
|
|
|
- if (!is_readonly)
|
|
|
|
|
- {
|
|
|
|
|
- state->TextAIsValid = true;
|
|
|
|
|
- state->TextA.resize(state->TextW.Size * 4 + 1);
|
|
|
|
|
- ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL);
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
// User callback
|
|
// User callback
|
|
|
if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)
|
|
if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)
|
|
@@ -4525,8 +4587,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
if (buf_dirty)
|
|
if (buf_dirty)
|
|
|
{
|
|
{
|
|
|
IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
|
|
IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
|
|
|
|
|
+ InputTextReconcileUndoStateAfterUserCallback(state, callback_data.Buf, callback_data.BufTextLen); // FIXME: Move the rest of this block inside function and rename to InputTextReconcileStateAfterUserCallback() ?
|
|
|
if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
|
|
if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
|
|
|
- state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
|
|
|
|
|
|
|
+ state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); // Worse case scenario resize
|
|
|
state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL);
|
|
state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL);
|
|
|
state->CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
|
|
state->CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
|
|
|
state->CursorAnimReset();
|
|
state->CursorAnimReset();
|
|
@@ -4568,7 +4631,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
|
|
apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
|
|
|
IM_ASSERT(apply_new_text_length <= buf_size);
|
|
IM_ASSERT(apply_new_text_length <= buf_size);
|
|
|
}
|
|
}
|
|
|
- //IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
|
|
|
|
|
|
|
+ //IMGUI_DEBUG_PRINT("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
|
|
|
|
|
|
|
|
// If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
|
|
// If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
|
|
|
ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
|
|
ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
|
|
@@ -4824,11 +4887,45 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
|
|
|
|
|
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
|
|
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
|
|
|
if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
|
|
if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
|
|
|
- return enter_pressed;
|
|
|
|
|
|
|
+ return validated;
|
|
|
else
|
|
else
|
|
|
return value_changed;
|
|
return value_changed;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state)
|
|
|
|
|
+{
|
|
|
|
|
+#ifndef IMGUI_DISABLE_DEBUG_TOOLS
|
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
|
+ ImStb::STB_TexteditState* stb_state = &state->Stb;
|
|
|
|
|
+ ImStb::StbUndoState* undo_state = &stb_state->undostate;
|
|
|
|
|
+ Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId);
|
|
|
|
|
+ Text("CurLenW: %d, CurLenA: %d, Cursor: %d, Selection: %d..%d", state->CurLenA, state->CurLenW, stb_state->cursor, stb_state->select_start, stb_state->select_end);
|
|
|
|
|
+ Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point);
|
|
|
|
|
+ if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 15), true)) // Visualize undo state
|
|
|
|
|
+ {
|
|
|
|
|
+ PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
|
|
|
|
+ for (int n = 0; n < STB_TEXTEDIT_UNDOSTATECOUNT; n++)
|
|
|
|
|
+ {
|
|
|
|
|
+ ImStb::StbUndoRecord* undo_rec = &undo_state->undo_rec[n];
|
|
|
|
|
+ const char undo_rec_type = (n < undo_state->undo_point) ? 'u' : (n >= undo_state->redo_point) ? 'r' : ' ';
|
|
|
|
|
+ if (undo_rec_type == ' ')
|
|
|
|
|
+ BeginDisabled();
|
|
|
|
|
+ char buf[64] = "";
|
|
|
|
|
+ if (undo_rec_type != ' ' && undo_rec->char_storage != -1)
|
|
|
|
|
+ ImTextStrToUtf8(buf, IM_ARRAYSIZE(buf), undo_state->undo_char + undo_rec->char_storage, undo_state->undo_char + undo_rec->char_storage + undo_rec->insert_length);
|
|
|
|
|
+ Text("%c [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \"%s\"",
|
|
|
|
|
+ undo_rec_type, n, undo_rec->where, undo_rec->insert_length, undo_rec->delete_length, undo_rec->char_storage, buf);
|
|
|
|
|
+ if (undo_rec_type == ' ')
|
|
|
|
|
+ EndDisabled();
|
|
|
|
|
+ }
|
|
|
|
|
+ PopStyleVar();
|
|
|
|
|
+ }
|
|
|
|
|
+ EndChild();
|
|
|
|
|
+#else
|
|
|
|
|
+ IM_UNUSED(state);
|
|
|
|
|
+#endif
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
//-------------------------------------------------------------------------
|
|
//-------------------------------------------------------------------------
|
|
|
// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
|
|
// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
|
|
|
//-------------------------------------------------------------------------
|
|
//-------------------------------------------------------------------------
|
|
@@ -5810,9 +5907,9 @@ bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char
|
|
|
if (window->SkipItems)
|
|
if (window->SkipItems)
|
|
|
return false;
|
|
return false;
|
|
|
|
|
|
|
|
- ImGuiContext& g = *GImGui;
|
|
|
|
|
- const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
|
|
|
|
|
- return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
|
|
|
|
|
|
|
+ const char* label, *label_end;
|
|
|
|
|
+ ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
|
|
|
|
|
+ return TreeNodeBehavior(window->GetID(str_id), flags, label, label_end);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
|
|
bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
|
|
@@ -5821,12 +5918,19 @@ bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char
|
|
|
if (window->SkipItems)
|
|
if (window->SkipItems)
|
|
|
return false;
|
|
return false;
|
|
|
|
|
|
|
|
|
|
+ const char* label, *label_end;
|
|
|
|
|
+ ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
|
|
|
|
|
+ return TreeNodeBehavior(window->GetID(ptr_id), flags, label, label_end);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void ImGui::TreeNodeSetOpen(ImGuiID id, bool open)
|
|
|
|
|
+{
|
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiContext& g = *GImGui;
|
|
|
- const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
|
|
|
|
|
- return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
|
|
|
|
|
|
|
+ ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;
|
|
|
|
|
+ storage->SetInt(id, open ? 1 : 0);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
|
|
|
|
|
|
|
+bool ImGui::TreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
|
|
|
{
|
|
{
|
|
|
if (flags & ImGuiTreeNodeFlags_Leaf)
|
|
if (flags & ImGuiTreeNodeFlags_Leaf)
|
|
|
return true;
|
|
return true;
|
|
@@ -5842,7 +5946,7 @@ bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
|
|
|
if (g.NextItemData.OpenCond & ImGuiCond_Always)
|
|
if (g.NextItemData.OpenCond & ImGuiCond_Always)
|
|
|
{
|
|
{
|
|
|
is_open = g.NextItemData.OpenVal;
|
|
is_open = g.NextItemData.OpenVal;
|
|
|
- storage->SetInt(id, is_open);
|
|
|
|
|
|
|
+ TreeNodeSetOpen(id, is_open);
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
@@ -5851,7 +5955,7 @@ bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
|
|
|
if (stored_value == -1)
|
|
if (stored_value == -1)
|
|
|
{
|
|
{
|
|
|
is_open = g.NextItemData.OpenVal;
|
|
is_open = g.NextItemData.OpenVal;
|
|
|
- storage->SetInt(id, is_open);
|
|
|
|
|
|
|
+ TreeNodeSetOpen(id, is_open);
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
@@ -5917,7 +6021,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
|
|
|
// For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
|
|
// For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
|
|
|
// This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
|
|
// This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
|
|
|
const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
|
|
const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
|
|
|
- bool is_open = TreeNodeBehaviorIsOpen(id, flags);
|
|
|
|
|
|
|
+ bool is_open = TreeNodeUpdateNextOpen(id, flags);
|
|
|
if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
|
|
if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
|
|
|
window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth);
|
|
window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth);
|
|
|
|
|
|
|
@@ -6878,14 +6982,19 @@ static bool IsRootOfOpenMenuSet()
|
|
|
if ((g.OpenPopupStack.Size <= g.BeginPopupStack.Size) || (window->Flags & ImGuiWindowFlags_ChildMenu))
|
|
if ((g.OpenPopupStack.Size <= g.BeginPopupStack.Size) || (window->Flags & ImGuiWindowFlags_ChildMenu))
|
|
|
return false;
|
|
return false;
|
|
|
|
|
|
|
|
- // Initially we used 'OpenParentId' to differentiate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) based on parent ID.
|
|
|
|
|
|
|
+ // Initially we used 'upper_popup->OpenParentId == window->IDStack.back()' to differentiate multiple menu sets from each others
|
|
|
|
|
+ // (e.g. inside menu bar vs loose menu items) based on parent ID.
|
|
|
// This would however prevent the use of e.g. PuhsID() user code submitting menus.
|
|
// This would however prevent the use of e.g. PuhsID() user code submitting menus.
|
|
|
// Previously this worked between popup and a first child menu because the first child menu always had the _ChildWindow flag,
|
|
// Previously this worked between popup and a first child menu because the first child menu always had the _ChildWindow flag,
|
|
|
// making hovering on parent popup possible while first child menu was focused - but this was generally a bug with other side effects.
|
|
// making hovering on parent popup possible while first child menu was focused - but this was generally a bug with other side effects.
|
|
|
// Instead we don't treat Popup specifically (in order to consistently support menu features in them), maybe the first child menu of a Popup
|
|
// Instead we don't treat Popup specifically (in order to consistently support menu features in them), maybe the first child menu of a Popup
|
|
|
- // doesn't have the _ChildWindow flag, and we rely on this IsRootOfOpenMenuSet() check to allow hovering between root window/popup and first chilld menu.
|
|
|
|
|
|
|
+ // doesn't have the _ChildWindow flag, and we rely on this IsRootOfOpenMenuSet() check to allow hovering between root window/popup and first child menu.
|
|
|
|
|
+ // In the end, lack of ID check made it so we could no longer differentiate between separate menu sets. To compensate for that, we at least check parent window nav layer.
|
|
|
|
|
+ // This fixes the most common case of menu opening on hover when moving between window content and menu bar. Multiple different menu sets in same nav layer would still
|
|
|
|
|
+ // open on hover, but that should be a lesser problem, because if such menus are close in proximity in window content then it won't feel weird and if they are far apart
|
|
|
|
|
+ // it likely won't be a problem anyone runs into.
|
|
|
const ImGuiPopupData* upper_popup = &g.OpenPopupStack[g.BeginPopupStack.Size];
|
|
const ImGuiPopupData* upper_popup = &g.OpenPopupStack[g.BeginPopupStack.Size];
|
|
|
- return (/*upper_popup->OpenParentId == window->IDStack.back() &&*/ upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu));
|
|
|
|
|
|
|
+ return (window->DC.NavLayerCurrent == upper_popup->ParentNavLayer && upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
|
|
bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
|
|
@@ -6900,7 +7009,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
|
|
|
bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None);
|
|
bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None);
|
|
|
|
|
|
|
|
// Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
|
|
// Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
|
|
|
- // The first menu in a hierarchy isn't so hovering doesn't get accross (otherwise e.g. resizing borders with ImGuiButtonFlags_FlattenChildren would react), but top-most BeginMenu() will bypass that limitation.
|
|
|
|
|
|
|
+ // The first menu in a hierarchy isn't so hovering doesn't get across (otherwise e.g. resizing borders with ImGuiButtonFlags_FlattenChildren would react), but top-most BeginMenu() will bypass that limitation.
|
|
|
ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
|
|
ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
|
|
|
if (window->Flags & ImGuiWindowFlags_ChildMenu)
|
|
if (window->Flags & ImGuiWindowFlags_ChildMenu)
|
|
|
flags |= ImGuiWindowFlags_ChildWindow;
|
|
flags |= ImGuiWindowFlags_ChildWindow;
|
|
@@ -7477,7 +7586,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
|
|
|
// and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
|
|
// 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 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) ? false : true;
|
|
|
- tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x;
|
|
|
|
|
|
|
+ tab->ContentWidth = (tab->RequestedWidth > 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button).x;
|
|
|
|
|
|
|
|
int section_n = TabItemGetSectionIdx(tab);
|
|
int section_n = TabItemGetSectionIdx(tab);
|
|
|
ImGuiTabBarSection* section = §ions[section_n];
|
|
ImGuiTabBarSection* section = §ions[section_n];
|
|
@@ -7486,9 +7595,9 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
|
|
|
|
|
|
|
|
// Store data so we can build an array sorted by width if we need to shrink tabs down
|
|
// Store data so we can build an array sorted by width if we need to shrink tabs down
|
|
|
IM_MSVC_WARNING_SUPPRESS(6385);
|
|
IM_MSVC_WARNING_SUPPRESS(6385);
|
|
|
- int shrink_buffer_index = shrink_buffer_indexes[section_n]++;
|
|
|
|
|
- g.ShrinkWidthBuffer[shrink_buffer_index].Index = tab_n;
|
|
|
|
|
- g.ShrinkWidthBuffer[shrink_buffer_index].Width = tab->ContentWidth;
|
|
|
|
|
|
|
+ ImGuiShrinkWidthItem* shrink_width_item = &g.ShrinkWidthBuffer[shrink_buffer_indexes[section_n]++];
|
|
|
|
|
+ shrink_width_item->Index = tab_n;
|
|
|
|
|
+ shrink_width_item->Width = shrink_width_item->InitialWidth = tab->ContentWidth;
|
|
|
|
|
|
|
|
IM_ASSERT(tab->ContentWidth > 0.0f);
|
|
IM_ASSERT(tab->ContentWidth > 0.0f);
|
|
|
tab->Width = tab->ContentWidth;
|
|
tab->Width = tab->ContentWidth;
|
|
@@ -7958,10 +8067,13 @@ bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags)
|
|
|
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)
|
|
|
{
|
|
{
|
|
|
// Layout whole tab bar if not already done
|
|
// Layout whole tab bar if not already done
|
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
if (tab_bar->WantLayout)
|
|
if (tab_bar->WantLayout)
|
|
|
|
|
+ {
|
|
|
|
|
+ ImGuiNextItemData backup_next_item_data = g.NextItemData;
|
|
|
TabBarLayout(tab_bar);
|
|
TabBarLayout(tab_bar);
|
|
|
-
|
|
|
|
|
- ImGuiContext& g = *GImGui;
|
|
|
|
|
|
|
+ g.NextItemData = backup_next_item_data;
|
|
|
|
|
+ }
|
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
if (window->SkipItems)
|
|
if (window->SkipItems)
|
|
|
return false;
|
|
return false;
|
|
@@ -7987,9 +8099,6 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
|
|
|
else if (p_open == NULL)
|
|
else if (p_open == NULL)
|
|
|
flags |= ImGuiTabItemFlags_NoCloseButton;
|
|
flags |= ImGuiTabItemFlags_NoCloseButton;
|
|
|
|
|
|
|
|
- // Calculate tab contents size
|
|
|
|
|
- ImVec2 size = TabItemCalcSize(label, p_open != NULL);
|
|
|
|
|
-
|
|
|
|
|
// Acquire tab data
|
|
// Acquire tab data
|
|
|
ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
|
|
ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
|
|
|
bool tab_is_new = false;
|
|
bool tab_is_new = false;
|
|
@@ -7998,11 +8107,17 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
|
|
|
tab_bar->Tabs.push_back(ImGuiTabItem());
|
|
tab_bar->Tabs.push_back(ImGuiTabItem());
|
|
|
tab = &tab_bar->Tabs.back();
|
|
tab = &tab_bar->Tabs.back();
|
|
|
tab->ID = id;
|
|
tab->ID = id;
|
|
|
- tab->Width = size.x;
|
|
|
|
|
- tab_bar->TabsAddedNew = true;
|
|
|
|
|
- tab_is_new = true;
|
|
|
|
|
|
|
+ tab_bar->TabsAddedNew = tab_is_new = true;
|
|
|
}
|
|
}
|
|
|
tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab);
|
|
tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab);
|
|
|
|
|
+
|
|
|
|
|
+ // Calculate tab contents size
|
|
|
|
|
+ ImVec2 size = TabItemCalcSize(label, p_open != NULL);
|
|
|
|
|
+ tab->RequestedWidth = -1.0f;
|
|
|
|
|
+ if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth)
|
|
|
|
|
+ size.x = tab->RequestedWidth = g.NextItemData.Width;
|
|
|
|
|
+ if (tab_is_new)
|
|
|
|
|
+ tab->Width = size.x;
|
|
|
tab->ContentWidth = size.x;
|
|
tab->ContentWidth = size.x;
|
|
|
tab->BeginOrder = tab_bar->TabsActiveCount++;
|
|
tab->BeginOrder = tab_bar->TabsActiveCount++;
|
|
|
|
|
|
|
@@ -8018,13 +8133,14 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
|
|
|
tab_bar->TabsNames.append(label, label + strlen(label) + 1);
|
|
tab_bar->TabsNames.append(label, label + strlen(label) + 1);
|
|
|
|
|
|
|
|
// Update selected tab
|
|
// Update selected tab
|
|
|
- if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
|
|
|
|
|
- if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
|
|
|
|
|
- if (!is_tab_button)
|
|
|
|
|
|
|
+ if (!is_tab_button)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
|
|
|
|
|
+ if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
|
|
|
tab_bar->NextSelectedTabId = id; // New tabs gets activated
|
|
tab_bar->NextSelectedTabId = id; // New tabs gets activated
|
|
|
- if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // SetSelected can only be passed on explicit tab bar
|
|
|
|
|
- if (!is_tab_button)
|
|
|
|
|
|
|
+ if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // _SetSelected can only be passed on explicit tab bar
|
|
|
tab_bar->NextSelectedTabId = id;
|
|
tab_bar->NextSelectedTabId = id;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// Lock visibility
|
|
// Lock visibility
|
|
|
// (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!)
|
|
// (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!)
|