InputText: Added support for Page Up/Down in InputTextMultiline. (#3430)

+ fix stb_textedit.h to build with C language (amend fbf70070)
This commit is contained in:
Louis Schnellbach 2020-09-17 11:39:54 +02:00 committed by ocornut
parent 8eca736a7a
commit ec945f44b5
3 changed files with 63 additions and 29 deletions

View File

@ -50,6 +50,7 @@ Other Changes:
- Nav: Fixed using Alt to toggle the Menu layer when inside a Modal window. (#787) - Nav: Fixed using Alt to toggle the Menu layer when inside a Modal window. (#787)
- Scrolling: Fixed SetScrollHere functions edge snapping when called during a frame where ContentSize - Scrolling: Fixed SetScrollHere functions edge snapping when called during a frame where ContentSize
is changing (issue introduced in 1.78). (#3452). is changing (issue introduced in 1.78). (#3452).
- InputText: Added support for Page Up/Down in InputTextMultiline(). (#3430) [@Xipiryon]
- InputText: Added selection helpers in ImGuiInputTextCallbackData(). - InputText: Added selection helpers in ImGuiInputTextCallbackData().
- InputText: Added ImGuiInputTextFlags_CallbackEdit to modify internally owned buffer after an edit. - InputText: Added ImGuiInputTextFlags_CallbackEdit to modify internally owned buffer after an edit.
(note that InputText() already returns true on edit, the callback is useful mainly to manipulate the (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the
@ -59,8 +60,9 @@ Other Changes:
- InputText: Fixed minor scrolling glitch when erasing trailing lines in InputTextMultiline(). - InputText: Fixed minor scrolling glitch when erasing trailing lines in InputTextMultiline().
- InputText: Fixed cursor being partially covered after using Ctrl+End key. - InputText: Fixed cursor being partially covered after using Ctrl+End key.
- InputText: Fixed callback's helper DeleteChars() function when cursor is inside the deleted block. (#3454) - InputText: Fixed callback's helper DeleteChars() function when cursor is inside the deleted block. (#3454)
- InputText: Fixed minor inconsistency when pressing Down on the last line when it doesn't have a carriage - InputText: Made pressing Down arrow on the last line when it doesn't have a carriage return not move to the end
return (it used to move to the end of the line). [@Xipiryon] of the line (so it is consistent with Up arrow, and behave same as Notepad and Visual Studio. Note that some
other text editors instead would move the crusor to the end of the line). [@Xipiryon]
- DragFloat, DragScalar: Fixed ImGuiSliderFlags_ClampOnInput not being honored in the special case - DragFloat, DragScalar: Fixed ImGuiSliderFlags_ClampOnInput not being honored in the special case
where v_min == v_max. (#3361) where v_min == v_max. (#3361)
- SliderInt, SliderScalar: Fixed reaching of maximum value with inverted integer min/max ranges, both - SliderInt, SliderScalar: Fixed reaching of maximum value with inverted integer min/max ranges, both

View File

@ -3590,6 +3590,8 @@ static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const Im
#define STB_TEXTEDIT_K_REDO 0x20000B // keyboard input to perform redo #define STB_TEXTEDIT_K_REDO 0x20000B // keyboard input to perform redo
#define STB_TEXTEDIT_K_WORDLEFT 0x20000C // keyboard input to move cursor left one word #define STB_TEXTEDIT_K_WORDLEFT 0x20000C // keyboard input to move cursor left one word
#define STB_TEXTEDIT_K_WORDRIGHT 0x20000D // keyboard input to move cursor right one word #define STB_TEXTEDIT_K_WORDRIGHT 0x20000D // keyboard input to move cursor right one word
#define STB_TEXTEDIT_K_PGUP 0x20000E // keyboard input to move cursor up a page
#define STB_TEXTEDIT_K_PGDOWN 0x20000F // keyboard input to move cursor down a page
#define STB_TEXTEDIT_K_SHIFT 0x400000 #define STB_TEXTEDIT_K_SHIFT 0x400000
#define STB_TEXTEDIT_IMPLEMENTATION #define STB_TEXTEDIT_IMPLEMENTATION
@ -3856,6 +3858,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
bool clear_active_id = false; bool clear_active_id = false;
bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline); bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
const bool init_make_active = (focus_requested || user_clicked || user_scroll_finish || user_nav_input_start); const bool init_make_active = (focus_requested || user_clicked || user_scroll_finish || user_nav_input_start);
const bool init_state = (init_make_active || user_scroll_active); const bool init_state = (init_make_active || user_scroll_active);
if (init_state && g.ActiveId != id) if (init_state && g.ActiveId != id)
@ -3916,7 +3920,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel); g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);
g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Home) | ((ImU64)1 << ImGuiKey_End); g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Home) | ((ImU64)1 << ImGuiKey_End);
if (is_multiline) if (is_multiline)
g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_PageUp) | ((ImU64)1 << ImGuiKey_PageDown); // FIXME-NAV: Page up/down actually not supported yet by widget, but claim them ahead. g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_PageUp) | ((ImU64)1 << ImGuiKey_PageDown);
if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) // Disable keyboard tabbing out as we will use the \t character. if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) // Disable keyboard tabbing out as we will use the \t character.
g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Tab); g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Tab);
} }
@ -4055,6 +4059,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
IM_ASSERT(state != NULL); IM_ASSERT(state != NULL);
IM_ASSERT(io.KeyMods == GetMergedKeyModFlags() && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); // We rarely do this check, but if anything let's do it here. IM_ASSERT(io.KeyMods == GetMergedKeyModFlags() && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); // We rarely do this check, but if anything let's do it here.
const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1);
state->Stb.row_count_per_page = row_count_per_page;
const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
const bool is_osx = io.ConfigMacOSXBehaviors; const bool is_osx = io.ConfigMacOSXBehaviors;
const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiKeyModFlags_Super | ImGuiKeyModFlags_Shift)); const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiKeyModFlags_Super | ImGuiKeyModFlags_Shift));
@ -4074,6 +4081,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_PageUp) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; }
else if (IsKeyPressedMap(ImGuiKey_PageDown) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; }
else if (IsKeyPressedMap(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } else if (IsKeyPressedMap(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_Delete) && !is_readonly) { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Delete) && !is_readonly) { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
@ -4443,12 +4452,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (is_multiline) if (is_multiline)
{ {
// Test if cursor is vertically visible // Test if cursor is vertically visible
float scroll_y = draw_window->Scroll.y;
const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
if (cursor_offset.y - g.FontSize < scroll_y) if (cursor_offset.y - g.FontSize < scroll_y)
scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
else if (cursor_offset.y - inner_size.y >= scroll_y) else if (cursor_offset.y - inner_size.y >= scroll_y)
scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f; scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y); scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y);
draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag
draw_window->Scroll.y = scroll_y; draw_window->Scroll.y = scroll_y;

View File

@ -148,6 +148,8 @@
// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right // STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right
// STB_TEXTEDIT_K_UP keyboard input to move cursor up // STB_TEXTEDIT_K_UP keyboard input to move cursor up
// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down // STB_TEXTEDIT_K_DOWN keyboard input to move cursor down
// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page
// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page
// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME // STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME
// STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END // STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END
// STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME // STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME
@ -170,10 +172,6 @@
// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text // STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text
// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text // STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text
// //
// Todo:
// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page
// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page
//
// Keyboard input must be encoded as a single integer value; e.g. a character code // Keyboard input must be encoded as a single integer value; e.g. a character code
// and some bitflags that represent shift states. to simplify the interface, SHIFT must // and some bitflags that represent shift states. to simplify the interface, SHIFT must
// be a bitflag, so we can test the shifted state of cursor movements to allow selection, // be a bitflag, so we can test the shifted state of cursor movements to allow selection,
@ -337,6 +335,10 @@ typedef struct
// each textfield keeps its own insert mode state. to keep an app-wide // each textfield keeps its own insert mode state. to keep an app-wide
// insert mode, copy this value in/out of the app state // insert mode, copy this value in/out of the app state
int row_count_per_page;
// page size in number of row.
// this value MUST be set to >0 for pageup or pagedown in multilines documents.
///////////////////// /////////////////////
// //
// private data // private data
@ -855,12 +857,16 @@ retry:
break; break;
case STB_TEXTEDIT_K_DOWN: case STB_TEXTEDIT_K_DOWN:
case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: { case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT:
case STB_TEXTEDIT_K_PGDOWN:
case STB_TEXTEDIT_K_PGDOWN | STB_TEXTEDIT_K_SHIFT: {
StbFindState find; StbFindState find;
StbTexteditRow row; StbTexteditRow row;
int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; int i, j, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGDOWN;
int row_count = is_page ? state->row_count_per_page : 1;
if (state->single_line) { if (!is_page && state->single_line) {
// on windows, up&down in single-line behave like left&right // on windows, up&down in single-line behave like left&right
key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT);
goto retry; goto retry;
@ -875,17 +881,19 @@ retry:
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
// now find character position down a row for (j = 0; j < row_count; ++j) {
if (find.length) { float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x;
int start = find.first_char + find.length;
if (find.length == 0)
break;
// [DEAR IMGUI] // [DEAR IMGUI]
// going down while being on the last line shouldn't bring us to that line end // going down while being on the last line shouldn't bring us to that line end
if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE) if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)
break; break;
float goal_x = state->has_preferred_x ? state->preferred_x : find.x; // now find character position down a row
float x;
int start = find.first_char + find.length;
state->cursor = start; state->cursor = start;
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
x = row.x0; x = row.x0;
@ -907,17 +915,25 @@ retry:
if (sel) if (sel)
state->select_end = state->cursor; state->select_end = state->cursor;
// go to next line
find.first_char = find.first_char + find.length;
find.length = row.num_chars;
} }
break; break;
} }
case STB_TEXTEDIT_K_UP: case STB_TEXTEDIT_K_UP:
case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: { case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT:
case STB_TEXTEDIT_K_PGUP:
case STB_TEXTEDIT_K_PGUP | STB_TEXTEDIT_K_SHIFT: {
StbFindState find; StbFindState find;
StbTexteditRow row; StbTexteditRow row;
int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; int i, j, prev_scan, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGUP;
int row_count = is_page ? state->row_count_per_page : 1;
if (state->single_line) { if (!is_page && state->single_line) {
// on windows, up&down become left&right // on windows, up&down become left&right
key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT);
goto retry; goto retry;
@ -932,11 +948,14 @@ retry:
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
for (j = 0; j < row_count; ++j) {
float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x;
// can only go up if there's a previous row // can only go up if there's a previous row
if (find.prev_first != find.first_char) { if (find.prev_first == find.first_char)
break;
// now find character position up a row // now find character position up a row
float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
float x;
state->cursor = find.prev_first; state->cursor = find.prev_first;
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
x = row.x0; x = row.x0;
@ -958,6 +977,14 @@ retry:
if (sel) if (sel)
state->select_end = state->cursor; state->select_end = state->cursor;
// go to previous line
// (we need to scan previous line the hard way. maybe we could expose this as a new API function?)
prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0;
while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE)
--prev_scan;
find.first_char = find.prev_first;
find.prev_first = prev_scan;
} }
break; break;
} }
@ -1081,10 +1108,6 @@ retry:
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
} }
// @TODO:
// STB_TEXTEDIT_K_PGUP - move cursor up a page
// STB_TEXTEDIT_K_PGDOWN - move cursor down a page
} }
} }
@ -1356,6 +1379,7 @@ static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_lin
state->initialized = 1; state->initialized = 1;
state->single_line = (unsigned char) is_single_line; state->single_line = (unsigned char) is_single_line;
state->insert_mode = 0; state->insert_mode = 0;
state->row_count_per_page = 0;
} }
// API initialize // API initialize