Revision a8009d0e
Added by david.sorber 8 months ago
| external/imgui/imgui_widgets.cpp | ||
|---|---|---|
|
// dear imgui, v1.91.5
|
||
|
// dear imgui, v1.92.5
|
||
|
// (widgets code)
|
||
|
|
||
|
/*
|
||
| ... | ... | |
|
#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx'
|
||
|
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
|
||
|
#pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok.
|
||
|
#pragma clang diagnostic ignored "-Wformat" // warning: format specifies type 'int' but the argument has type 'unsigned int'
|
||
|
#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.
|
||
|
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
|
||
|
#pragma clang diagnostic ignored "-Wunused-macros" // warning: macro is not used // we define snprintf/vsnprintf on Windows so they are available, but not always used.
|
||
| ... | ... | |
|
#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
|
||
|
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access
|
||
|
#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type
|
||
|
#pragma clang diagnostic ignored "-Wswitch-default" // warning: 'switch' missing 'default' label
|
||
|
#elif defined(__GNUC__)
|
||
|
#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
|
||
|
#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe
|
||
|
#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*'
|
||
|
#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
|
||
|
#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function
|
||
|
#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1
|
||
|
#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 "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers
|
||
|
#endif
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
| ... | ... | |
|
|
||
|
// For InputTextEx()
|
||
|
static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false);
|
||
|
static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
|
||
|
static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
|
||
|
static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining = NULL, ImVec2* out_offset = NULL, ImDrawTextFlags flags = 0);
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// [SECTION] Widgets: Text, etc.
|
||
| ... | ... | |
|
// Calculate length
|
||
|
const char* text_begin = text;
|
||
|
if (text_end == NULL)
|
||
|
text_end = text + strlen(text); // FIXME-OPT
|
||
|
text_end = text + ImStrlen(text); // FIXME-OPT
|
||
|
|
||
|
const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
|
||
|
const float wrap_pos_x = window->DC.TextWrapPos;
|
||
| ... | ... | |
|
int lines_skipped = 0;
|
||
|
while (line < text_end && lines_skipped < lines_skippable)
|
||
|
{
|
||
|
const char* line_end = (const char*)memchr(line, '\n', text_end - line);
|
||
|
const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line);
|
||
|
if (!line_end)
|
||
|
line_end = text_end;
|
||
|
if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
|
||
| ... | ... | |
|
if (IsClippedEx(line_rect, 0))
|
||
|
break;
|
||
|
|
||
|
const char* line_end = (const char*)memchr(line, '\n', text_end - line);
|
||
|
const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line);
|
||
|
if (!line_end)
|
||
|
line_end = text_end;
|
||
|
text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
|
||
| ... | ... | |
|
int lines_skipped = 0;
|
||
|
while (line < text_end)
|
||
|
{
|
||
|
const char* line_end = (const char*)memchr(line, '\n', text_end - line);
|
||
|
const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line);
|
||
|
if (!line_end)
|
||
|
line_end = text_end;
|
||
|
if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
|
||
| ... | ... | |
|
PopTextWrapPos();
|
||
|
}
|
||
|
|
||
|
void ImGui::TextAligned(float align_x, float size_x, const char* fmt, ...)
|
||
|
{
|
||
|
va_list args;
|
||
|
va_start(args, fmt);
|
||
|
TextAlignedV(align_x, size_x, fmt, args);
|
||
|
va_end(args);
|
||
|
}
|
||
|
|
||
|
// align_x: 0.0f = left, 0.5f = center, 1.0f = right.
|
||
|
// size_x : 0.0f = shortcut for GetContentRegionAvail().x
|
||
|
// FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024)
|
||
|
void ImGui::TextAlignedV(float align_x, float size_x, const char* fmt, va_list args)
|
||
|
{
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
|
if (window->SkipItems)
|
||
|
return;
|
||
|
|
||
|
const char* text, *text_end;
|
||
|
ImFormatStringToTempBufferV(&text, &text_end, fmt, args);
|
||
|
const ImVec2 text_size = CalcTextSize(text, text_end);
|
||
|
size_x = CalcItemSize(ImVec2(size_x, 0.0f), 0.0f, text_size.y).x;
|
||
|
|
||
|
ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
|
||
|
ImVec2 pos_max(pos.x + size_x, window->ClipRect.Max.y);
|
||
|
ImVec2 size(ImMin(size_x, text_size.x), text_size.y);
|
||
|
window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, pos.x + text_size.x);
|
||
|
window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, pos.x + text_size.x);
|
||
|
if (align_x > 0.0f && text_size.x < size_x)
|
||
|
pos.x += ImTrunc((size_x - text_size.x) * align_x);
|
||
|
RenderTextEllipsis(window->DrawList, pos, pos_max, pos_max.x, text, text_end, &text_size);
|
||
|
|
||
|
const ImVec2 backup_max_pos = window->DC.CursorMaxPos;
|
||
|
ItemSize(size);
|
||
|
ItemAdd(ImRect(pos, pos + size), 0);
|
||
|
window->DC.CursorMaxPos.x = backup_max_pos.x; // Cancel out extending content size because right-aligned text would otherwise mess it up.
|
||
|
|
||
|
if (size_x < text_size.x && IsItemHovered(ImGuiHoveredFlags_NoNavOverride | ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_ForTooltip))
|
||
|
SetTooltip("%.*s", (int)(text_end - text), text);
|
||
|
}
|
||
|
|
||
|
void ImGui::LabelText(const char* label, const char* fmt, ...)
|
||
|
{
|
||
|
va_list args;
|
||
| ... | ... | |
|
// - PressedOnDragDropHold can generally be associated with any flag.
|
||
|
// - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported.
|
||
|
//------------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
// The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set:
|
||
|
// The behavior of the return-value changes when ImGuiItemFlags_ButtonRepeat is set:
|
||
|
// Repeat+ Repeat+ Repeat+ Repeat+
|
||
|
// PressedOnClickRelease PressedOnClick PressedOnRelease PressedOnDoubleClick
|
||
|
//-------------------------------------------------------------------------------------------------------------------------------------------------
|
||
| ... | ... | |
|
// And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);'
|
||
|
// For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading.
|
||
|
// - Since v1.91.2 (Sept 2024) we included io.ConfigDebugHighlightIdConflicts feature.
|
||
|
// One idiom which was previously valid which will now emit a warning is when using multiple overlayed ButtonBehavior()
|
||
|
// One idiom which was previously valid which will now emit a warning is when using multiple overlaid ButtonBehavior()
|
||
|
// with same ID and different MouseButton (see #8030). You can fix it by:
|
||
|
// (1) switching to use a single ButtonBehavior() with multiple _MouseButton flags.
|
||
|
// or (2) surrounding those calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag()
|
||
| ... | ... | |
|
ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags);
|
||
|
if (flags & ImGuiButtonFlags_AllowOverlap)
|
||
|
item_flags |= ImGuiItemFlags_AllowOverlap;
|
||
|
if (item_flags & ImGuiItemFlags_NoFocus)
|
||
|
flags |= ImGuiButtonFlags_NoFocus | ImGuiButtonFlags_NoNavFocus;
|
||
|
|
||
|
// Default only reacts to left mouse button
|
||
|
if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0)
|
||
| ... | ... | |
|
flags |= (item_flags & ImGuiItemFlags_ButtonRepeat) ? ImGuiButtonFlags_PressedOnClick : 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->RootWindow == window->RootWindow;
|
||
|
if (flatten_hovered_children)
|
||
|
g.HoveredWindow = window;
|
||
|
|
||
| ... | ... | |
|
bool pressed = false;
|
||
|
bool hovered = ItemHoverable(bb, id, item_flags);
|
||
|
|
||
|
// Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
|
||
|
if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
|
||
|
if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
|
||
|
// Special mode for Drag and Drop used by openables (tree nodes, tabs etc.)
|
||
|
// where holding the button pressed for a long time while drag a payload item triggers the button.
|
||
|
if (g.DragDropActive)
|
||
|
{
|
||
|
if ((flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
|
||
|
{
|
||
|
hovered = true;
|
||
|
SetHoveredID(id);
|
||
| ... | ... | |
|
FocusWindow(window);
|
||
|
}
|
||
|
}
|
||
|
if (g.DragDropAcceptIdPrev == id && (g.DragDropAcceptFlagsPrev & ImGuiDragDropFlags_AcceptDrawAsHovered))
|
||
|
hovered = true;
|
||
|
}
|
||
|
|
||
|
if (flatten_hovered_children)
|
||
|
g.HoveredWindow = backup_hovered_window;
|
||
| ... | ... | |
|
if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere))
|
||
|
{
|
||
|
SetActiveID(id, window);
|
||
|
g.ActiveIdMouseButton = mouse_button_clicked;
|
||
|
g.ActiveIdMouseButton = (ImS8)mouse_button_clicked;
|
||
|
if (!(flags & ImGuiButtonFlags_NoNavFocus))
|
||
|
{
|
||
|
SetFocusID(id, window);
|
||
|
FocusWindow(window);
|
||
|
}
|
||
|
else
|
||
|
else if (!(flags & ImGuiButtonFlags_NoFocus))
|
||
|
{
|
||
|
FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child
|
||
|
}
|
||
| ... | ... | |
|
ClearActiveID();
|
||
|
else
|
||
|
SetActiveID(id, window); // Hold on ID
|
||
|
g.ActiveIdMouseButton = mouse_button_clicked;
|
||
|
g.ActiveIdMouseButton = (ImS8)mouse_button_clicked;
|
||
|
if (!(flags & ImGuiButtonFlags_NoNavFocus))
|
||
|
{
|
||
|
SetFocusID(id, window);
|
||
|
FocusWindow(window);
|
||
|
}
|
||
|
else
|
||
|
else if (!(flags & ImGuiButtonFlags_NoFocus))
|
||
|
{
|
||
|
FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child
|
||
|
}
|
||
| ... | ... | |
|
|
||
|
// Keyboard/Gamepad navigation handling
|
||
|
// We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse.
|
||
|
if (g.NavId == id && g.NavCursorVisible && g.NavHighlightItemUnderNav)
|
||
|
if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))
|
||
|
hovered = true;
|
||
|
if (g.NavActivateDownId == id)
|
||
|
if ((item_flags & ImGuiItemFlags_Disabled) == 0)
|
||
|
{
|
||
|
bool nav_activated_by_code = (g.NavActivateId == id);
|
||
|
bool nav_activated_by_inputs = (g.NavActivatePressedId == id);
|
||
|
if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat))
|
||
|
if (g.NavId == id && g.NavCursorVisible && g.NavHighlightItemUnderNav)
|
||
|
if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))
|
||
|
hovered = true;
|
||
|
if (g.NavActivateDownId == id)
|
||
|
{
|
||
|
// Avoid pressing multiple keys from triggering excessive amount of repeat events
|
||
|
const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space);
|
||
|
const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_Enter);
|
||
|
const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate);
|
||
|
const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->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)
|
||
|
{
|
||
|
// Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
|
||
|
pressed = true;
|
||
|
SetActiveID(id, window);
|
||
|
g.ActiveIdSource = g.NavInputSource;
|
||
|
if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut))
|
||
|
SetFocusID(id, window);
|
||
|
if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)
|
||
|
g.ActiveIdFromShortcut = true;
|
||
|
bool nav_activated_by_code = (g.NavActivateId == id);
|
||
|
bool nav_activated_by_inputs = (g.NavActivatePressedId == id);
|
||
|
if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat))
|
||
|
{
|
||
|
// Avoid pressing multiple keys from triggering excessive amount of repeat events
|
||
|
const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space);
|
||
|
const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_Enter);
|
||
|
const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate);
|
||
|
const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->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)
|
||
|
{
|
||
|
// Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
|
||
|
pressed = true;
|
||
|
SetActiveID(id, window);
|
||
|
g.ActiveIdSource = g.NavInputSource;
|
||
|
if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut))
|
||
|
SetFocusID(id, window);
|
||
|
if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)
|
||
|
g.ActiveIdFromShortcut = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
| ... | ... | |
|
}
|
||
|
|
||
|
// Activation highlight (this may be a remote activation)
|
||
|
if (g.NavHighlightActivatedId == id)
|
||
|
if (g.NavHighlightActivatedId == id && (item_flags & ImGuiItemFlags_Disabled) == 0)
|
||
|
hovered = true;
|
||
|
|
||
|
if (out_hovered) *out_hovered = hovered;
|
||
| ... | ... | |
|
if (hovered)
|
||
|
window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col);
|
||
|
RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact);
|
||
|
ImU32 cross_col = GetColorU32(ImGuiCol_Text);
|
||
|
ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f);
|
||
|
float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
|
||
|
window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f);
|
||
|
window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f);
|
||
|
const ImU32 cross_col = GetColorU32(ImGuiCol_Text);
|
||
|
const ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f);
|
||
|
const float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
|
||
|
const float cross_thickness = 1.0f; // FIXME-DPI
|
||
|
window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, cross_thickness);
|
||
|
window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, cross_thickness);
|
||
|
|
||
|
return pressed;
|
||
|
}
|
||
| ... | ... | |
|
// Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.
|
||
|
ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
const ImRect outer_rect = window->Rect();
|
||
|
const ImRect inner_rect = window->InnerRect;
|
||
|
const float border_size = window->WindowBorderSize;
|
||
|
const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)
|
||
|
IM_ASSERT(scrollbar_size > 0.0f);
|
||
|
IM_ASSERT(scrollbar_size >= 0.0f);
|
||
|
const float border_size = IM_ROUND(window->WindowBorderSize * 0.5f);
|
||
|
const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : 0.0f;
|
||
|
if (axis == ImGuiAxis_X)
|
||
|
return ImRect(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size);
|
||
|
return ImRect(inner_rect.Min.x + border_size, ImMax(outer_rect.Min.y + border_size, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size);
|
||
|
else
|
||
|
return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size);
|
||
|
return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y + border_top, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size);
|
||
|
}
|
||
|
|
||
|
void ImGui::Scrollbar(ImGuiAxis axis)
|
||
| ... | ... | |
|
// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
|
||
|
// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
|
||
|
// Still, the code should probably be made simpler..
|
||
|
bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags flags)
|
||
|
bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags draw_rounding_flags)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiWindow* window = g.CurrentWindow;
|
||
| ... | ... | |
|
|
||
|
// When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the window resize grab)
|
||
|
float alpha = 1.0f;
|
||
|
if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
|
||
|
alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
|
||
|
if ((axis == ImGuiAxis_Y) && bb_frame_height < bb_frame_width)
|
||
|
alpha = ImSaturate(bb_frame_height / ImMax(bb_frame_width * 2.0f, 1.0f));
|
||
|
if (alpha <= 0.0f)
|
||
|
return false;
|
||
|
|
||
| ... | ... | |
|
const bool allow_interaction = (alpha >= 1.0f);
|
||
|
|
||
|
ImRect bb = bb_frame;
|
||
|
bb.Expand(ImVec2(-ImClamp(IM_TRUNC((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_TRUNC((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f)));
|
||
|
float padding = IM_TRUNC(ImMin(style.ScrollbarPadding, ImMin(bb_frame_width, bb_frame_height) * 0.5f));
|
||
|
bb.Expand(-padding);
|
||
|
|
||
|
// V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
|
||
|
const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight();
|
||
| ... | ... | |
|
// But we maintain a minimum size in pixel to allow for the user to still aim inside.
|
||
|
IM_ASSERT(ImMax(size_contents_v, size_visible_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
|
||
|
const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_visible_v), (ImS64)1);
|
||
|
const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), style.GrabMinSize, scrollbar_size_v);
|
||
|
const float grab_h_minsize = ImMin(bb.GetSize()[axis], style.GrabMinSize);
|
||
|
const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), grab_h_minsize, scrollbar_size_v);
|
||
|
const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
|
||
|
|
||
|
// Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
|
||
| ... | ... | |
|
// Render
|
||
|
const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg);
|
||
|
const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
|
||
|
window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags);
|
||
|
window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, draw_rounding_flags);
|
||
|
ImRect grab_rect;
|
||
|
if (axis == ImGuiAxis_X)
|
||
|
grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y);
|
||
| ... | ... | |
|
return held;
|
||
|
}
|
||
|
|
||
|
// - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
|
||
|
// - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
|
||
|
// - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above.
|
||
|
void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
|
||
|
void ImGui::ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
|
if (window->SkipItems)
|
||
|
return;
|
||
|
|
||
|
const float border_size = (border_col.w > 0.0f) ? 1.0f : 0.0f;
|
||
|
const ImVec2 padding(border_size, border_size);
|
||
|
const ImVec2 padding(g.Style.ImageBorderSize, g.Style.ImageBorderSize);
|
||
|
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
|
||
|
ItemSize(bb);
|
||
|
if (!ItemAdd(bb, 0))
|
||
|
return;
|
||
|
|
||
|
// Render
|
||
|
if (border_size > 0.0f)
|
||
|
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f, ImDrawFlags_None, border_size);
|
||
|
window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
|
||
|
if (g.Style.ImageBorderSize > 0.0f)
|
||
|
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, ImDrawFlags_None, g.Style.ImageBorderSize);
|
||
|
if (bg_col.w > 0.0f)
|
||
|
window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
|
||
|
window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
|
||
|
}
|
||
|
|
||
|
void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1)
|
||
|
{
|
||
|
ImageWithBg(tex_ref, image_size, uv0, uv1);
|
||
|
}
|
||
|
|
||
|
// 1.91.9 (February 2025) removed 'tint_col' and 'border_col' parameters, made border size not depend on color value. (#8131, #8238)
|
||
|
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||
|
void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
PushStyleVar(ImGuiStyleVar_ImageBorderSize, (border_col.w > 0.0f) ? ImMax(1.0f, g.Style.ImageBorderSize) : 0.0f); // Preserve legacy behavior where border is always visible when border_col's Alpha is >0.0f
|
||
|
PushStyleColor(ImGuiCol_Border, border_col);
|
||
|
ImageWithBg(tex_ref, image_size, uv0, uv1, ImVec4(0, 0, 0, 0), tint_col);
|
||
|
PopStyleColor();
|
||
|
PopStyleVar();
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390)
|
||
|
// We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API.
|
||
|
bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags)
|
||
|
bool ImGui::ImageButtonEx(ImGuiID id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
| ... | ... | |
|
RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding));
|
||
|
if (bg_col.w > 0.0f)
|
||
|
window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
|
||
|
window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
|
||
|
window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
|
||
|
|
||
|
return pressed;
|
||
|
}
|
||
|
|
||
|
// Note that ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button.
|
||
|
bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
|
||
|
// - ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button.
|
||
|
// - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. (#8165) // FIXME: Maybe that's not the best design?
|
||
|
bool ImGui::ImageButton(const char* str_id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiWindow* window = g.CurrentWindow;
|
||
|
if (window->SkipItems)
|
||
|
return false;
|
||
|
|
||
|
return ImageButtonEx(window->GetID(str_id), user_texture_id, image_size, uv0, uv1, bg_col, tint_col);
|
||
|
return ImageButtonEx(window->GetID(str_id), tex_ref, image_size, uv0, uv1, bg_col, tint_col);
|
||
|
}
|
||
|
|
||
|
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||
|
// Legacy API obsoleted in 1.89. Two differences with new ImageButton()
|
||
|
// - old ImageButton() used ImTextureId as item id (created issue with multiple buttons with same image, transient texture id values, opaque computation of ID)
|
||
|
// - old ImageButton() used ImTextureID as item id (created issue with multiple buttons with same image, transient texture id values, opaque computation of ID)
|
||
|
// - new ImageButton() requires an explicit 'const char* str_id'
|
||
|
// - old ImageButton() had frame_padding' override argument.
|
||
|
// - new ImageButton() always use style.FramePadding.
|
||
| ... | ... | |
|
const ImGuiID id = window->GetID(label);
|
||
|
const char* label_end = FindRenderedTextEnd(label);
|
||
|
|
||
|
ImVec2 pos = window->DC.CursorPos;
|
||
|
ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
|
||
|
ImVec2 size = CalcTextSize(label, label_end, true);
|
||
|
ImRect bb(pos, pos + size);
|
||
|
ItemSize(size, 0.0f);
|
||
| ... | ... | |
|
ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z);
|
||
|
}
|
||
|
|
||
|
float line_y = bb.Max.y + ImFloor(g.Font->Descent * g.FontScale * 0.20f);
|
||
|
window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode.
|
||
|
float line_y = bb.Max.y + ImFloor(g.FontBaked->Descent * g.FontBakedScale * 0.20f);
|
||
|
window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode // FIXME-DPI
|
||
|
|
||
|
PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf));
|
||
|
RenderText(bb.Min, label, label_end);
|
||
| ... | ... | |
|
return pressed;
|
||
|
}
|
||
|
|
||
|
void ImGui::TextLinkOpenURL(const char* label, const char* url)
|
||
|
bool ImGui::TextLinkOpenURL(const char* label, const char* url)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
if (url == NULL)
|
||
|
url = label;
|
||
|
if (TextLink(label))
|
||
|
if (g.PlatformIO.Platform_OpenInShellFn != NULL)
|
||
|
g.PlatformIO.Platform_OpenInShellFn(&g, url);
|
||
|
bool pressed = TextLink(label);
|
||
|
if (pressed && g.PlatformIO.Platform_OpenInShellFn != NULL)
|
||
|
g.PlatformIO.Platform_OpenInShellFn(&g, url);
|
||
|
SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when we same as label
|
||
|
if (BeginPopupContextItem())
|
||
|
{
|
||
| ... | ... | |
|
SetClipboardText(url);
|
||
|
EndPopup();
|
||
|
}
|
||
|
return pressed;
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
| ... | ... | |
|
window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness);
|
||
|
if (g.LogEnabled)
|
||
|
LogSetNextTextDecoration("---", NULL);
|
||
|
RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, bb.Max.x, label, label_end, &label_size);
|
||
|
RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, label, label_end, &label_size);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
| ... | ... | |
|
|
||
|
// Shrink excess width from a set of item, by removing width from the larger items first.
|
||
|
// Set items Width to -1.0f to disable shrinking this item.
|
||
|
void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess)
|
||
|
void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess, float width_min)
|
||
|
{
|
||
|
if (count == 1)
|
||
|
{
|
||
|
if (items[0].Width >= 0.0f)
|
||
|
items[0].Width = ImMax(items[0].Width - width_excess, 1.0f);
|
||
|
items[0].Width = ImMax(items[0].Width - width_excess, width_min);
|
||
|
return;
|
||
|
}
|
||
|
ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer);
|
||
|
ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer); // Sort largest first, smallest last.
|
||
|
int count_same_width = 1;
|
||
|
while (width_excess > 0.0f && count_same_width < count)
|
||
|
while (width_excess > 0.001f && count_same_width < count)
|
||
|
{
|
||
|
while (count_same_width < count && items[0].Width <= items[count_same_width].Width)
|
||
|
count_same_width++;
|
||
|
float max_width_to_remove_per_item = (count_same_width < count && items[count_same_width].Width >= 0.0f) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f);
|
||
|
max_width_to_remove_per_item = ImMin(items[0].Width - width_min, max_width_to_remove_per_item);
|
||
|
if (max_width_to_remove_per_item <= 0.0f)
|
||
|
break;
|
||
|
float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item);
|
||
|
float base_width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item);
|
||
|
for (int item_n = 0; item_n < count_same_width; item_n++)
|
||
|
items[item_n].Width -= width_to_remove_per_item;
|
||
|
width_excess -= width_to_remove_per_item * count_same_width;
|
||
|
{
|
||
|
float width_to_remove_for_this_item = ImMin(base_width_to_remove_per_item, items[item_n].Width - width_min);
|
||
|
items[item_n].Width -= width_to_remove_for_this_item;
|
||
|
width_excess -= width_to_remove_for_this_item;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Round width and redistribute remainder
|
||
| ... | ... | |
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
|
|
||
|
ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags;
|
||
|
ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.HasFlags;
|
||
|
g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
|
||
|
if (window->SkipItems)
|
||
|
return false;
|
||
| ... | ... | |
|
if (!popup_open)
|
||
|
return false;
|
||
|
|
||
|
g.NextWindowData.Flags = backup_next_window_data_flags;
|
||
|
g.NextWindowData.HasFlags = backup_next_window_data_flags;
|
||
|
return BeginComboPopup(popup_id, bb, flags);
|
||
|
}
|
||
|
|
||
| ... | ... | |
|
|
||
|
// Set popup size
|
||
|
float w = bb.GetWidth();
|
||
|
if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)
|
||
|
if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint)
|
||
|
{
|
||
|
g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
|
||
|
}
|
||
| ... | ... | |
|
else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;
|
||
|
else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;
|
||
|
ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX);
|
||
|
if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size
|
||
|
if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size
|
||
|
constraint_min.x = w;
|
||
|
if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f)
|
||
|
if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f)
|
||
|
constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items);
|
||
|
SetNextWindowSizeConstraints(constraint_min, constraint_max);
|
||
|
}
|
||
| ... | ... | |
|
if (!ret)
|
||
|
{
|
||
|
EndPopup();
|
||
|
IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
|
||
|
if (!g.IO.ConfigDebugBeginReturnValueOnce && !g.IO.ConfigDebugBeginReturnValueLoop) // Begin may only return false with those debug tools activated.
|
||
|
IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
|
||
|
return false;
|
||
|
}
|
||
|
g.BeginComboDepth++;
|
||
| ... | ... | |
|
{
|
||
|
if (idx == items_count)
|
||
|
break;
|
||
|
p += strlen(p) + 1;
|
||
|
p += ImStrlen(p) + 1;
|
||
|
items_count++;
|
||
|
}
|
||
|
return *p ? p : NULL;
|
||
| ... | ... | |
|
preview_value = getter(user_data, *current_item);
|
||
|
|
||
|
// The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here.
|
||
|
if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint))
|
||
|
if (popup_max_height_in_items != -1 && !(g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint))
|
||
|
SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
|
||
|
|
||
|
if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
|
||
| ... | ... | |
|
const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
|
||
|
while (*p)
|
||
|
{
|
||
|
p += strlen(p) + 1;
|
||
|
p += ImStrlen(p) + 1;
|
||
|
items_count++;
|
||
|
}
|
||
|
bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
|
||
| ... | ... | |
|
{ sizeof(float), "float", "%.3f","%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg)
|
||
|
{ sizeof(double), "double","%f", "%lf" }, // ImGuiDataType_Double
|
||
|
{ sizeof(bool), "bool", "%d", "%d" }, // ImGuiDataType_Bool
|
||
|
{ 0, "char*","%s", "%s" }, // ImGuiDataType_String
|
||
|
};
|
||
|
IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
|
||
|
|
||
| ... | ... | |
|
if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
|
||
|
{
|
||
|
adjust_delta = g.IO.MouseDelta[axis];
|
||
|
if (g.IO.KeyAlt)
|
||
|
if (g.IO.KeyAlt && !(flags & ImGuiSliderFlags_NoSpeedTweaks))
|
||
|
adjust_delta *= 1.0f / 100.0f;
|
||
|
if (g.IO.KeyShift)
|
||
|
if (g.IO.KeyShift && !(flags & ImGuiSliderFlags_NoSpeedTweaks))
|
||
|
adjust_delta *= 10.0f;
|
||
|
}
|
||
|
else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
|
||
| ... | ... | |
|
const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
|
||
|
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 / 10.0f : tweak_fast ? 10.0f : 1.0f;
|
||
|
const float tweak_factor = (flags & ImGuiSliderFlags_NoSpeedTweaks) ? 1.0f : tweak_slow ? 1.0f / 10.0f : tweak_fast ? 10.0f : 1.0f;
|
||
|
adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor;
|
||
|
v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
|
||
|
}
|
||
| ... | ... | |
|
bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
|
||
|
if (!temp_input_is_active)
|
||
|
{
|
||
|
// Tabbing or CTRL-clicking on Drag turns it into an InputText
|
||
|
// Tabbing or Ctrl+Click on Drag turns it into an InputText
|
||
|
const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
|
||
|
const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id));
|
||
|
const bool make_active = (clicked || double_clicked || g.NavActivateId == id);
|
||
| ... | ... | |
|
temp_input_is_active = true;
|
||
|
}
|
||
|
|
||
|
// Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert)
|
||
|
if (make_active)
|
||
|
memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size);
|
||
|
|
||
|
if (make_active && !temp_input_is_active)
|
||
|
{
|
||
|
SetActiveID(id, window);
|
||
| ... | ... | |
|
|
||
|
if (temp_input_is_active)
|
||
|
{
|
||
|
// Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)
|
||
|
// Only clamp Ctrl+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)
|
||
|
bool clamp_enabled = false;
|
||
|
if ((flags & ImGuiSliderFlags_ClampOnInput) && (p_min != NULL || p_max != NULL))
|
||
|
{
|
||
| ... | ... | |
|
bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
|
||
|
if (!temp_input_is_active)
|
||
|
{
|
||
|
// Tabbing or CTRL-clicking on Slider turns it into an input box
|
||
|
// Tabbing or Ctrl+Click on Slider turns it into an input box
|
||
|
const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
|
||
|
const bool make_active = (clicked || g.NavActivateId == id);
|
||
|
if (make_active && clicked)
|
||
| ... | ... | |
|
if ((clicked && g.IO.KeyCtrl) || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
|
||
|
temp_input_is_active = true;
|
||
|
|
||
|
// Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert)
|
||
|
if (make_active)
|
||
|
memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size);
|
||
|
|
||
|
if (make_active && !temp_input_is_active)
|
||
|
{
|
||
|
SetActiveID(id, window);
|
||
| ... | ... | |
|
|
||
|
if (temp_input_is_active)
|
||
|
{
|
||
|
// Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)
|
||
|
// Only clamp Ctrl+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)
|
||
|
const bool clamp_enabled = (flags & ImGuiSliderFlags_ClampOnInput) != 0;
|
||
|
return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL);
|
||
|
}
|
||
| ... | ... | |
|
format = "%.0f deg";
|
||
|
float v_deg = (*v_rad) * 360.0f / (2 * IM_PI);
|
||
|
bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags);
|
||
|
*v_rad = v_deg * (2 * IM_PI) / 360.0f;
|
||
|
if (value_changed)
|
||
|
*v_rad = v_deg * (2 * IM_PI) / 360.0f;
|
||
|
return value_changed;
|
||
|
}
|
||
|
|
||
| ... | ... | |
|
return (precision == INT_MAX) ? default_precision : precision;
|
||
|
}
|
||
|
|
||
|
// Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets)
|
||
|
// Create text input in place of another active widget (e.g. used when doing a Ctrl+Click on drag/slider widgets)
|
||
|
// FIXME: Facilitate using this in variety of other situations.
|
||
|
// FIXME: Among other things, setting ImGuiItemFlags_AllowDuplicateId in LastItemData is currently correct but
|
||
|
// the expected relationship between TempInputXXX functions and LastItemData is a little fishy.
|
||
| ... | ... | |
|
}
|
||
|
|
||
|
// 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.
|
||
|
// 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.
|
||
|
bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min, const void* p_clamp_max)
|
||
|
{
|
||
| ... | ... | |
|
// - InputText()
|
||
|
// - InputTextWithHint()
|
||
|
// - InputTextMultiline()
|
||
|
// - InputTextGetCharInfo() [Internal]
|
||
|
// - InputTextReindexLines() [Internal]
|
||
|
// - InputTextReindexLinesRange() [Internal]
|
||
|
// - InputTextEx() [Internal]
|
||
|
// - DebugNodeInputTextState() [Internal]
|
||
|
//-------------------------------------------------------------------------
|
||
| ... | ... | |
|
#include "imstb_textedit.h"
|
||
|
}
|
||
|
|
||
|
// If you want to use InputText() with std::string or any custom dynamic string type, use the wrapper in misc/cpp/imgui_stdlib.h/.cpp!
|
||
|
bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
||
|
{
|
||
|
IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
|
||
| ... | ... | |
|
return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
|
||
|
}
|
||
|
|
||
|
// This is only used in the path where the multiline widget is inactivate.
|
||
|
static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
|
||
|
{
|
||
|
int line_count = 0;
|
||
|
const char* s = text_begin;
|
||
|
while (true)
|
||
|
{
|
||
|
const char* s_eol = strchr(s, '\n');
|
||
|
line_count++;
|
||
|
if (s_eol == NULL)
|
||
|
{
|
||
|
s = s + strlen(s);
|
||
|
break;
|
||
|
}
|
||
|
s = s_eol + 1;
|
||
|
}
|
||
|
*out_text_end = s;
|
||
|
return line_count;
|
||
|
}
|
||
|
|
||
|
// FIXME: Ideally we'd share code with ImFont::CalcTextSizeA()
|
||
|
static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining, ImVec2* out_offset, bool stop_on_new_line)
|
||
|
static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining, ImVec2* out_offset, ImDrawTextFlags flags)
|
||
|
{
|
||
|
ImGuiContext& g = *ctx;
|
||
|
ImFont* font = g.Font;
|
||
|
const float line_height = g.FontSize;
|
||
|
const float scale = line_height / font->FontSize;
|
||
|
|
||
|
ImVec2 text_size = ImVec2(0, 0);
|
||
|
float line_width = 0.0f;
|
||
|
|
||
|
const char* s = text_begin;
|
||
|
while (s < text_end)
|
||
|
{
|
||
|
unsigned int c = (unsigned int)*s;
|
||
|
if (c < 0x80)
|
||
|
s += 1;
|
||
|
else
|
||
|
s += ImTextCharFromUtf8(&c, s, text_end);
|
||
|
|
||
|
if (c == '\n')
|
||
|
{
|
||
|
text_size.x = ImMax(text_size.x, line_width);
|
||
|
text_size.y += line_height;
|
||
|
line_width = 0.0f;
|
||
|
if (stop_on_new_line)
|
||
|
break;
|
||
|
continue;
|
||
|
}
|
||
|
if (c == '\r')
|
||
|
continue;
|
||
|
|
||
|
const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX) * scale;
|
||
|
line_width += char_width;
|
||
|
}
|
||
|
|
||
|
if (text_size.x < line_width)
|
||
|
text_size.x = line_width;
|
||
|
|
||
|
if (out_offset)
|
||
|
*out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n
|
||
|
|
||
|
if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n
|
||
|
text_size.y += line_height;
|
||
|
|
||
|
if (remaining)
|
||
|
*remaining = s;
|
||
|
|
||
|
return text_size;
|
||
|
ImGuiInputTextState* obj = &g.InputTextState;
|
||
|
IM_ASSERT(text_end_display >= text_begin && text_end_display <= text_end);
|
||
|
return ImFontCalcTextSizeEx(g.Font, g.FontSize, FLT_MAX, obj->WrapWidth, text_begin, text_end_display, text_end, out_remaining, out_offset, flags);
|
||
|
}
|
||
|
|
||
|
// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar)
|
||
| ... | ... | |
|
namespace ImStb
|
||
|
{
|
||
|
static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->TextLen; }
|
||
|
static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->TextLen); return obj->TextA[idx]; }
|
||
|
static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextA.Data + line_start_idx + char_idx, obj->TextA.Data + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; }
|
||
|
static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx >= 0 && idx <= obj->TextLen); return obj->TextSrc[idx]; }
|
||
|
static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.FontBaked->GetCharAdvance((ImWchar)c) * g.FontBakedScale; }
|
||
|
static char STB_TEXTEDIT_NEWLINE = '\n';
|
||
|
static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)
|
||
|
{
|
||
|
const char* text = obj->TextA.Data;
|
||
|
const char* text = obj->TextSrc;
|
||
|
const char* text_remaining = NULL;
|
||
|
const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->TextLen, &text_remaining, NULL, true);
|
||
|
const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->TextLen, text + obj->TextLen, &text_remaining, NULL, ImDrawTextFlags_StopOnNewLine | ImDrawTextFlags_WrapKeepBlanks);
|
||
|
r->x0 = 0.0f;
|
||
|
r->x1 = size.x;
|
||
|
r->baseline_y_delta = size.y;
|
||
| ... | ... | |
|
if (idx >= obj->TextLen)
|
||
|
return obj->TextLen + 1;
|
||
|
unsigned int c;
|
||
|
return idx + ImTextCharFromUtf8(&c, obj->TextA.Data + idx, obj->TextA.Data + obj->TextLen);
|
||
|
return idx + ImTextCharFromUtf8(&c, obj->TextSrc + idx, obj->TextSrc + obj->TextLen);
|
||
|
}
|
||
|
|
||
|
static int IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx)
|
||
|
{
|
||
|
if (idx <= 0)
|
||
|
return -1;
|
||
|
const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, obj->TextA.Data + idx);
|
||
|
return (int)(p - obj->TextA.Data);
|
||
|
const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, obj->TextSrc + idx);
|
||
|
return (int)(p - obj->TextSrc);
|
||
|
}
|
||
|
|
||
|
static bool ImCharIsSeparatorW(unsigned int c)
|
||
| ... | ... | |
|
|
||
|
static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)
|
||
|
{
|
||
|
// When ImGuiInputTextFlags_Password is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators.
|
||
|
// When ImGuiInputTextFlags_Password is set, we don't want actions such as Ctrl+Arrow to leak the fact that underlying data are blanks or separators.
|
||
|
if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
|
||
|
return 0;
|
||
|
|
||
|
const char* curr_p = obj->TextA.Data + idx;
|
||
|
const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, curr_p);
|
||
|
unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextA.Data + obj->TextLen);
|
||
|
unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextA.Data + obj->TextLen);
|
||
|
const char* curr_p = obj->TextSrc + idx;
|
||
|
const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p);
|
||
|
unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextSrc + obj->TextLen);
|
||
|
unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextSrc + obj->TextLen);
|
||
|
|
||
|
bool prev_white = ImCharIsBlankW(prev_c);
|
||
|
bool prev_separ = ImCharIsSeparatorW(prev_c);
|
||
| ... | ... | |
|
if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
|
||
|
return 0;
|
||
|
|
||
|
const char* curr_p = obj->TextA.Data + idx;
|
||
|
const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, curr_p);
|
||
|
unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextA.Data + obj->TextLen);
|
||
|
unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextA.Data + obj->TextLen);
|
||
|
const char* curr_p = obj->TextSrc + idx;
|
||
|
const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p);
|
||
|
unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextSrc + obj->TextLen);
|
||
|
unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextSrc + obj->TextLen);
|
||
|
|
||
|
bool prev_white = ImCharIsBlankW(prev_c);
|
||
|
bool prev_separ = ImCharIsSeparatorW(prev_c);
|
||
| ... | ... | |
|
#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
|
||
|
|
||
|
// Reimplementation of stb_textedit_move_line_start()/stb_textedit_move_line_end() which supports word-wrapping.
|
||
|
static int STB_TEXTEDIT_MOVELINESTART_IMPL(ImGuiInputTextState* obj, ImStb::STB_TexteditState* state, int cursor)
|
||
|
{
|
||
|
if (state->single_line)
|
||
|
return 0;
|
||
|
|
||
|
if (obj->WrapWidth > 0.0f)
|
||
|
{
|
||
|
ImGuiContext& g = *obj->Ctx;
|
||
|
const char* p_cursor = obj->TextSrc + cursor;
|
||
|
const char* p_bol = ImStrbol(p_cursor, obj->TextSrc);
|
||
|
const char* p = p_bol;
|
||
|
const char* text_end = obj->TextSrc + obj->TextLen; // End of line would be enough
|
||
|
while (p >= p_bol)
|
||
|
{
|
||
|
const char* p_eol = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, p, text_end, obj->WrapWidth, ImDrawTextFlags_WrapKeepBlanks);
|
||
|
if (p == p_cursor) // If we are already on a visible beginning-of-line, return real beginning-of-line (would be same as regular handler below)
|
||
|
return (int)(p_bol - obj->TextSrc);
|
||
|
if (p_eol == p_cursor && obj->TextA[cursor] != '\n' && obj->LastMoveDirectionLR == ImGuiDir_Left)
|
||
|
return (int)(p_bol - obj->TextSrc);
|
||
|
if (p_eol >= p_cursor)
|
||
|
return (int)(p - obj->TextSrc);
|
||
|
p = (*p_eol == '\n') ? p_eol + 1 : p_eol;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Regular handler, same as stb_textedit_move_line_start()
|
||
|
while (cursor > 0)
|
||
|
{
|
||
|
int prev_cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, cursor);
|
||
|
if (STB_TEXTEDIT_GETCHAR(obj, prev_cursor) == STB_TEXTEDIT_NEWLINE)
|
||
|
break;
|
||
|
cursor = prev_cursor;
|
||
|
}
|
||
|
return cursor;
|
||
|
}
|
||
|
|
||
|
static int STB_TEXTEDIT_MOVELINEEND_IMPL(ImGuiInputTextState* obj, ImStb::STB_TexteditState* state, int cursor)
|
||
|
{
|
||
|
int n = STB_TEXTEDIT_STRINGLEN(obj);
|
||
|
if (state->single_line)
|
||
|
return n;
|
||
|
|
||
|
if (obj->WrapWidth > 0.0f)
|
||
|
{
|
||
|
ImGuiContext& g = *obj->Ctx;
|
||
|
const char* p_cursor = obj->TextSrc + cursor;
|
||
|
const char* p = ImStrbol(p_cursor, obj->TextSrc);
|
||
|
const char* text_end = obj->TextSrc + obj->TextLen; // End of line would be enough
|
||
|
while (p < text_end)
|
||
|
{
|
||
|
const char* p_eol = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, p, text_end, obj->WrapWidth, ImDrawTextFlags_WrapKeepBlanks);
|
||
|
cursor = (int)(p_eol - obj->TextSrc);
|
||
|
if (p_eol == p_cursor && obj->LastMoveDirectionLR != ImGuiDir_Left) // If we are already on a visible end-of-line, switch to regular handle
|
||
|
break;
|
||
|
if (p_eol > p_cursor)
|
||
|
return cursor;
|
||
|
p = (*p_eol == '\n') ? p_eol + 1 : p_eol;
|
||
|
}
|
||
|
}
|
||
|
// Regular handler, same as stb_textedit_move_line_end()
|
||
|
while (cursor < n && STB_TEXTEDIT_GETCHAR(obj, cursor) != STB_TEXTEDIT_NEWLINE)
|
||
|
cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, cursor);
|
||
|
return cursor;
|
||
|
}
|
||
|
|
||
|
#define STB_TEXTEDIT_MOVELINESTART STB_TEXTEDIT_MOVELINESTART_IMPL
|
||
|
#define STB_TEXTEDIT_MOVELINEEND STB_TEXTEDIT_MOVELINEEND_IMPL
|
||
|
|
||
|
static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
|
||
|
{
|
||
|
// Offset remaining text (+ copy zero terminator)
|
||
|
IM_ASSERT(obj->TextSrc == obj->TextA.Data);
|
||
|
char* dst = obj->TextA.Data + pos;
|
||
|
|
||
|
char* src = obj->TextA.Data + pos + n;
|
||
|
memmove(dst, src, obj->TextLen - n - pos + 1);
|
||
|
obj->Edited = true;
|
||
|
obj->TextLen -= n;
|
||
|
|
||
|
// Offset remaining text (FIXME-OPT: Use memmove)
|
||
|
const char* src = obj->TextA.Data + pos + n;
|
||
|
while (char c = *src++)
|
||
|
*dst++ = c;
|
||
|
*dst = '\0';
|
||
|
}
|
||
|
|
||
|
static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const char* new_text, int new_text_len)
|
||
|
static int STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const char* new_text, int new_text_len)
|
||
|
{
|
||
|
const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0;
|
||
|
const int text_len = obj->TextLen;
|
||
|
IM_ASSERT(pos <= text_len);
|
||
|
|
||
|
if (!is_resizable && (new_text_len + obj->TextLen + 1 > obj->BufCapacity))
|
||
|
return false;
|
||
|
// We support partial insertion (with a mod in stb_textedit.h)
|
||
|
const int avail = obj->BufCapacity - 1 - obj->TextLen;
|
||
|
if (!is_resizable && new_text_len > avail)
|
||
|
new_text_len = (int)(ImTextFindValidUtf8CodepointEnd(new_text, new_text + new_text_len, new_text + avail) - new_text); // Truncate to closest UTF-8 codepoint. Alternative: return 0 to cancel insertion.
|
||
|
if (new_text_len == 0)
|
||
|
return 0;
|
||
|
|
||
|
// Grow internal buffer if needed
|
||
|
if (new_text_len + text_len + 1 > obj->TextA.Size)
|
||
|
IM_ASSERT(obj->TextSrc == obj->TextA.Data);
|
||
|
if (text_len + new_text_len + 1 > obj->TextA.Size && is_resizable)
|
||
|
{
|
||
|
if (!is_resizable)
|
||
|
return false;
|
||
|
obj->TextA.resize(text_len + ImClamp(new_text_len, 32, ImMax(256, new_text_len)) + 1);
|
||
|
obj->TextSrc = obj->TextA.Data;
|
||
|
}
|
||
|
|
||
|
char* text = obj->TextA.Data;
|
||
| ... | ... | |
|
obj->TextLen += new_text_len;
|
||
|
obj->TextA[obj->TextLen] = '\0';
|
||
|
|
||
|
return true;
|
||
|
return new_text_len;
|
||
|
}
|
||
|
|
||
|
// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols)
|
||
| ... | ... | |
|
state->cursor = state->select_start = state->select_end = 0;
|
||
|
if (text_len <= 0)
|
||
|
return;
|
||
|
if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len))
|
||
|
int text_len_inserted = ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len);
|
||
|
if (text_len_inserted > 0)
|
||
|
{
|
||
|
state->cursor = state->select_start = state->select_end = text_len;
|
||
|
state->has_preferred_x = 0;
|
||
| ... | ... | |
|
stb_textedit_key(this, Stb, key);
|
||
|
CursorFollow = true;
|
||
|
CursorAnimReset();
|
||
|
const int key_u = (key & ~STB_TEXTEDIT_K_SHIFT);
|
||
|
if (key_u == STB_TEXTEDIT_K_LEFT || key_u == STB_TEXTEDIT_K_LINESTART || key_u == STB_TEXTEDIT_K_TEXTSTART || key_u == STB_TEXTEDIT_K_BACKSPACE || key_u == STB_TEXTEDIT_K_WORDLEFT)
|
||
|
LastMoveDirectionLR = ImGuiDir_Left;
|
||
|
else if (key_u == STB_TEXTEDIT_K_RIGHT || key_u == STB_TEXTEDIT_K_LINEEND || key_u == STB_TEXTEDIT_K_TEXTEND || key_u == STB_TEXTEDIT_K_DELETE || key_u == STB_TEXTEDIT_K_WORDRIGHT)
|
||
|
LastMoveDirectionLR = ImGuiDir_Right;
|
||
|
}
|
||
|
|
||
|
void ImGuiInputTextState::OnCharPressed(unsigned int c)
|
||
| ... | ... | |
|
// The changes we had to make to stb_textedit_key made it very much UTF-8 specific which is not too great.
|
||
|
char utf8[5];
|
||
|
ImTextCharToUtf8(utf8, c);
|
||
|
stb_textedit_text(this, Stb, utf8, (int)strlen(utf8));
|
||
|
stb_textedit_text(this, Stb, utf8, (int)ImStrlen(utf8));
|
||
|
CursorFollow = true;
|
||
|
CursorAnimReset();
|
||
|
}
|
||
| ... | ... | |
|
int ImGuiInputTextState::GetCursorPos() const { return Stb->cursor; }
|
||
|
int ImGuiInputTextState::GetSelectionStart() const { return Stb->select_start; }
|
||
|
int ImGuiInputTextState::GetSelectionEnd() const { return Stb->select_end; }
|
||
|
float ImGuiInputTextState::GetPreferredOffsetX() const { return Stb->has_preferred_x ? Stb->preferred_x : -1; }
|
||
|
void ImGuiInputTextState::SelectAll() { Stb->select_start = 0; Stb->cursor = Stb->select_end = TextLen; Stb->has_preferred_x = 0; }
|
||
|
void ImGuiInputTextState::ReloadUserBufAndSelectAll() { ReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; }
|
||
|
void ImGuiInputTextState::ReloadUserBufAndKeepSelection() { ReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; }
|
||
|
void ImGuiInputTextState::ReloadUserBufAndMoveToEnd() { ReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; }
|
||
|
void ImGuiInputTextState::ReloadUserBufAndSelectAll() { WantReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; }
|
||
|
void ImGuiInputTextState::ReloadUserBufAndKeepSelection() { WantReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; }
|
||
|
void ImGuiInputTextState::ReloadUserBufAndMoveToEnd() { WantReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; }
|
||
|
|
||
|
ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
|
||
|
{
|
||
|
memset(this, 0, sizeof(*this));
|
||
|
}
|
||
|
|
||
|
// Public API to manipulate UTF-8 text
|
||
|
// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
|
||
|
// Public API to manipulate UTF-8 text from within a callback.
|
||
|
// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
|
||
|
// Historically they existed because STB_TEXTEDIT_INSERTCHARS() etc. worked on our ImWchar
|
||
|
// buffer, but nowadays they both work on UTF-8 data. Should aim to merge both.
|
||
|
void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
|
||
|
{
|
||
|
IM_ASSERT(pos + bytes_count <= BufTextLen);
|
||
|
char* dst = Buf + pos;
|
||
|
const char* src = Buf + pos + bytes_count;
|
||
|
while (char c = *src++)
|
||
|
*dst++ = c;
|
||
|
*dst = '\0';
|
||
|
memmove(dst, src, BufTextLen - bytes_count - pos + 1);
|
||
|
|
||
|
if (CursorPos >= pos + bytes_count)
|
||
|
CursorPos -= bytes_count;
|
||
| ... | ... | |
|
if (new_text == new_text_end)
|
||
|
return;
|
||
|
|
||
|
// Grow internal buffer if needed
|
||
|
ImGuiContext& g = *Ctx;
|
||
|
ImGuiInputTextState* obj = &g.InputTextState;
|
||
|
IM_ASSERT(obj->ID != 0 && g.ActiveId == obj->ID);
|
||
|
const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
|
||
|
const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
|
||
|
if (new_text_len + BufTextLen >= BufSize)
|
||
|
{
|
||
|
if (!is_resizable)
|
||
|
return;
|
||
|
const bool is_readonly = (Flags & ImGuiInputTextFlags_ReadOnly) != 0;
|
||
|
int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)ImStrlen(new_text);
|
||
|
|
||
|
// We support partial insertion (with a mod in stb_textedit.h)
|
||
|
const int avail = BufSize - 1 - BufTextLen;
|
||
|
if (!is_resizable && new_text_len > avail)
|
||
|
new_text_len = (int)(ImTextFindValidUtf8CodepointEnd(new_text, new_text + new_text_len, new_text + avail) - new_text); // Truncate to closest UTF-8 codepoint. Alternative: return 0 to cancel insertion.
|
||
|
if (new_text_len == 0)
|
||
|
return;
|
||
|
|
||
|
// Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the mildly similar code (until we remove the U16 buffer altogether!)
|
||
|
ImGuiContext& g = *Ctx;
|
||
|
ImGuiInputTextState* edit_state = &g.InputTextState;
|
||
|
IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
|
||
|
IM_ASSERT(Buf == edit_state->TextA.Data);
|
||
|
// Grow internal buffer if needed
|
||
|
if (new_text_len + BufTextLen + 1 > obj->TextA.Size && is_resizable && !is_readonly)
|
||
|
{
|
||
|
IM_ASSERT(Buf == obj->TextA.Data);
|
||
|
int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
|
||
|
edit_state->TextA.resize(new_buf_size + 1);
|
||
|
Buf = edit_state->TextA.Data;
|
||
|
BufSize = edit_state->BufCapacity = new_buf_size;
|
||
|
obj->TextA.resize(new_buf_size + 1);
|
||
|
obj->TextSrc = obj->TextA.Data;
|
||
|
Buf = obj->TextA.Data;
|
||
|
BufSize = obj->BufCapacity = new_buf_size;
|
||
|
}
|
||
|
|
||
|
if (BufTextLen != pos)
|
||
| ... | ... | |
|
memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
|
||
|
Buf[BufTextLen + new_text_len] = '\0';
|
||
|
|
||
|
BufDirty = true;
|
||
|
BufTextLen += new_text_len;
|
||
|
if (CursorPos >= pos)
|
||
|
CursorPos += new_text_len;
|
||
|
CursorPos = ImClamp(CursorPos, 0, BufTextLen);
|
||
|
SelectionStart = SelectionEnd = CursorPos;
|
||
|
BufDirty = true;
|
||
|
BufTextLen += new_text_len;
|
||
|
}
|
||
|
|
||
|
void ImGui::PushPasswordFont()
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked;
|
||
|
IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0);
|
||
|
ImFontGlyph* glyph = g.FontBaked->FindGlyph('*');
|
||
|
g.InputTextPasswordFontBackupFlags = g.Font->Flags;
|
||
|
backup->FallbackGlyphIndex = g.FontBaked->FallbackGlyphIndex;
|
||
|
backup->FallbackAdvanceX = g.FontBaked->FallbackAdvanceX;
|
||
|
backup->IndexLookup.swap(g.FontBaked->IndexLookup);
|
||
|
backup->IndexAdvanceX.swap(g.FontBaked->IndexAdvanceX);
|
||
|
g.Font->Flags |= ImFontFlags_NoLoadGlyphs;
|
||
|
g.FontBaked->FallbackGlyphIndex = g.FontBaked->Glyphs.index_from_ptr(glyph);
|
||
|
g.FontBaked->FallbackAdvanceX = glyph->AdvanceX;
|
||
|
}
|
||
|
|
||
|
void ImGui::PopPasswordFont()
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked;
|
||
|
g.Font->Flags = g.InputTextPasswordFontBackupFlags;
|
||
|
g.FontBaked->FallbackGlyphIndex = backup->FallbackGlyphIndex;
|
||
|
g.FontBaked->FallbackAdvanceX = backup->FallbackAdvanceX;
|
||
|
g.FontBaked->IndexLookup.swap(backup->IndexLookup);
|
||
|
g.FontBaked->IndexAdvanceX.swap(backup->IndexAdvanceX);
|
||
|
IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0);
|
||
|
}
|
||
|
|
||
|
// Return false to discard a character.
|
||
| ... | ... | |
|
if (c < 0x20)
|
||
|
{
|
||
|
bool pass = false;
|
||
|
pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0; // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code)
|
||
|
pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0; // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code)
|
||
|
if (c == '\n' && input_source_is_clipboard && (flags & ImGuiInputTextFlags_Multiline) == 0) // In single line mode, replace \n with a space
|
||
|
{
|
||
|
c = *p_char = ' ';
|
||
|
pass = true;
|
||
|
}
|
||
|
pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0;
|
||
|
pass |= (c == '\t') && (flags & ImGuiInputTextFlags_AllowTabInput) != 0;
|
||
|
if (!pass)
|
||
|
return false;
|
||
| ... | ... | |
|
if (c == '.' || c == ',')
|
||
|
c = c_decimal_point;
|
||
|
|
||
|
// Full-width -> half-width conversion for numeric fields (https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
|
||
|
// Full-width -> half-width conversion for numeric fields: https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
|
||
|
// While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may
|
||
|
// scratch their head as to why it works in numerical fields vs in generic text fields it would require support in the font.
|
||
|
if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | ImGuiInputTextFlags_CharsHexadecimal))
|
||
| ... | ... | |
|
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.
|
||
|
// Find the shortest single replacement we can make to get from old_buf to new_buf
|
||
|
// Note that this doesn't directly alter state->TextA, state->TextLen. They are expected to be made valid separately.
|
||
|
// 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)
|
||
|
static void InputTextReconcileUndoState(ImGuiInputTextState* state, const char* old_buf, int old_length, const char* new_buf, int new_length)
|
||
|
{
|
||
|
const char* old_buf = state->CallbackTextBackup.Data;
|
||
|
const int old_length = state->CallbackTextBackup.Size - 1;
|
||
|
|
||
|
const int shorter_length = ImMin(old_length, 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_a[first_diff])
|
||
|
if (old_buf[first_diff] != new_buf[first_diff])
|
||
|
break;
|
||
|
if (first_diff == old_length && first_diff == new_length_a)
|
||
|
if (first_diff == old_length && first_diff == new_length)
|
||
|
return;
|
||
|
|
||
|
int old_last_diff = old_length - 1;
|
||
|
int new_last_diff = new_length_a - 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_a[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;
|
||
| ... | ... | |
|
else
|
||
|
{
|
||
|
IM_ASSERT(state->TextA.Data != 0);
|
||
|
IM_ASSERT(state->TextA[state->TextLen] == 0);
|
||
|
g.InputTextDeactivatedState.TextA.resize(state->TextLen + 1);
|
||
|
memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->TextLen + 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int* ImLowerBound(int* in_begin, int* in_end, int v)
|
||
|
{
|
||
|
int* in_p = in_begin;
|
||
|
for (size_t count = (size_t)(in_end - in_p); count > 0; )
|
||
|
{
|
||
|
size_t count2 = count >> 1;
|
||
|
int* mid = in_p + count2;
|
||
|
if (*mid < v)
|
||
|
{
|
||
|
in_p = ++mid;
|
||
|
count -= count2 + 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
count = count2;
|
||
|
}
|
||
|
}
|
||
|
return in_p;
|
||
|
}
|
||
|
|
||
|
// FIXME-WORDWRAP: Bundle some of this into ImGuiTextIndex and/or extract as a different tool?
|
||
|
// 'max_output_buffer_size' happens to be a meaningful optimization to avoid writing the full line_index when not necessarily needed (e.g. very large buffer, scrolled up, inactive)
|
||
|
static int InputTextLineIndexBuild(ImGuiInputTextFlags flags, ImGuiTextIndex* line_index, const char* buf, const char* buf_end, float wrap_width, int max_output_buffer_size, const char** out_buf_end)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
int size = 0;
|
||
|
const char* s;
|
||
|
if (flags & ImGuiInputTextFlags_WordWrap)
|
||
|
{
|
||
|
for (s = buf; s < buf_end; s = (*s == '\n') ? s + 1 : s)
|
||
|
{
|
||
|
if (size++ <= max_output_buffer_size)
|
||
|
line_index->Offsets.push_back((int)(s - buf));
|
||
|
s = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, s, buf_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks);
|
||
|
}
|
||
|
}
|
||
|
else if (buf_end != NULL)
|
||
|
{
|
||
|
for (s = buf; s < buf_end; s = s ? s + 1 : buf_end)
|
||
|
{
|
||
|
if (size++ <= max_output_buffer_size)
|
||
|
line_index->Offsets.push_back((int)(s - buf));
|
||
|
s = (const char*)ImMemchr(s, '\n', buf_end - s);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
const char* s_eol;
|
||
|
for (s = buf; ; s = s_eol + 1)
|
||
|
{
|
||
|
if (size++ <= max_output_buffer_size)
|
||
|
line_index->Offsets.push_back((int)(s - buf));
|
||
|
if ((s_eol = strchr(s, '\n')) != NULL)
|
||
|
continue;
|
||
|
s += strlen(s);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (out_buf_end != NULL)
|
||
|
*out_buf_end = buf_end = s;
|
||
|
if (size == 0)
|
||
|
{
|
||
|
line_index->Offsets.push_back(0);
|
||
|
size++;
|
||
|
}
|
||
|
if (buf_end > buf && buf_end[-1] == '\n' && size <= max_output_buffer_size)
|
||
|
{
|
||
|
line_index->Offsets.push_back((int)(buf_end - buf));
|
||
|
size++;
|
||
|
}
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
static ImVec2 InputTextLineIndexGetPosOffset(ImGuiContext& g, ImGuiInputTextState* state, ImGuiTextIndex* line_index, const char* buf, const char* buf_end, int cursor_n)
|
||
|
{
|
||
|
const char* cursor_ptr = buf + cursor_n;
|
||
|
int* it_begin = line_index->Offsets.begin();
|
||
|
int* it_end = line_index->Offsets.end();
|
||
|
const int* it = ImLowerBound(it_begin, it_end, cursor_n);
|
||
|
if (it > it_begin)
|
||
|
if (it == it_end || *it != cursor_n || (state != NULL && state->WrapWidth > 0.0f && state->LastMoveDirectionLR == ImGuiDir_Right && cursor_ptr[-1] != '\n' && cursor_ptr[-1] != 0))
|
||
|
it--;
|
||
|
|
||
|
const int line_no = (it == it_begin) ? 0 : line_index->Offsets.index_from_ptr(it);
|
||
|
const char* line_start = line_index->get_line_begin(buf, line_no);
|
||
|
ImVec2 offset;
|
||
|
offset.x = InputTextCalcTextSize(&g, line_start, cursor_ptr, buf_end, NULL, NULL, ImDrawTextFlags_WrapKeepBlanks).x;
|
||
|
offset.y = (line_no + 1) * g.FontSize;
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
// Edit a string of text
|
||
|
// - 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
|
||
|
// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
|
||
|
// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect.
|
||
|
// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
|
||
|
// (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are
|
||
|
// doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
|
||
|
// - If you want to use InputText() with std::string or any custom dynamic string type, use the wrapper in misc/cpp/imgui_stdlib.h/.cpp!
|
||
|
bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
|
||
|
{
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
| ... | ... | |
|
IM_ASSERT(buf != NULL && buf_size >= 0);
|
||
|
IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys)
|
||
|
IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
|
||
|
IM_ASSERT(!((flags & ImGuiInputTextFlags_ElideLeft) && (flags & ImGuiInputTextFlags_Multiline))); // Multiline does not not work with left-trimming
|
||
|
IM_ASSERT((flags & ImGuiInputTextFlags_WordWrap) == 0 || (flags & ImGuiInputTextFlags_Password) == 0); // WordWrap does not work with Password mode.
|
||
|
IM_ASSERT((flags & ImGuiInputTextFlags_WordWrap) == 0 || (flags & ImGuiInputTextFlags_Multiline) != 0); // WordWrap does not work in single-line mode.
|
||
|
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiIO& io = g.IO;
|
||
| ... | ... | |
|
item_data_backup = g.LastItemData;
|
||
|
window->DC.CursorPos = backup_pos;
|
||
|
|
||
|
// Prevent NavActivation from Tabbing when our widget accepts Tab inputs: this allows cycling through widgets without stopping.
|
||
|
if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && (flags & ImGuiInputTextFlags_AllowTabInput))
|
||
|
// Prevent NavActivation from explicit Tabbing when our widget accepts Tab inputs: this allows cycling through widgets without stopping.
|
||
|
if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && !(g.NavActivateFlags & ImGuiActivateFlags_FromFocusApi) && (flags & ImGuiInputTextFlags_AllowTabInput))
|
||
|
g.NavActivateId = 0;
|
||
|
|
||
|
// Prevent NavActivate reactivating in BeginChild() when we are already active.
|
||
| ... | ... | |
|
if (is_resizable)
|
||
|
IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
|
||
|
|
||
|
// Word-wrapping: enforcing a fixed width not altered by vertical scrollbar makes things easier, notably to track cursor reliably and avoid one-frame glitches.
|
||
|
// Instead of using ImGuiWindowFlags_AlwaysVerticalScrollbar we account for that space if the scrollbar is not visible.
|
||
|
const bool is_wordwrap = (flags & ImGuiInputTextFlags_WordWrap) != 0;
|
||
|
float wrap_width = 0.0f;
|
||
|
if (is_wordwrap)
|
||
|
wrap_width = ImMax(1.0f, GetContentRegionAvail().x + (draw_window->ScrollbarY ? 0.0f : -g.Style.ScrollbarSize));
|
||
|
|
||
|
const bool input_requested_by_nav = (g.ActiveId != id) && ((g.NavActivateId == id) && ((g.NavActivateFlags & ImGuiActivateFlags_PreferInput) || (g.NavInputSource == ImGuiInputSource_Keyboard)));
|
||
|
|
||
|
const bool user_clicked = hovered && io.MouseClicked[0];
|
||
| ... | ... | |
|
|
||
|
float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
|
||
|
|
||
|
const bool init_reload_from_user_buf = (state != NULL && state->ReloadUserBuf);
|
||
|
const bool init_reload_from_user_buf = (state != NULL && state->WantReloadUserBuf);
|
||
|
const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state.
|
||
|
const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav);
|
||
|
const bool init_state = (init_make_active || user_scroll_active);
|
||
|
if ((init_state && g.ActiveId != id) || init_changed_specs || init_reload_from_user_buf)
|
||
|
if (init_reload_from_user_buf)
|
||
|
{
|
||
|
int new_len = (int)ImStrlen(buf);
|
||
|
IM_ASSERT(new_len + 1 <= buf_size && "Is your input buffer properly zero-terminated?");
|
||
|
state->WantReloadUserBuf = false;
|
||
|
InputTextReconcileUndoState(state, state->TextA.Data, state->TextLen, buf, new_len);
|
||
|
state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string.
|
||
|
state->TextLen = new_len;
|
||
|
memcpy(state->TextA.Data, buf, state->TextLen + 1);
|
||
|
state->Stb->select_start = state->ReloadSelectionStart;
|
||
|
state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd; // will be clamped to bounds below
|
||
|
}
|
||
|
else if ((init_state && g.ActiveId != id) || init_changed_specs)
|
||
|
{
|
||
|
// Access state even if we don't own it yet.
|
||
|
state = &g.InputTextState;
|
||
|
state->CursorAnimReset();
|
||
|
state->ReloadUserBuf = false;
|
||
|
|
||
|
// Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714)
|
||
|
InputTextDeactivateHook(state->ID);
|
||
|
|
||
|
// Take a copy of the initial buffer value.
|
||
|
// From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode)
|
||
|
const int buf_len = (int)strlen(buf);
|
||
|
if (!init_reload_from_user_buf)
|
||
|
{
|
||
|
// Take a copy of the initial buffer value.
|
||
|
state->TextToRevertTo.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->TextToRevertTo.Data, buf, buf_len + 1);
|
||
|
}
|
||
|
const int buf_len = (int)ImStrlen(buf);
|
||
|
IM_ASSERT(((buf_len + 1 <= buf_size) || (buf_len == 0 && buf_size == 0)) && "Is your input buffer properly zero-terminated?");
|
||
|
state->TextToRevertTo.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->TextToRevertTo.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 differentiate recycle_cursor vs recycle_undostate?
|
||
|
bool recycle_state = (state->ID == id && !init_changed_specs && !init_reload_from_user_buf);
|
||
|
if (recycle_state && (state->TextLen != buf_len || (strncmp(state->TextA.Data, buf, buf_len) != 0)))
|
||
|
bool recycle_state = (state->ID == id && !init_changed_specs);
|
||
|
if (recycle_state && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0)))
|
||
|
recycle_state = false;
|
||
|
|
||
|
// Start edition
|
||
|
state->ID = id;
|
||
|
state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string.
|
||
|
state->TextLen = (int)strlen(buf);
|
||
|
memcpy(state->TextA.Data, buf, state->TextLen + 1);
|
||
|
|
||
|
if (recycle_state)
|
||
|
state->TextLen = buf_len;
|
||
|
if (!is_readonly)
|
||
|
{
|
||
|
// Recycle existing cursor/selection/undo stack but clamp position
|
||
|
// Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
|
||
|
state->CursorClamp();
|
||
|
state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string.
|
||
|
memcpy(state->TextA.Data, buf, state->TextLen + 1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
state->Scroll = ImVec2(0.0f, 0.0f);
|
||
|
|
||
|
// Find initial scroll position for right alignment
|
||
|
state->Scroll = ImVec2(0.0f, 0.0f);
|
||
|
if (flags & ImGuiInputTextFlags_ElideLeft)
|
||
Update Dear ImGui to version 1.92.5.