|
@@ -1,4 +1,4 @@
|
|
|
-// dear imgui, v1.91.6
|
|
|
+// dear imgui, v1.91.7 WIP
|
|
|
// (widgets code)
|
|
|
|
|
|
/*
|
|
@@ -2440,9 +2440,9 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const
|
|
|
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)
|
|
@@ -2450,7 +2450,7 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const
|
|
|
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));
|
|
|
}
|
|
@@ -2638,6 +2638,10 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
|
|
|
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);
|
|
@@ -3228,6 +3232,10 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
|
|
|
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);
|
|
@@ -3936,12 +3944,12 @@ static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, c
|
|
|
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 <= 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.Font->GetCharAdvance((ImWchar)c) * g.FontScale; }
|
|
|
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);
|
|
|
r->x0 = 0.0f;
|
|
@@ -3960,15 +3968,15 @@ static int IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL(ImGuiInputTextState* obj, int id
|
|
|
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)
|
|
@@ -3991,10 +3999,10 @@ static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)
|
|
|
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);
|
|
@@ -4007,10 +4015,10 @@ static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx)
|
|
|
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);
|
|
@@ -4047,16 +4055,13 @@ static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx)
|
|
|
|
|
|
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)
|
|
@@ -4069,11 +4074,13 @@ static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ch
|
|
|
return false;
|
|
|
|
|
|
// Grow internal buffer if needed
|
|
|
+ IM_ASSERT(obj->TextSrc == obj->TextA.Data);
|
|
|
if (new_text_len + text_len + 1 > obj->TextA.Size)
|
|
|
{
|
|
|
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;
|
|
@@ -4171,26 +4178,25 @@ int ImGuiInputTextState::GetCursorPos() const { return Stb->cursor
|
|
|
int ImGuiInputTextState::GetSelectionStart() const { return Stb->select_start; }
|
|
|
int ImGuiInputTextState::GetSelectionEnd() const { return Stb->select_end; }
|
|
|
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;
|
|
@@ -4215,13 +4221,13 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons
|
|
|
if (!is_resizable)
|
|
|
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);
|
|
|
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);
|
|
|
+ edit_state->TextSrc = edit_state->TextA.Data;
|
|
|
Buf = edit_state->TextA.Data;
|
|
|
BufSize = edit_state->BufCapacity = new_buf_size;
|
|
|
}
|
|
@@ -4339,26 +4345,23 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im
|
|
|
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;
|
|
@@ -4510,64 +4513,65 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
|
|
|
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)strlen(buf);
|
|
|
+ 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;
|
|
|
+ state->CursorClamp();
|
|
|
+ }
|
|
|
+ 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);
|
|
|
- }
|
|
|
+ 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 (!is_readonly)
|
|
|
+ {
|
|
|
+ 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);
|
|
|
+ }
|
|
|
|
|
|
// Find initial scroll position for right alignment
|
|
|
state->Scroll = ImVec2(0.0f, 0.0f);
|
|
|
if (flags & ImGuiInputTextFlags_ElideLeft)
|
|
|
state->Scroll.x += ImMax(0.0f, CalcTextSize(buf).x - frame_size.x + style.FramePadding.x * 2.0f);
|
|
|
|
|
|
+ // 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.
|
|
|
if (recycle_state)
|
|
|
- {
|
|
|
- // 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();
|
|
|
- }
|
|
|
else
|
|
|
- {
|
|
|
stb_textedit_initialize_state(state->Stb, !is_multiline);
|
|
|
- }
|
|
|
|
|
|
- if (init_reload_from_user_buf)
|
|
|
- {
|
|
|
- state->Stb->select_start = state->ReloadSelectionStart;
|
|
|
- state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd;
|
|
|
- state->CursorClamp();
|
|
|
- }
|
|
|
- else if (!is_multiline)
|
|
|
+ if (!is_multiline)
|
|
|
{
|
|
|
if (flags & ImGuiInputTextFlags_AutoSelectAll)
|
|
|
select_all = true;
|
|
@@ -4617,7 +4621,15 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
// Expose scroll in a manner that is agnostic to us using a child window
|
|
|
if (is_multiline && state != NULL)
|
|
|
state->Scroll.y = draw_window->Scroll.y;
|
|
|
+
|
|
|
+ // Read-only mode always ever read from source buffer. Refresh TextLen when active.
|
|
|
+ if (is_readonly && state != NULL)
|
|
|
+ state->TextLen = (int)strlen(buf);
|
|
|
+ //if (is_readonly && state != NULL)
|
|
|
+ // state->TextA.clear(); // Uncomment to facilitate debugging, but we otherwise prefer to keep/amortize th allocation.
|
|
|
}
|
|
|
+ if (state != NULL)
|
|
|
+ state->TextSrc = is_readonly ? buf : state->TextA.Data;
|
|
|
|
|
|
// We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function)
|
|
|
if (g.ActiveId == id && state == NULL)
|
|
@@ -4882,13 +4894,13 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
// Cut, Copy
|
|
|
if (g.PlatformIO.Platform_SetClipboardTextFn != NULL)
|
|
|
{
|
|
|
+ // SetClipboardText() only takes null terminated strings + state->TextSrc may point to read-only user buffer, so we need to make a copy.
|
|
|
const int ib = state->HasSelection() ? ImMin(state->Stb->select_start, state->Stb->select_end) : 0;
|
|
|
const int ie = state->HasSelection() ? ImMax(state->Stb->select_start, state->Stb->select_end) : state->TextLen;
|
|
|
-
|
|
|
- char backup = state->TextA.Data[ie];
|
|
|
- state->TextA.Data[ie] = 0; // A bit of a hack since SetClipboardText only takes null terminated strings
|
|
|
- SetClipboardText(state->TextA.Data + ib);
|
|
|
- state->TextA.Data[ie] = backup;
|
|
|
+ g.TempBuffer.reserve(ie - ib + 1);
|
|
|
+ memcpy(g.TempBuffer.Data, state->TextSrc, ie - ib);
|
|
|
+ g.TempBuffer.Data[ie] = 0;
|
|
|
+ SetClipboardText(g.TempBuffer.Data);
|
|
|
}
|
|
|
if (is_cut)
|
|
|
{
|
|
@@ -4904,25 +4916,27 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
{
|
|
|
// Filter pasted buffer
|
|
|
const int clipboard_len = (int)strlen(clipboard);
|
|
|
- char* clipboard_filtered = (char*)IM_ALLOC(clipboard_len + 1);
|
|
|
- int clipboard_filtered_len = 0;
|
|
|
+ ImVector<char> clipboard_filtered;
|
|
|
+ clipboard_filtered.reserve(clipboard_len + 1);
|
|
|
for (const char* s = clipboard; *s != 0; )
|
|
|
{
|
|
|
unsigned int c;
|
|
|
- int len = ImTextCharFromUtf8(&c, s, NULL);
|
|
|
- s += len;
|
|
|
+ int in_len = ImTextCharFromUtf8(&c, s, NULL);
|
|
|
+ s += in_len;
|
|
|
if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true))
|
|
|
continue;
|
|
|
- memcpy(clipboard_filtered + clipboard_filtered_len, s - len, len);
|
|
|
- clipboard_filtered_len += len;
|
|
|
+ char c_utf8[5];
|
|
|
+ ImTextCharToUtf8(c_utf8, c);
|
|
|
+ int out_len = (int)strlen(c_utf8);
|
|
|
+ clipboard_filtered.resize(clipboard_filtered.Size + out_len);
|
|
|
+ memcpy(clipboard_filtered.Data + clipboard_filtered.Size - out_len, c_utf8, out_len);
|
|
|
}
|
|
|
- clipboard_filtered[clipboard_filtered_len] = 0;
|
|
|
- if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
|
|
|
+ if (clipboard_filtered.Size > 0) // If everything was filtered, ignore the pasting operation
|
|
|
{
|
|
|
- stb_textedit_paste(state, state->Stb, clipboard_filtered, clipboard_filtered_len);
|
|
|
+ clipboard_filtered.push_back(0);
|
|
|
+ stb_textedit_paste(state, state->Stb, clipboard_filtered.Data, clipboard_filtered.Size - 1);
|
|
|
state->CursorFollow = true;
|
|
|
}
|
|
|
- MemFree(clipboard_filtered);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -5015,10 +5029,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
callback_data.UserData = callback_user_data;
|
|
|
|
|
|
// FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925
|
|
|
+ char* callback_buf = is_readonly ? buf : state->TextA.Data;
|
|
|
+ IM_ASSERT(callback_buf == state->TextSrc);
|
|
|
state->CallbackTextBackup.resize(state->TextLen + 1);
|
|
|
- memcpy(state->CallbackTextBackup.Data, state->TextA.Data, state->TextLen + 1);
|
|
|
+ memcpy(state->CallbackTextBackup.Data, callback_buf, state->TextLen + 1);
|
|
|
|
|
|
- char* callback_buf = is_readonly ? buf : state->TextA.Data;
|
|
|
callback_data.EventKey = event_key;
|
|
|
callback_data.Buf = callback_buf;
|
|
|
callback_data.BufTextLen = state->TextLen;
|
|
@@ -5045,7 +5060,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
{
|
|
|
// Callback may update buffer and thus set buf_dirty even in read-only mode.
|
|
|
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() ?
|
|
|
+ InputTextReconcileUndoState(state, state->CallbackTextBackup.Data, state->CallbackTextBackup.Size - 1, callback_data.Buf, callback_data.BufTextLen);
|
|
|
state->TextLen = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
|
|
|
state->CursorAnimReset();
|
|
|
}
|
|
@@ -5053,9 +5068,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
}
|
|
|
|
|
|
// Will copy result string if modified
|
|
|
- if (!is_readonly && strcmp(state->TextA.Data, buf) != 0)
|
|
|
+ if (!is_readonly && strcmp(state->TextSrc, buf) != 0)
|
|
|
{
|
|
|
- apply_new_text = state->TextA.Data;
|
|
|
+ apply_new_text = state->TextSrc;
|
|
|
apply_new_text_length = state->TextLen;
|
|
|
value_changed = true;
|
|
|
}
|
|
@@ -5149,7 +5164,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
// - Measure text height (for scrollbar)
|
|
|
// We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort)
|
|
|
// FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
|
|
|
- const char* text_begin = state->TextA.Data;
|
|
|
+ const char* text_begin = buf_display;
|
|
|
const char* text_end = text_begin + state->TextLen;
|
|
|
ImVec2 cursor_offset, select_start_offset;
|
|
|
|
|
@@ -5330,6 +5345,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
g.LastItemData.StatusFlags = item_data_backup.StatusFlags;
|
|
|
}
|
|
|
}
|
|
|
+ if (state)
|
|
|
+ state->TextSrc = NULL;
|
|
|
|
|
|
// Log as text
|
|
|
if (g.LogEnabled && (!is_password || is_displaying_hint))
|
|
@@ -8213,15 +8230,10 @@ void ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
// This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.
|
|
|
-// This handle some subtleties with capturing info from the label, but for 99% uses it could essentially be rewritten as:
|
|
|
-// if (ImGui::BeginChild("...", ImVec2(ImGui::CalcItemWidth(), ImGui::GetTextLineHeight() * 7.5f), ImGuiChildFlags_FrameStyle))
|
|
|
-// { .... }
|
|
|
-// ImGui::EndChild();
|
|
|
-// ImGui::SameLine();
|
|
|
-// ImGui::AlignTextToFramePadding();
|
|
|
-// ImGui::Text("Label");
|
|
|
+// This handle some subtleties with capturing info from the label.
|
|
|
+// If you don't need a label you can pretty much directly use ImGui::BeginChild() with ImGuiChildFlags_FrameStyle.
|
|
|
// Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty"
|
|
|
-// Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.25 * item_height).
|
|
|
+// Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.5f * item_height).
|
|
|
bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)
|
|
|
{
|
|
|
ImGuiContext& g = *GImGui;
|