summaryrefslogtreecommitdiff
path: root/src/gui/textbox.cpp
diff options
context:
space:
mode:
author2lag <96544487+2lag@users.noreply.github.com>2026-08-08 17:33:36 +0200
committer2lag <96544487+2lag@users.noreply.github.com>2026-08-08 17:33:36 +0200
commit2973df584978d9aae6f164ce0204179c8a28b3ae (patch)
treec1b793c8e0d17aeb66c5197bc28acca84b2c98ab /src/gui/textbox.cpp
parente540910745aad31378452eec25aacfb3f346191a (diff)
console, textbox changes, some other shit
Diffstat (limited to 'src/gui/textbox.cpp')
-rw-r--r--src/gui/textbox.cpp1011
1 files changed, 876 insertions, 135 deletions
diff --git a/src/gui/textbox.cpp b/src/gui/textbox.cpp
index c367193..0a9745f 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<I32>( 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<U32>( tb->cursorin, tb->cursorout );
+}
- *endposx = x + tw;
- *endposy = y + th;
+static U32 gui_textbox_selection_end( const GUI_TEXTBOX* tb ) {
+ return max<U32>( 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<U32>( tb->cursorin, length );
+ tb->cursorout = min<U32>( 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<U32>( 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<I32>( 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<I32>( 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<U32>(
+ gui_textbox_selection_start( tb ),
+ length
+ );
+ U32 end = min<U32>(
+ 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<U32>( tb->maxlen, GUI_TEXTBOX_MAX );
+
+ U32 start = min<U32>(
+ gui_textbox_selection_start( tb ),
+ length
+ );
+ U32 end = min<U32>(
+ 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<U32>( 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<U32>( 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<U32>( 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<I32>( 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<I32>( 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<U32>(
+ gui_textbox_selection_start( tb ), length
+ );
+ U32 selection_end = min<U32>(
+ 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<U32>(
+ selection_start, line_start
+ );
+ U32 range_end = min<U32>(
+ 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<I32>( 0, tb->w - 2 ),
+ max<I32>( 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<I32>( 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<I32>( 0,
+ max<I32>( text_w, tb->cursorpxout + 1 ) - content_w
+ );
+
+ tb->scrollpx = m_clamp<I32>(
+ 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<I32>(
+ tb->scrollpy,
+ 0,
+ max<I32>( 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<I32>( 0, tb->w - 2 ),
+ max<I32>( 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<I32>( 1, tb->w - 4 ),
+ max<I32>( 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<U32>( tb->cursorin, length );
+ tb->cursorout = min<U32>( 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 );