diff --git a/clib.json b/clib.json index bd3894f4..a3b05f86 100644 --- a/clib.json +++ b/clib.json @@ -1,6 +1,6 @@ { "name": "nuklear", - "version": "4.12.6", + "version": "4.12.7", "repo": "Immediate-Mode-UI/Nuklear", "description": "A small ANSI C gui toolkit", "keywords": ["gl", "ui", "toolkit"], diff --git a/demo/glfw_opengl3/Makefile b/demo/glfw_opengl3/Makefile index 35a9c65a..be7da50a 100644 --- a/demo/glfw_opengl3/Makefile +++ b/demo/glfw_opengl3/Makefile @@ -2,7 +2,7 @@ BIN = demo # Flags -CFLAGS += -std=c89 -Wall -Wextra -pedantic +CFLAGS += -g -std=c89 -Wall -Wextra -pedantic SRC = main.c OBJ = $(SRC:.c=.o) diff --git a/nuklear.h b/nuklear.h index 9d63deb6..71b55a24 100644 --- a/nuklear.h +++ b/nuklear.h @@ -109,6 +109,7 @@ NK_BUTTON_TRIGGER_ON_RELEASE | Different platforms require button clicks occu NK_ZERO_COMMAND_MEMORY | Defining this will zero out memory for each drawing command added to a drawing queue (inside nk_command_buffer_push). Zeroing command memory is very useful for fast checking (using memcmp) if command buffers are equal and avoid drawing frames when nothing on screen has changed since previous frame. NK_UINT_DRAW_INDEX | Defining this will set the size of vertex index elements when using NK_VERTEX_BUFFER_OUTPUT to 32bit instead of the default of 16bit NK_KEYSTATE_BASED_INPUT | Define this if your backend uses key state for each frame rather than key press/release events +NK_IS_WORD_BOUNDARY(c) | Define this to a function macro that takes a single nk_rune (nk_uint) and returns true if it's a word separator. If not defined, uses the default definition (see nk_is_word_boundary()) !!! WARNING The following flags will pull in the standard C library: @@ -27054,21 +27055,28 @@ nk_is_word_boundary( struct nk_text_edit *state, int idx) { int len; nk_rune c; - if (idx <= 0) return 1; + if (idx < 0) return 1; if (!nk_str_at_rune(&state->string, idx, &c, &len)) return 1; - return (c == ' ' || c == '\t' ||c == 0x3000 || c == ',' || c == ';' || - c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c == ']' || - c == '|'); +#ifndef NK_IS_WORD_BOUNDARY + return (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || + c == '\v' || c == 0x3000); +#else + return NK_IS_WORD_BOUNDARY(c); +#endif } NK_INTERN int nk_textedit_move_to_word_previous(struct nk_text_edit *state) { int c = state->cursor - 1; - while( c >= 0 && !nk_is_word_boundary(state, c)) - --c; - - if( c < 0 ) - c = 0; + if (c > 0) { + if (nk_is_word_boundary(state, c)) { + while (c > 0 && nk_is_word_boundary(state, --c)); + } + while (!nk_is_word_boundary(state, --c)); + c++; + } else { + return 0; + } return c; } @@ -27076,12 +27084,15 @@ NK_INTERN int nk_textedit_move_to_word_next(struct nk_text_edit *state) { const int len = state->string.len; - int c = state->cursor+1; - while( c < len && !nk_is_word_boundary(state, c)) - ++c; - - if( c > len ) - c = len; + int c = state->cursor; + if (c < len) { + if (!nk_is_word_boundary(state, c)) { + while (c < len && !nk_is_word_boundary(state, ++c)); + } + while (c < len && nk_is_word_boundary(state, ++c)); + } else { + return len; + } return c; } @@ -27266,7 +27277,7 @@ nk_textedit_key(struct nk_text_edit *state, enum nk_keys key, int shift_mod, case NK_KEY_TEXT_WORD_LEFT: if (shift_mod) { if( !NK_TEXT_HAS_SELECTION( state ) ) - nk_textedit_prep_selection_at_cursor(state); + nk_textedit_prep_selection_at_cursor(state); state->cursor = nk_textedit_move_to_word_previous(state); state->select_end = state->cursor; nk_textedit_clamp(state ); @@ -27995,7 +28006,6 @@ nk_do_edit(nk_flags *state, struct nk_command_buffer *out, /* update edit state */ prev_state = (char)edit->active; - is_hovered = (char)nk_input_is_mouse_hovering_rect(in, bounds); if (in && in->mouse.buttons[NK_BUTTON_LEFT].clicked && in->mouse.buttons[NK_BUTTON_LEFT].down) { edit->active = NK_INBOX(in->mouse.pos.x, in->mouse.pos.y, bounds.x, bounds.y, bounds.w, bounds.h); @@ -28287,10 +28297,12 @@ nk_do_edit(nk_flags *state, struct nk_command_buffer *out, } else edit->scrollbar.x = 0; if (flags & NK_EDIT_MULTILINE) { - /* vertical scroll */ + /* vertical scroll: like horizontal, it only adjusts if the + * cursor leaves the visible area, and then only just enough + * to keep it visible */ if (cursor_pos.y < edit->scrollbar.y) - edit->scrollbar.y = NK_MAX(0.0f, cursor_pos.y - row_height); - if (cursor_pos.y >= edit->scrollbar.y + row_height) + edit->scrollbar.y = NK_MAX(0.0f, cursor_pos.y); + if (cursor_pos.y > edit->scrollbar.y + area.h - row_height) edit->scrollbar.y = edit->scrollbar.y + row_height; } else edit->scrollbar.y = 0; } @@ -28313,9 +28325,13 @@ nk_do_edit(nk_flags *state, struct nk_command_buffer *out, scroll_step = scroll.h * 0.10f; scroll_inc = scroll.h * 0.01f; scroll_target = text_size.y; - edit->scrollbar.y = nk_do_scrollbarv(&ws, out, scroll, 0, + edit->scrollbar.y = nk_do_scrollbarv(&ws, out, scroll, is_hovered, scroll_offset, scroll_target, scroll_step, scroll_inc, &style->scrollbar, in, font); + /* Eat mouse scroll if we're active */ + if (is_hovered && in->mouse.scroll_delta.y) { + in->mouse.scroll_delta.y = 0; + } } } @@ -30700,6 +30716,7 @@ nk_tooltipfv(struct nk_context *ctx, const char *fmt, va_list args) /// - [y]: Minor version with non-breaking API and library changes /// - [z]: Patch version with no direct changes to the API /// +/// - 2025/04/06 (4.12.7) - Fix text input navigation and mouse scrolling /// - 2025/03/29 (4.12.6) - Fix unitialized data in nk_input_char /// - 2025/03/05 (4.12.5) - Fix scrolling knob also scrolling parent window, remove dead code /// - 2024/12/11 (4.12.4) - Fix array subscript [0, 0] is outside array bounds of ‘char[1]’ diff --git a/src/CHANGELOG b/src/CHANGELOG index a1b0e7a7..300c3d40 100644 --- a/src/CHANGELOG +++ b/src/CHANGELOG @@ -7,6 +7,7 @@ /// - [y]: Minor version with non-breaking API and library changes /// - [z]: Patch version with no direct changes to the API /// +/// - 2025/04/06 (4.12.7) - Fix text input navigation and mouse scrolling /// - 2025/03/29 (4.12.6) - Fix unitialized data in nk_input_char /// - 2025/03/05 (4.12.5) - Fix scrolling knob also scrolling parent window, remove dead code /// - 2024/12/11 (4.12.4) - Fix array subscript [0, 0] is outside array bounds of ‘char[1]’ diff --git a/src/HEADER.md b/src/HEADER.md index c502f1be..60b24788 100644 --- a/src/HEADER.md +++ b/src/HEADER.md @@ -108,6 +108,7 @@ NK_BUTTON_TRIGGER_ON_RELEASE | Different platforms require button clicks occu NK_ZERO_COMMAND_MEMORY | Defining this will zero out memory for each drawing command added to a drawing queue (inside nk_command_buffer_push). Zeroing command memory is very useful for fast checking (using memcmp) if command buffers are equal and avoid drawing frames when nothing on screen has changed since previous frame. NK_UINT_DRAW_INDEX | Defining this will set the size of vertex index elements when using NK_VERTEX_BUFFER_OUTPUT to 32bit instead of the default of 16bit NK_KEYSTATE_BASED_INPUT | Define this if your backend uses key state for each frame rather than key press/release events +NK_IS_WORD_BOUNDARY(c) | Define this to a function macro that takes a single nk_rune (nk_uint) and returns true if it's a word separator. If not defined, uses the default definition (see nk_is_word_boundary()) !!! WARNING The following flags will pull in the standard C library: diff --git a/src/nuklear_edit.c b/src/nuklear_edit.c index a7b2bfcf..4106cf87 100644 --- a/src/nuklear_edit.c +++ b/src/nuklear_edit.c @@ -188,7 +188,6 @@ nk_do_edit(nk_flags *state, struct nk_command_buffer *out, /* update edit state */ prev_state = (char)edit->active; - is_hovered = (char)nk_input_is_mouse_hovering_rect(in, bounds); if (in && in->mouse.buttons[NK_BUTTON_LEFT].clicked && in->mouse.buttons[NK_BUTTON_LEFT].down) { edit->active = NK_INBOX(in->mouse.pos.x, in->mouse.pos.y, bounds.x, bounds.y, bounds.w, bounds.h); @@ -480,10 +479,12 @@ nk_do_edit(nk_flags *state, struct nk_command_buffer *out, } else edit->scrollbar.x = 0; if (flags & NK_EDIT_MULTILINE) { - /* vertical scroll */ + /* vertical scroll: like horizontal, it only adjusts if the + * cursor leaves the visible area, and then only just enough + * to keep it visible */ if (cursor_pos.y < edit->scrollbar.y) - edit->scrollbar.y = NK_MAX(0.0f, cursor_pos.y - row_height); - if (cursor_pos.y >= edit->scrollbar.y + row_height) + edit->scrollbar.y = NK_MAX(0.0f, cursor_pos.y); + if (cursor_pos.y > edit->scrollbar.y + area.h - row_height) edit->scrollbar.y = edit->scrollbar.y + row_height; } else edit->scrollbar.y = 0; } @@ -506,9 +507,13 @@ nk_do_edit(nk_flags *state, struct nk_command_buffer *out, scroll_step = scroll.h * 0.10f; scroll_inc = scroll.h * 0.01f; scroll_target = text_size.y; - edit->scrollbar.y = nk_do_scrollbarv(&ws, out, scroll, 0, + edit->scrollbar.y = nk_do_scrollbarv(&ws, out, scroll, is_hovered, scroll_offset, scroll_target, scroll_step, scroll_inc, &style->scrollbar, in, font); + /* Eat mouse scroll if we're active */ + if (is_hovered && in->mouse.scroll_delta.y) { + in->mouse.scroll_delta.y = 0; + } } } diff --git a/src/nuklear_text_editor.c b/src/nuklear_text_editor.c index 1517d81b..e3bfea0a 100644 --- a/src/nuklear_text_editor.c +++ b/src/nuklear_text_editor.c @@ -277,21 +277,28 @@ nk_is_word_boundary( struct nk_text_edit *state, int idx) { int len; nk_rune c; - if (idx <= 0) return 1; + if (idx < 0) return 1; if (!nk_str_at_rune(&state->string, idx, &c, &len)) return 1; - return (c == ' ' || c == '\t' ||c == 0x3000 || c == ',' || c == ';' || - c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c == ']' || - c == '|'); +#ifndef NK_IS_WORD_BOUNDARY + return (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || + c == '\v' || c == 0x3000); +#else + return NK_IS_WORD_BOUNDARY(c); +#endif } NK_INTERN int nk_textedit_move_to_word_previous(struct nk_text_edit *state) { int c = state->cursor - 1; - while( c >= 0 && !nk_is_word_boundary(state, c)) - --c; - - if( c < 0 ) - c = 0; + if (c > 0) { + if (nk_is_word_boundary(state, c)) { + while (c > 0 && nk_is_word_boundary(state, --c)); + } + while (!nk_is_word_boundary(state, --c)); + c++; + } else { + return 0; + } return c; } @@ -299,12 +306,15 @@ NK_INTERN int nk_textedit_move_to_word_next(struct nk_text_edit *state) { const int len = state->string.len; - int c = state->cursor+1; - while( c < len && !nk_is_word_boundary(state, c)) - ++c; - - if( c > len ) - c = len; + int c = state->cursor; + if (c < len) { + if (!nk_is_word_boundary(state, c)) { + while (c < len && !nk_is_word_boundary(state, ++c)); + } + while (c < len && nk_is_word_boundary(state, ++c)); + } else { + return len; + } return c; } @@ -489,7 +499,7 @@ nk_textedit_key(struct nk_text_edit *state, enum nk_keys key, int shift_mod, case NK_KEY_TEXT_WORD_LEFT: if (shift_mod) { if( !NK_TEXT_HAS_SELECTION( state ) ) - nk_textedit_prep_selection_at_cursor(state); + nk_textedit_prep_selection_at_cursor(state); state->cursor = nk_textedit_move_to_word_previous(state); state->select_end = state->cursor; nk_textedit_clamp(state );