diff options
| author | aura <nw@moneybot.cc> | 2026-08-11 19:10:16 +0200 |
|---|---|---|
| committer | aura <nw@moneybot.cc> | 2026-08-11 19:10:16 +0200 |
| commit | 17c07b5f884453130d8be3a6d91d730d80d4f8ae (patch) | |
| tree | 6ec11e5e5f7f0be9ed899196c2897aa76a907de2 /src/gui/textbox.cpp | |
| parent | 1aa17de9188790f049c0edc4281597cb4e535627 (diff) | |
| parent | 0a9d7ce6a0764a87766ebb23cc1ece4c26c8c678 (diff) | |
Merge remote-tracking branch 'origin/day'
Diffstat (limited to 'src/gui/textbox.cpp')
| -rw-r--r-- | src/gui/textbox.cpp | 1011 |
1 files changed, 876 insertions, 135 deletions
diff --git a/src/gui/textbox.cpp b/src/gui/textbox.cpp index c367193..edf2a30 100644 --- a/src/gui/textbox.cpp +++ b/src/gui/textbox.cpp @@ -1,51 +1,363 @@ #include "base.h" +#include "../render/gl_2d_font.h" #include "../util/input.h" #include <SDL_keycode.h> const U32 TEXTBOX_TITLE_OFFSET = 15; -U8 is_printable_char( U8 key ) { - return key >= 32 && key < 127; +struct GUI_TEXTBOX_KEY_RESULT { + U8 value_changed{}; + U8 cursor_moved{}; +}; + +static I32 gui_textbox_line_height() { + GL_FONT* font = _gui.fonts.jpn12.glfnt; + return font + ? max( 1, (I32)font->char_height ) + : 1; } -void gui_textbox_draw_newline_str( GUI_TEXTBOX* tb, I32 x, I32 y, CLR clr, I32* endposx, I32* endposy ) { - char linebuf[GUI_TEXTBOX_MAX]; - char* start = tb->value; - I32 tw, th; +static U32 gui_textbox_line_start( const char* text, U32 cursor ) { + while( cursor > 0 && text[cursor - 1] != '\n' ) + --cursor; + return cursor; +} - for( char* c = start; !!*c; ++c ) { - if( *c == '\n' ) { - U32 len = (U32)( c - start ); - memcpy( linebuf, start, len ); - linebuf[len] = 0; +static U32 gui_textbox_line_end( const char* text, U32 length, U32 cursor ) { + while( cursor < length && text[cursor] != '\n' ) + ++cursor; + return cursor; +} + +static U8 gui_textbox_has_selection( const GUI_TEXTBOX* tb ) { + return tb->cursorin != tb->cursorout; +} - gui_draw_str( x, y, ALIGN_L, FNT_JPN12, clr, linebuf ); - gui_draw_get_str_bounds( &tw, &th, FNT_JPN12, linebuf ); - y += th + 1; +static U32 gui_textbox_selection_start( const GUI_TEXTBOX* tb ) { + return min( tb->cursorin, tb->cursorout ); +} - *endposx = x + tw; - *endposy = y + th; +static U32 gui_textbox_selection_end( const GUI_TEXTBOX* tb ) { + return max( tb->cursorin, tb->cursorout ); +} - if( *(c+1) == 0 ) - break; +static U8 gui_textbox_shift_down( const GUI_TEXTBOX* tb ) { + return tb->keys[SDLK_LSHIFT & 0xff] + || tb->keys[SDLK_RSHIFT & 0xff]; +} - start = ++c; - } - else if( (*c+1) == 0 ) { - U32 len = (U32)( c - start ); - memcpy( linebuf, start, len ); - linebuf[len] = 0; +static I32 gui_textbox_range_width( const char* text, U32 start, U32 end ) { + GL_FONT* font = _gui.fonts.jpn12.glfnt; + if( !font ) + return 0; - gui_draw_str( x, y, ALIGN_L, FNT_JPN12, clr, linebuf ); - gui_draw_get_str_bounds( &tw, &th, FNT_JPN12, linebuf ); - y += th + 1; - *endposx = x + tw; - *endposy = y + th; + F32 width = 0.f; + for( U32 idx = start; idx < end; ++idx ) { + U8 c = (U8)text[idx]; + if( !c || c == '\n' ) break; - } + width += font->glyphs[c].advance_x; + } + + return (I32)( width + 0.5f ); +} + +static I32 gui_textbox_cursor_row( const char* text, U32 cursor ) { + I32 row = 0; + for( U32 idx = 0; idx < cursor; ++idx ) { + if( text[idx] == '\n' ) + ++row; + } + return row; +} + +static I32 gui_textbox_line_count( const char* text, U32 length ) { + I32 count = 1; + for( U32 idx = 0; idx < length; ++idx ) { + if( text[idx] == '\n' ) + ++count; + } + return count; +} + +static U32 gui_textbox_line_start_from_row( + const char* text, + U32 length, + I32 wanted_row +) { + U32 start = 0; + for( I32 row = 0; row < wanted_row; ++row ) { + U32 end = gui_textbox_line_end( text, length, start ); + if( end >= length ) + return length; + start = end + 1; + } + return start; +} + +static U32 gui_textbox_nearest_cursor_x( + const char* text, + U32 line_start, U32 line_end, + I32 wanted_x +) { + GL_FONT* font = _gui.fonts.jpn12.glfnt; + if( !font || wanted_x <= 0 ) + return line_start; + + F32 x = 0.f; + + for( U32 idx = line_start; idx < line_end; ++idx ) { + F32 advance = font->glyphs[(U8)text[idx]].advance_x; + if( (F32)wanted_x < x + advance * 0.5f ) + return idx; + x += advance; + } + return line_end; +} + +static void gui_textbox_update_cursor_metrics( GUI_TEXTBOX* tb ) { + U32 length = (U32)strlen( tb->value ); + + tb->cursorin = min( tb->cursorin, length ); + tb->cursorout = min( tb->cursorout, length ); + + U32 line_start_in = gui_textbox_line_start( tb->value, tb->cursorin ); + U32 line_start_out = gui_textbox_line_start( tb->value, tb->cursorout ); + + tb->cursorpxin = gui_textbox_range_width( + tb->value, + line_start_in, + tb->cursorin + ); + tb->cursorpxout = gui_textbox_range_width( + tb->value, + line_start_out, + tb->cursorout + ); + + tb->cursorpy = gui_textbox_cursor_row( + tb->value, + tb->cursorout + ) * gui_textbox_line_height(); +} + +static void gui_textbox_set_cursor( + GUI_TEXTBOX* tb, + U32 cursor, + U8 extend_selection = 0, + U8 preserve_vertical_goal = 0 +) { + U32 length = (U32)strlen( tb->value ); + tb->cursorout = min( cursor, length ); + + if( !extend_selection ) + tb->cursorin = tb->cursorout; + + gui_textbox_update_cursor_metrics( tb ); + + if( !preserve_vertical_goal ) + tb->cursor_goal_valid = 0; +} + +static U32 gui_textbox_cursor_from_point( + GUI_TEXTBOX* tb, + I32 local_x, I32 local_y +) { + U32 length = (U32)strlen( tb->value ); + I32 line_height = gui_textbox_line_height(); + I32 line_count = gui_textbox_line_count( tb->value, length ); + I32 row = local_y / line_height; + row = m_clamp( row, 0, line_count - 1 ); + U32 line_start = gui_textbox_line_start_from_row( + tb->value, length, row + ); + U32 line_end = gui_textbox_line_end( + tb->value, length, line_start + ); + return gui_textbox_nearest_cursor_x( + tb->value, + line_start, + line_end, + max( 0, local_x ) + ); +} + +static U8 gui_textbox_move_vertical( + GUI_TEXTBOX* tb, + I32 direction, + U8 extend_selection = 0 +) { + if( !direction ) + return 0; + + U32 length = (U32)strlen( tb->value ); + gui_textbox_update_cursor_metrics( tb ); + + if( !tb->cursor_goal_valid ) { + tb->cursor_goal_px = tb->cursorpxout; + tb->cursor_goal_valid = 1; + } + + U32 current_start = gui_textbox_line_start( tb->value, tb->cursorout ); + U32 target_start = 0; + U32 target_end = 0; + + if( direction < 0 ) { + if( current_start == 0 ) + return 0; + + target_end = current_start - 1; + target_start = gui_textbox_line_start( tb->value, target_end ); + } + else { + U32 current_end = gui_textbox_line_end( + tb->value, + length, + tb->cursorout + ); + if( current_end >= length ) + return 0; + + target_start = current_end + 1; + target_end = gui_textbox_line_end( tb->value, length, target_start ); + } + + U32 target = gui_textbox_nearest_cursor_x( + tb->value, + target_start, + target_end, + tb->cursor_goal_px + ); + gui_textbox_set_cursor( tb, target, extend_selection, 1 ); + return 1; +} + +static U8 gui_textbox_delete_selection( GUI_TEXTBOX* tb ) { + if( !gui_textbox_has_selection( tb ) ) + return 0; + + U32 length = (U32)strlen( tb->value ); + U32 start = min( + gui_textbox_selection_start( tb ), + length + ); + U32 end = min( + gui_textbox_selection_end( tb ), + length + ); + + if( start >= end ) { + gui_textbox_set_cursor( tb, start ); + return 0; } + + if( tb->pre_cb ) + tb->pre_cb( tb ); + + memmove( + tb->value + start, + tb->value + end, + length - end + 1 + ); + + gui_textbox_set_cursor( tb, start ); + return 1; +} + +static U32 gui_textbox_insert_bytes( + GUI_TEXTBOX* tb, + const char* source, + U32 count +) { + if( !tb || !source || !count || !tb->maxlen ) + return 0; + + U32 length = (U32)strlen( tb->value ); + U32 limit = min( tb->maxlen, GUI_TEXTBOX_MAX ); + + U32 start = min( + gui_textbox_selection_start( tb ), + length + ); + U32 end = min( + gui_textbox_selection_end( tb ), + length + ); + + U32 selected = end - start; + U32 remaining = length - selected; + + if( limit <= 1 || remaining >= limit - 1 ) + return 0; + + U32 available = limit - 1 - remaining; + count = min( count, available ); + + if( !count ) + return 0; + + if( tb->pre_cb ) + tb->pre_cb( tb ); + + memmove( + tb->value + start + count, + tb->value + end, + length - end + 1 + ); + + memcpy( + tb->value + start, + source, count + ); + + gui_textbox_set_cursor( tb, start + count ); + return count; +} + +static U8 gui_textbox_backspace( GUI_TEXTBOX* tb ) { + if( gui_textbox_delete_selection( tb ) ) + return 1; + + U32 length = (U32)strlen( tb->value ); + U32 cursor = min( tb->cursorout, length ); + if( !cursor ) + return 0; + + if( tb->pre_cb ) + tb->pre_cb( tb ); + + memmove( + tb->value + cursor - 1, + tb->value + cursor, + length - cursor + 1 + ); + + gui_textbox_set_cursor( tb, cursor - 1 ); + return 1; +} + +static U8 gui_textbox_delete_forward( GUI_TEXTBOX* tb ) { + if( gui_textbox_delete_selection( tb ) ) + return 1; + + U32 length = (U32)strlen( tb->value ); + U32 cursor = min( tb->cursorout, length ); + if( cursor >= length ) + return 0; + + if( tb->pre_cb ) + tb->pre_cb( tb ); + + memmove( + tb->value + cursor, + tb->value + cursor + 1, + length - cursor + ); + + gui_textbox_set_cursor( tb, cursor ); + return 1; } void gui_textbox_draw_blinker( GUI_TEXTBOX* tb, I32 x, I32 y, CLR clr ) { @@ -56,154 +368,574 @@ void gui_textbox_draw_blinker( GUI_TEXTBOX* tb, I32 x, I32 y, CLR clr ) { tb->blinktime = gui_time(); } - if( tb->blink ) - gui_draw_str( x, y, ALIGN_L, FNT_JPN12, clr, "|" ); + if( tb->blink ) { + I32 caret_h = max( 1, + gui_textbox_line_height() - 2 + ); + gui_draw_frect( + x, y + 1, + 1, caret_h, + clr + ); + } } -void gui_textbox_draw_fn( void* ptr ) { - GUI_TEXTBOX* tb = (GUI_TEXTBOX*)ptr; +static I32 gui_textbox_newline_width() { + GL_FONT* font = _gui.fonts.jpn12.glfnt; - I32 x = gui_relx( tb ); - I32 y = gui_rely( tb ); + if( !font ) + return 1; - gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, tb->name ); - y += TEXTBOX_TITLE_OFFSET; + return max( 1, + (I32)( font->glyphs[(U8)' '].advance_x + 0.5f ) + ); +} - CLR col = gui_is_fg_window( tb )? ui_clr.border : ui_clr.border_inactive; +static void gui_textbox_draw_selection( + GUI_TEXTBOX* tb, + I32 draw_x, I32 draw_y, + I32 line_height, + CLR color +) { + if( !gui_textbox_has_selection( tb ) ) + return; - gui_draw_frect( x, y, tb->w, tb->h, col ); - gui_draw_frect( x+1, y+1, tb->w-2, tb->h-2, ui_clr.bg_sec ); + U32 length = (U32)strlen( tb->value ); + U32 selection_start = min( + gui_textbox_selection_start( tb ), length + ); + U32 selection_end = min( + gui_textbox_selection_end( tb ), length + ); + + U32 line_start = gui_textbox_line_start( + tb->value, selection_start + ); + + I32 row = gui_textbox_cursor_row( tb->value, line_start ); + + I32 newline_width = gui_textbox_newline_width(); + + for( ;; ) { + U32 line_end = gui_textbox_line_end( + tb->value, length, line_start + ); + + U32 range_start = max( + selection_start, line_start + ); + U32 range_end = min( + selection_end, line_end + ); + + I32 x0 = gui_textbox_range_width( + tb->value, line_start, range_start + ); + I32 x1 = gui_textbox_range_width( + tb->value, line_start, range_end + ); + I32 width = x1 - x0; + + if( line_end < length && selection_end > line_end ) + width += newline_width; + + if( width > 0 ) { + gui_draw_frect( + draw_x + x0, + draw_y + row * line_height, + width, line_height, color + ); + } - CLR clr = tb->active? ui_clr.txt : ui_clr.txt_inactive; - I32 ypos = y + 3; - I32 xpos = x + 2; + if( line_end >= length || line_end >= selection_end ) + break; - I32 endposx, endposy; + line_start = line_end + 1; + ++row; + } +} - if( tb->allow_newl ) { - gui_textbox_draw_newline_str( tb, xpos, ypos, clr, &endposx, &endposy ); - } else { - gui_draw_str( xpos, ypos, ALIGN_L, FNT_JPN12, clr, tb->value ); +static void gui_textbox_draw_frame( + GUI_TEXTBOX* tb, + I32 x, I32 y +) { + CLR border = gui_is_fg_window( tb ) + ? ui_clr.border + : ui_clr.border_inactive; + + gui_draw_frect( x, y, tb->w, tb->h, border ); + gui_draw_frect( + x + 1, y + 1, + max( 0, tb->w - 2 ), + max( 0, tb->h - 2 ), + ui_clr.bg_sec + ); +} - I32 tw, th; - gui_draw_get_str_bounds( &tw, &th, FNT_JPN12, tb->value ); - endposx = xpos + tw; - endposy = ypos; +static void gui_textbox_update_scroll( + GUI_TEXTBOX* tb, + I32 content_w, + I32 content_h, + I32 line_height +) { + I32 text_h = line_height; + I32 text_w = 0; + + gui_draw_get_str_bounds( + &text_w, &text_h, FNT_JPN12, + "%s", tb->value + ); + + text_h = max( text_h, + tb->cursorpy + line_height + ); + + if( tb->cursorpxout < tb->scrollpx ) + tb->scrollpx = tb->cursorpxout; + else if( tb->cursorpxout + 1 > tb->scrollpx + content_w ) + tb->scrollpx = tb->cursorpxout + 1 - content_w; + + I32 max_scroll_x = max( 0, + max( text_w, tb->cursorpxout + 1 ) - content_w + ); + + tb->scrollpx = m_clamp( + tb->scrollpx, 0, max_scroll_x + ); + + if( !tb->allow_newl ) { + tb->cursorpy = 0; + tb->scrollpy = 0; + return; } - gui_textbox_draw_blinker( tb, endposx, endposy, clr ); + if( tb->cursorpy < tb->scrollpy ) + tb->scrollpy = tb->cursorpy; + else if( tb->cursorpy + line_height > tb->scrollpy + content_h ) + tb->scrollpy = tb->cursorpy + line_height - content_h; + + tb->scrollpy = m_clamp( + tb->scrollpy, + 0, + max( 0, text_h - content_h ) + ); +} + +static void gui_textbox_draw_content( + GUI_TEXTBOX* tb, + I32 x, I32 y, + I32 line_height, + CLR text_color +) { + I32 draw_x = x + 2 - tb->scrollpx; + I32 draw_y = y + 3 - tb->scrollpy; + + gui_draw_push_clip( + x + 1, y + 1, + max( 0, tb->w - 2 ), + max( 0, tb->h - 2 ) + ); + + gui_textbox_draw_selection( tb, + draw_x, draw_y, + line_height, + ui_clr.selection + ); + + gui_draw_str( + draw_x, draw_y, ALIGN_L, FNT_JPN12, + text_color, "%s", tb->value + ); + + gui_textbox_draw_blinker( tb, + draw_x + tb->cursorpxout, + draw_y + tb->cursorpy, + text_color + ); + + gui_draw_pop_clip(); +} + +void gui_textbox_draw_fn( void* ptr ) { + GUI_TEXTBOX* tb = (GUI_TEXTBOX*)ptr; + I32 x = gui_relx( tb ); + I32 title_y = gui_rely( tb ); + + gui_draw_str( + x, title_y, ALIGN_L, + FNT_JPN12, ui_clr.txt, + "%s", tb->name + ); + + I32 y = title_y + TEXTBOX_TITLE_OFFSET; + I32 line_height = gui_textbox_line_height(); + + gui_textbox_draw_frame( tb, x, y ); + gui_textbox_update_cursor_metrics( tb ); + gui_textbox_update_scroll( tb, + max( 1, tb->w - 4 ), + max( 1, tb->h - 4 ), + line_height + ); + + CLR text_color = tb->active + ? ui_clr.txt + : ui_clr.txt_inactive; + + gui_textbox_draw_content( tb, x, y, line_height, text_color ); } void gui_textbox_handle_mouse( GUI_TEXTBOX* tb ) { I32 x = gui_relx( tb ); I32 y = gui_rely( tb ) + TEXTBOX_TITLE_OFFSET; - I32 w = tb->w; - I32 h = tb->h; - U8 m1 = gui_mbutton_down( 0 ); - I32 mx, my; + U8 m1 = gui_mbutton_down( GUI_MBTNLEFT ); + I32 mx = 0; + I32 my = 0; gui_cursor_pos( &mx, &my ); - U8 inbounds = mx >= x && mx <= x + w && my >= y && my <= y + h; + U8 inbounds = mx >= x + && mx < x + tb->w + && my >= y + && my < y + tb->h; - if( m1 ) { - if( inbounds ) - tb->m1held = 1; - else - tb->active = 0; - } else { - if( inbounds && tb->m1held ) { - tb->active = 1; - tb->blink = 1; - tb->blinktime = gui_time(); - } + if( !m1 ) { tb->m1held = 0; + return; + } + + if( !tb->m1held ) { + if( !inbounds ) { + tb->m1held = 2; + tb->active = 0; + return; + } + + I32 local_x = mx - ( x + 2 ) + tb->scrollpx; + I32 local_y = my - ( y + 3 ) + tb->scrollpy; + + U32 cursor = gui_textbox_cursor_from_point( + tb, local_x, local_y + ); + + gui_textbox_set_cursor( tb, cursor ); + tb->m1held = 1; + tb->active = 1; + } + else if( tb->m1held == 1 ) { + I32 local_x = mx - ( x + 2 ) + tb->scrollpx; + I32 local_y = my - ( y + 3 ) + tb->scrollpy; + + U32 cursor = gui_textbox_cursor_from_point( + tb, local_x, local_y + ); + + gui_textbox_set_cursor( tb, cursor, 1 ); + } + + if( tb->m1held == 1 ) { + tb->blink = 1; + tb->blinktime = gui_time(); + } +} + +static U8 gui_textbox_move_horizontal_key( + GUI_TEXTBOX* tb, + I32 direction, + U8 extend_selection +) { + if( !extend_selection && gui_textbox_has_selection( tb ) ) { + U32 target = direction < 0 + ? gui_textbox_selection_start( tb ) + : gui_textbox_selection_end( tb ); + gui_textbox_set_cursor( tb, target ); + return 1; + } + + U32 length = (U32)strlen( tb->value ); + + if( direction < 0 && tb->cursorout > 0 ) { + gui_textbox_set_cursor( tb, + tb->cursorout - 1, + extend_selection + ); + return 1; + } + + if( direction > 0 && tb->cursorout < length ) { + gui_textbox_set_cursor( tb, + tb->cursorout + 1, + extend_selection + ); + return 1; + } + return 0; +} + +static U8 gui_textbox_move_vertical_key( + GUI_TEXTBOX* tb, + I32 direction, + U8 extend_selection +) { + U8 had_selection = gui_textbox_has_selection( tb ); + U8 moved = gui_textbox_move_vertical( tb, + direction, + extend_selection + ); + + if( !extend_selection && had_selection && !moved ) { + gui_textbox_set_cursor( tb, tb->cursorout ); + return 1; + } + return moved; +} + +static U8 gui_textbox_move_line_edge( + GUI_TEXTBOX* tb, + U8 move_to_end, + U8 extend_selection +) { + U32 length = (U32)strlen( tb->value ); + U32 target = move_to_end + ? gui_textbox_line_end( tb->value, length, tb->cursorout ) + : gui_textbox_line_start( tb->value, tb->cursorout ); + U8 moved = target != tb->cursorout + || ( !extend_selection + && gui_textbox_has_selection( tb ) + ); + gui_textbox_set_cursor( tb, + target, extend_selection + ); + return moved; +} + +static U8 gui_textbox_apply_edit_key( + GUI_TEXTBOX* tb, + U32 key, + GUI_TEXTBOX_KEY_RESULT* result +) { + constexpr U32 kp_enter = SDLK_KP_ENTER & 0xff; + constexpr U32 del = SDLK_DELETE & 0xff; + + if( key == '\b' ) { + result->value_changed = gui_textbox_backspace( tb ); + result->cursor_moved = result->value_changed; + return 1; } + + if( key == del ) { + result->value_changed = gui_textbox_delete_forward( tb ); + return 1; + } + + if( key == '\r' || key == kp_enter ) { + if( tb->allow_newl ) { + result->value_changed = gui_textbox_insert_bytes( tb, "\n", 1 ) == 1; + result->cursor_moved = result->value_changed; + } + else + tb->active = 0; + return 1; + } + + if( key == '\x1b' ) { + tb->active = 0; + return 1; + } + return 0; +} + +static U8 gui_textbox_apply_move_key( + GUI_TEXTBOX* tb, + U32 key, + U8 extend_selection, + GUI_TEXTBOX_KEY_RESULT* result +) { + constexpr U32 right = SDLK_RIGHT & 0xff; + constexpr U32 down = SDLK_DOWN & 0xff; + constexpr U32 home = SDLK_HOME & 0xff; + constexpr U32 left = SDLK_LEFT & 0xff; + constexpr U32 end = SDLK_END & 0xff; + constexpr U32 up = SDLK_UP & 0xff; + + if( key == left || key == right ) { + result->cursor_moved = gui_textbox_move_horizontal_key( + tb, + key == left ? -1 : 1, + extend_selection + ); + return 1; + } + + if( key == up || key == down ) { + if( !tb->allow_newl ) + return 0; + result->cursor_moved = gui_textbox_move_vertical_key( + tb, + key == up ? -1 : 1, + extend_selection + ); + return 1; + } + + if( key == home || key == end ) { + result->cursor_moved = gui_textbox_move_line_edge( + tb, + key == end, + extend_selection + ); + return 1; + } + return 0; +} + +static void gui_textbox_apply_key_result( + GUI_TEXTBOX* tb, + const GUI_TEXTBOX_KEY_RESULT& result, + F32 now +) { + if( result.value_changed && tb->cb ) + tb->cb( tb ); + + if( result.value_changed || result.cursor_moved ) { + tb->blink = 1; + tb->blinktime = now; + } +} + +static U8 gui_textbox_key_repeatable( + const GUI_TEXTBOX* tb, + U32 key +) { + constexpr U32 right = SDLK_RIGHT & 0xff; + constexpr U32 down = SDLK_DOWN & 0xff; + constexpr U32 left = SDLK_LEFT & 0xff; + constexpr U32 del = SDLK_DELETE & 0xff; + constexpr U32 up = SDLK_UP & 0xff; + + return key == '\b' + || key == del + || key == left + || key == right + || ( tb->allow_newl && ( key == up || key == down ) ); } void gui_textbox_loop_keys( GUI_TEXTBOX* tb ) { - U32 len = strlen( tb->value ); - - for( U32 i = 0; i < 0xff; ++i ) { - U8 key = tb->keys[i]; - U8 prev = tb->prevkeys[i]; - - if( !key && prev && i == tb->heldkey ) - tb->heldkey = 0; - else if( key && !prev ) { - if( is_printable_char( i ) && len < tb->maxlen - 1 ) { - if( tb->keys[SDLK_LSHIFT & 0xff] || tb->keys[SDLK_RSHIFT & 0xff] ) - tb->value[len] = (char)i + ('a' - 'A'); - else - tb->value[len] = (char)i; - tb->value[++len] = 0; - } - else if( i == '\b' ) { - if( len > 0 ) { - tb->value[--len] = 0; - } - } - else if( i == '\r' ) { - if( tb->allow_newl ) { - tb->value[len] = '\n'; - tb->value[++len] = 0; - } - else { - tb->active = 0; - } - } else { - if( i == '\x1b' ) - tb->active = 0; - if( tb->cb ) - tb->cb( tb ); - continue; - } - - tb->heldkey = i; - tb->heldtime = gui_time(); - if( tb->cb ) - tb->cb( tb ); + U32 length = strlen( tb->value ); + tb->cursorin = min( tb->cursorin, length ); + tb->cursorout = min( tb->cursorout, length ); + U8 extend_selection = gui_textbox_shift_down( tb ); + + for( U32 idx = 0; idx < 0x100; ++idx ) { + U8 prev = tb->prevkeys[idx]; + U8 key = tb->keys[idx]; + + if( !key ) { + if( prev && idx == tb->heldkey ) + tb->heldkey = 0; + continue; + } + + if( prev ) + continue; + + GUI_TEXTBOX_KEY_RESULT result{}; + U8 handled = gui_textbox_apply_edit_key( + tb, + idx, + &result + ); + if( !handled ) { + handled = gui_textbox_apply_move_key( + tb, idx, + extend_selection, + &result + ); } + if( !handled ) + continue; + + F32 now = gui_time(); + gui_textbox_apply_key_result( tb, result, now ); + + tb->heldtime = now; + tb->repeattime = now; + tb->heldkey = (U8)idx; } } void gui_textbox_handle_repeat( GUI_TEXTBOX* tb ) { - if( !tb->heldkey ) + if( !gui_textbox_key_repeatable( tb, tb->heldkey ) ) return; - F32 delta = gui_time() - tb->heldtime; - if( delta <= 0.75f ) + F32 now = gui_time(); + if( now - tb->heldtime <= 0.75f + || now - tb->repeattime <= 0.05f ) return; - if( gui_time() - tb->repeattime > 0.05f ) { - U32 len = strlen( tb->value ); - if( is_printable_char( tb->heldkey ) && len < (tb->maxlen - 1) ) { - if( tb->pre_cb ) tb->pre_cb( tb ); + GUI_TEXTBOX_KEY_RESULT result{}; + U8 extend_selection = gui_textbox_shift_down( tb ); + U8 handled = gui_textbox_apply_edit_key( + tb, + tb->heldkey, + &result + ); + if( !handled ) { + handled = gui_textbox_apply_move_key( + tb, tb->heldkey, + extend_selection, + &result + ); + } - if( tb->keys[SDLK_LSHIFT & 0xff] || tb->keys[SDLK_RSHIFT & 0xff] ) - tb->value[len] = tb->heldkey + ('a' - 'A'); - else - tb->value[len] = tb->heldkey; - tb->value[++len] = 0; + if( handled ) + gui_textbox_apply_key_result( tb, result, now ); - if( tb->cb ) tb->cb( tb ); - } - else if( tb->heldkey == '\b' && len > 0 ) { - if( tb->pre_cb ) tb->pre_cb( tb ); - tb->value[--len] = 0; - if( tb->cb ) tb->cb( tb ); + tb->repeattime = now; +} + +void gui_textbox_append_input_text( GUI_TEXTBOX* tb ) { + if( !tb->active || !input.textlen || !tb->maxlen ) + return; + + if( !tb->allow_newl ) { + U32 filtered = 0; + for( U32 idx = 0; idx < input.textlen; ++idx ) { + char c = input.text[idx]; + + if( c != '\n' && c != '\r' ) + input.text[filtered++] = c; } + input.textlen = filtered; + input.text[filtered] = 0; + } - tb->repeattime = gui_time(); + U32 inserted = gui_textbox_insert_bytes( + tb, + input.text, + input.textlen + ); + + if( inserted ) { + tb->blink = 1; + tb->blinktime = gui_time(); + + if( tb->cb ) + tb->cb( tb ); } + + input.textlen = 0; + input.text[0] = 0; } void gui_textbox_handle_keyboard( GUI_TEXTBOX* tb ) { if( !tb->active ) return; - memcpy( tb->prevkeys, tb->keys, 0xff ); - memcpy( tb->keys, input.keys, 0xff ); + gui_textbox_append_input_text( tb ); + + memcpy( tb->prevkeys, tb->keys, 0x100 ); + memcpy( tb->keys, input.keys, 0x100 ); gui_textbox_loop_keys( tb ); gui_textbox_handle_repeat( tb ); @@ -217,7 +949,7 @@ void gui_textbox_input_fn( void* ptr ) { } GUI_TEXTBOX* gui_textbox( I32 x, I32 y, I32 w, I32 h, const char* title, U32 maxlen, U8 allow_newl ) { - GUI_TEXTBOX* tb = new GUI_TEXTBOX; + GUI_TEXTBOX* tb = new GUI_TEXTBOX{}; tb->x = x; tb->y = y; @@ -234,13 +966,22 @@ GUI_TEXTBOX* gui_textbox( I32 x, I32 y, I32 w, I32 h, const char* title, U32 max tb->active = 0; tb->m1held = 0; tb->heldkey = 0; + tb->cursorpy = 0; tb->heldtime = 0; tb->repeattime = 0; tb->maxlen = maxlen; + tb->cursor_goal_px = 0; + tb->cursor_goal_valid = 0; tb->allow_newl = allow_newl; + tb->scrollpx = tb->scrollpy = 0; + tb->cursorin = tb->cursorout = 0; + tb->cursorpxin = tb->cursorpxout = 0; memset( tb->value, 0, GUI_TEXTBOX_MAX ); - memset( tb->keys, 0, 0xff ); - memset( tb->prevkeys, 0, 0xff ); + memset( tb->keys, 0, 0x100 ); + memset( tb->prevkeys, 0, 0x100 ); + + tb->blink = 1; + tb->blinktime = gui_time(); gui_get_view()->children.push( tb ); |
