diff options
Diffstat (limited to 'src/gui/console.cpp')
| -rw-r--r-- | src/gui/console.cpp | 1779 |
1 files changed, 1779 insertions, 0 deletions
diff --git a/src/gui/console.cpp b/src/gui/console.cpp new file mode 100644 index 0000000..cf6cc4d --- /dev/null +++ b/src/gui/console.cpp @@ -0,0 +1,1779 @@ +#include "SDL_clipboard.h" +#include "SDL_keyboard.h" +#include "SDL_keycode.h" +#include "SDL_mouse.h" + +#include "console.h" + +#include "../render/gl_2d_font.h" +#include "../util/console.h" +#include "../util/math.h" +#include "../game.h" +#include "base.h" + +#include <string.h> + +struct CONSOLE_INPUT_SEGMENT { + U32 length{}; + U32 start{}; + U8 valid{}; +}; + +struct CONSOLE_WRAP_RANGE { + U32 next; + U32 end; +}; + +extern GAME_DATA* game; +extern VAR_LIST* varl; + +constexpr I32 GUI_CONSOLE_HISTORY_MENU_ROWS = 4; +constexpr I32 GUI_CONSOLE_MINIMUM_HEIGHT = 128; +constexpr I32 GUI_CONSOLE_MINIMUM_WIDTH = 256; +constexpr I32 GUI_CONSOLE_INPUT_HEIGHT = 20; +constexpr I32 GUI_TEXTBOX_TITLE_OFFSET = 15; +constexpr I32 GUI_CONSOLE_BORDER_WIDTH = 1; +constexpr I32 GUI_CONSOLE_PADDING = 4; +constexpr I32 GUI_TITLE_HEIGHT = 26; +constexpr I32 GUI_BUTTON_SIZE = 16; +constexpr I32 GUI_GRAB_SIZE = 8; + +inline I32 get_input_y( I32 wnd_h ) { + return wnd_h + - ( GUI_CONSOLE_BORDER_WIDTH * 2 ) + - GUI_CONSOLE_PADDING + - GUI_TEXTBOX_TITLE_OFFSET + - GUI_CONSOLE_INPUT_HEIGHT; +} + +inline I32 get_input_w( I32 wnd_w ) { + return wnd_w + - ( GUI_CONSOLE_PADDING * 2 ) + - ( GUI_CONSOLE_BORDER_WIDTH * 2 ); +} + +inline I32 get_close_x( I32 wnd_w ) { + return wnd_w + - GUI_BUTTON_SIZE + - GUI_CONSOLE_PADDING + - ( GUI_CONSOLE_BORDER_WIDTH * 2 ); +} + +inline VEC2 get_console_output_pos( GUI_CONSOLEWINDOW* wnd ) { + I32 x = gui_relx( wnd ) + GUI_CONSOLE_BORDER_WIDTH + GUI_CONSOLE_PADDING; + I32 y = gui_rely( wnd ) + GUI_TITLE_HEIGHT + GUI_CONSOLE_PADDING; + return { x, y }; +} + +inline VEC2 get_console_output_sz( GUI_CONSOLEWINDOW* wnd ) { + I32 bottom = gui_rely( wnd->input ) + GUI_TEXTBOX_TITLE_OFFSET - GUI_CONSOLE_PADDING; + I32 width = wnd->w - ( GUI_CONSOLE_BORDER_WIDTH + GUI_CONSOLE_PADDING ) * 2; + I32 top = gui_rely( wnd ) + GUI_TITLE_HEIGHT + GUI_CONSOLE_PADDING; + return { + (F32)max( 0, width ), + (F32)max( 0, bottom - top ) + }; +} + +inline U8 constrain_console( GUI_CONSOLEWINDOW* wnd, I32 max_x, I32 max_y ) { + max_x = max( 1, max_x ); + max_y = max( 1, max_y ); + + I32 oldx = wnd->x; + I32 oldy = wnd->y; + I32 oldw = wnd->w; + I32 oldh = wnd->h; + + I32 minh = min( GUI_CONSOLE_MINIMUM_HEIGHT, max_y ); + I32 minw = min( GUI_CONSOLE_MINIMUM_WIDTH, max_x ); + + wnd->w = m_clamp( wnd->w, minw, max_x ); + wnd->h = m_clamp( wnd->h, minh, max_y ); + wnd->x = m_clamp( wnd->x, 0, max_x - wnd->w ); + wnd->y = m_clamp( wnd->y, 0, max_y - wnd->h ); + + return oldx != wnd->x + || oldy != wnd->y + || oldw != wnd->w + || oldh != wnd->h; +} + +inline GUI_VIEW* get_console_view( GUI_CONSOLEWINDOW* wnd ) { + if( !wnd->children.size ) + return 0; + return (GUI_VIEW*)wnd->children.at( 0 ); +} + +inline void layout_console_window( GUI_CONSOLEWINDOW* wnd ) { + GUI_VIEW* view = get_console_view( wnd ); + if( !view ) + return; + + view->w = max( 1, wnd->w - GUI_CONSOLE_BORDER_WIDTH * 2 ); + view->h = max( 1, wnd->h - GUI_CONSOLE_BORDER_WIDTH * 2 ); + + if( wnd->input ) { + wnd->input->y = get_input_y( wnd->h ); + wnd->input->w = get_input_w( wnd->w ); + } + + if( wnd->close ) + wnd->close->x = get_close_x( wnd->w ); + + if( wnd->title ) + wnd->title->w = view->w; +} + +inline void store_console_drag_origin( GUI_CONSOLEWINDOW* wnd ) { + wnd->drag_start_mouse = input.mouse.pos; + wnd->drag_start_pos = VEC2( wnd->x, wnd->y ); + wnd->drag_start_sz = VEC2( + wnd->x + wnd->w, + wnd->y + wnd->h + ); +} + +inline RESIZE_EDGES get_console_active_edges( + GUI_CONSOLEWINDOW* wnd, + VEC2 mouse, + I32 max_x, + I32 max_y +) { + F32 radius = GUI_GRAB_SIZE * 0.5f; + + if( mouse.x < 0 || mouse.x >= max_x + || mouse.y < 0 || mouse.y >= max_y + ) return RESIZE_NONE; + + F32 top = (F32)wnd->y; + F32 left = (F32)wnd->x; + F32 right = (F32)( wnd->x + wnd->w ); + F32 bottom = (F32)( wnd->y + wnd->h ); + + U8 within_vert = mouse.y >= top + GUI_TITLE_HEIGHT + && mouse.y < bottom + radius; + U8 within_horz = mouse.x >= left - radius + && mouse.x < right + radius; + U8 near_left = within_vert + && mouse.x >= left - radius + && mouse.x < left + radius; + U8 near_right = within_vert + && mouse.x >= right - radius + && mouse.x < right + radius; + U8 near_bottom = within_horz + && mouse.y >= bottom - radius + && mouse.y < bottom + radius; + + if( near_left && near_right ) { + F32 d_left = fabsf( mouse.x - left ); + F32 d_right = fabsf( mouse.x - right ); + + if( d_left <= d_right ) + near_right = 0; + else + near_left = 0; + } + + U8 mask = RESIZE_NONE; + if( near_left ) + mask |= RESIZE_LEFT; + if( near_right ) + mask |= RESIZE_RIGHT; + if( near_bottom ) + mask |= RESIZE_BOTTOM; + return (RESIZE_EDGES)mask; +} + +inline U8 is_over_console_input( GUI_CONSOLEWINDOW* wnd, VEC2 mouse ) { + if( !wnd->input ) + return 0; + I32 x = gui_relx( wnd->input ); + I32 y = gui_rely( wnd->input ) + GUI_TEXTBOX_TITLE_OFFSET; + + return mouse.x >= x + && mouse.x < x + wnd->input->w + && mouse.y >= y + && mouse.y < y + wnd->input->h; +} + +inline SDL_SystemCursor get_console_cursor( RESIZE_EDGES edges, U8 over_input ) { + if( ( edges & RESIZE_BOTTOM ) && ( edges & RESIZE_LEFT ) ) + return SDL_SYSTEM_CURSOR_SIZENESW; + if( ( edges & RESIZE_BOTTOM ) && ( edges & RESIZE_RIGHT ) ) + return SDL_SYSTEM_CURSOR_SIZENWSE; + if( edges & RESIZE_BOTTOM ) + return SDL_SYSTEM_CURSOR_SIZENS; + if( edges & ( RESIZE_LEFT | RESIZE_RIGHT ) ) + return SDL_SYSTEM_CURSOR_SIZEWE; + if( over_input ) + return SDL_SYSTEM_CURSOR_IBEAM; + return SDL_SYSTEM_CURSOR_ARROW; +} + +inline void set_console_cursor( RESIZE_EDGES edges, U8 over_input = 0 ) { + SDL_SystemCursor type = get_console_cursor( edges, over_input ); + SDL_Cursor* cursor = game->assets.cursors[type]; + if( cursor && SDL_GetCursor() != cursor ) + SDL_SetCursor( cursor ); +} + +inline void console_resize_begin( GUI_CONSOLEWINDOW* wnd, RESIZE_EDGES edges ) { + wnd->active_edges = edges; + wnd->locked = 1; + + store_console_drag_origin( wnd ); + SDL_CaptureMouse( SDL_TRUE ); +} + +inline void console_resize_end( GUI_CONSOLEWINDOW* wnd ) { + if( wnd->active_edges != RESIZE_NONE ) + SDL_CaptureMouse( SDL_FALSE ); + wnd->active_edges = RESIZE_NONE; + wnd->locked = 0; +} + +inline void resize_console_window( GUI_CONSOLEWINDOW* wnd, I32 max_x, I32 max_y ) { + F32 bottom = wnd->drag_start_sz.y; + F32 right = wnd->drag_start_sz.x; + F32 left = wnd->drag_start_pos.x; + F32 top = wnd->drag_start_pos.y; + + F32 dm_x = input.mouse.pos.x - wnd->drag_start_mouse.x; + F32 dm_y = input.mouse.pos.y - wnd->drag_start_mouse.y; + + F32 min_h = (F32)min( GUI_CONSOLE_MINIMUM_HEIGHT, max_y ); + F32 min_w = (F32)min( GUI_CONSOLE_MINIMUM_WIDTH, max_x ); + + U8 hit_constraint = 0; + + if( wnd->active_edges & RESIZE_LEFT ) { + F32 req = wnd->drag_start_pos.x + dm_x; + F32 applied = m_clamp( req, 0.0f, wnd->drag_start_sz.x - min_w ); + left = applied; + hit_constraint |= req != applied; + } else if( wnd->active_edges & RESIZE_RIGHT ) { + F32 req = wnd->drag_start_sz.x + dm_x; + F32 applied = m_clamp( req, + wnd->drag_start_pos.x + min_w, + (F32)max_x + ); + right = applied; + hit_constraint |= req != applied; + } + + if( wnd->active_edges & RESIZE_BOTTOM ) { + F32 req = wnd->drag_start_sz.y + dm_y; + F32 applied = m_clamp( req, + wnd->drag_start_pos.y + min_h, + (F32)max_y + ); + bottom = applied; + hit_constraint |= req != applied; + } + + wnd->y = (I32)top; + wnd->x = (I32)left; + wnd->w = max( 1, (I32)( right - left ) ); + wnd->h = max( 1, (I32)( bottom - top ) ); + layout_console_window( wnd ); + if( hit_constraint ) + store_console_drag_origin( wnd ); +} + +inline I32 console_output_line_height() { + I32 height = 0; + I32 width = 0; + + gui_draw_get_str_bounds( + &width, &height, + FNT_JPN12, "Ag" + ); + + return height > 0 + ? height + 1 + : 0; +} + +inline U8 console_output_id_bounds( + const CONSOLE_DATA* state, + U64* first, + U64* last +) { + if( !state ) + return 0; + + U8 has_partial = state->partial_stdout_line.size > 0; + U8 has_complete = state->lines.count > 0; + + if( !has_complete && !has_partial ) + return 0; + + if( first ) { + *first = has_complete + ? state->lines.total_pushed - state->lines.count + : state->lines.total_pushed; + } + + if( last ) { + *last = has_partial + ? state->lines.total_pushed + : state->lines.total_pushed - 1; + } + + return 1; +} + +inline U8 console_validate_output_selection( + GUI_CONSOLEWINDOW* wnd, + const CONSOLE_DATA* state +) { + if( !wnd || !wnd->output_selection_active ) + return 0; + + U64 first = 0; + U64 last = 0; + + if( !console_output_id_bounds( state, &first, &last ) ) { + wnd->output_selection_active = 0; + wnd->output_selecting = 0; + return 0; + } + + U64 selection_start = min( + wnd->output_selection_anchor, + wnd->output_selection_cursor + ); + + U64 selection_end = max( + wnd->output_selection_anchor, + wnd->output_selection_cursor + ); + + if( selection_end < first || selection_start > last ) { + wnd->output_selection_active = 0; + wnd->output_selecting = 0; + return 0; + } + + wnd->output_selection_anchor = max( first, + min( wnd->output_selection_anchor, last ) + ); + wnd->output_selection_cursor = max( first, + min( wnd->output_selection_cursor, last ) + ); + return 1; +} + +inline U8 console_output_line_selected( + const GUI_CONSOLEWINDOW* wnd, + U64 source_id +) { + if( !wnd || !wnd->output_selection_active ) + return 0; + + U64 start = min( + wnd->output_selection_anchor, + wnd->output_selection_cursor + ); + U64 end = max( + wnd->output_selection_anchor, + wnd->output_selection_cursor + ); + return source_id >= start && source_id <= end; +} + +inline void console_push_wrapped_range( + GUI_CONSOLEWINDOW* wnd, + const char* text, + U32 start, U32 end, + U64 source_id +) { + CONSOLE_WRAPPED_LINE wrapped = { + .text = STR( end - start, text + start ), + .source_id = source_id + }; + wnd->wrapped_lines.push( wrapped ); +} + +inline CONSOLE_WRAP_RANGE console_next_wrap_range( + const STR& line, + U32 start, + I32 max_w, + GL_FONT* font +) { + CONSOLE_WRAP_RANGE range = { + line.size, + line.size + }; + + I32 last_space = -1; + U32 cursor = start; + F32 width = 0.f; + + while( cursor < line.size ) { + U8 c = (U8)line.data[cursor]; + F32 advance = font->glyphs[c].advance_x; + + if( width + advance > max_w ) + break; + + width += advance; + + if( c == ' ' && cursor > start ) + last_space = (I32)cursor; + ++cursor; + } + + if( cursor == line.size ) + return range; + + if( cursor == start ) { + range.next = start + 1; + range.end = start + 1; + return range; + } + + if( last_space > (I32)start ) { + range.end = (U32)last_space; + range.next = range.end + 1; + + while( range.next < line.size + && line.data[range.next] == ' ' + ) ++range.next; + + return range; + } + + range.next = cursor; + range.end = cursor; + return range; +} + +void console_wrap_line( + GUI_CONSOLEWINDOW* wnd, + const STR& line, + U64 source_id, + I32 max_w, + GL_FONT* font +) { + if( !wnd || !font || max_w <= 0 ) + return; + + if( !line.size ) { + console_push_wrapped_range( + wnd, line.data, + 0, 0, source_id + ); + return; + } + + for( U32 start = 0; start < line.size; ) { + CONSOLE_WRAP_RANGE range = console_next_wrap_range( + line, start, + max_w, font + ); + console_push_wrapped_range( + wnd, line.data, + start, range.end, + source_id + ); + + start = range.next; + } +} + +void console_rebuild_wrapped_lines( + GUI_CONSOLEWINDOW* wnd, + const CONSOLE_DATA* state, + I32 width +) { + if( !wnd || !state || width <= 0 ) + return; + + U8 lines_removed = wnd->seen_revision != state->lines.revision; + + if( !lines_removed + && wnd->wrapped_width == width + && wnd->wrapped_revision == state->revision + ) return; + + GL_FONT* font = _gui.fonts.jpn12.glfnt; + if( !font ) + return; + + I32 old_count = (I32)wnd->wrapped_lines.size; + I32 old_offset = wnd->scroll_offset; + U8 width_changed = wnd->wrapped_width != width; + + wnd->wrapped_lines.clear(); + + U64 first_source_id = state->lines.total_pushed - state->lines.count; + for( U32 idx = 0; idx < state->lines.count; ++idx ) { + console_wrap_line( + wnd, + state->lines[idx], + first_source_id + idx, + width, + font + ); + } + + if( state->partial_stdout_line.size ) { + console_wrap_line( + wnd, + state->partial_stdout_line, + state->lines.total_pushed, + width, + font + ); + } + + I32 new_count = (I32)wnd->wrapped_lines.size; + + if( lines_removed ) + wnd->scroll_offset = 0; + else if( old_offset > 0 && !width_changed ) { + wnd->scroll_offset = max( 0, + old_offset + new_count - old_count + ); + } + wnd->wrapped_width = width; + wnd->wrapped_revision = state->revision; + wnd->seen_revision = state->lines.revision; +} + +void draw_console_output( GUI_CONSOLEWINDOW* wnd ) { + VEC2 pos = get_console_output_pos( wnd ); + VEC2 sz = get_console_output_sz( wnd ); + if( sz.x <= 0 || sz.y <= 0 ) + return; + + console_rebuild_wrapped_lines( wnd, &game->console, (I32)sz.x ); + console_validate_output_selection( wnd, &game->console ); + + I32 line_height = console_output_line_height(); + if( line_height <= 0 ) + return; + + wnd->visible_rows = (I32)sz.y / line_height; + I32 row_count = (I32)wnd->wrapped_lines.size; + I32 max_scroll = max( 0, row_count - wnd->visible_rows ); + wnd->scroll_offset = m_clamp( wnd->scroll_offset, 0, max_scroll ); + gui_draw_push_clip( + (I32)pos.x, (I32)pos.y, + (I32)sz.x, (I32)sz.y + ); + I32 y = (I32)( pos.y + sz.y ) - line_height; + I32 index = row_count - 1 - wnd->scroll_offset; + + while( index >= 0 && y >= pos.y ) { + CONSOLE_WRAPPED_LINE* row = &wnd->wrapped_lines[index]; + + if( console_output_line_selected( wnd, row->source_id ) ) + gui_draw_frect( (I32)pos.x, y, (I32)sz.x, line_height, ui_clr.selection ); + gui_draw_str( + pos.x, y, ALIGN_L, + FNT_JPN12, ui_clr.txt, + "%s", row->text.data + ); + y -= line_height; + --index; + } + gui_draw_pop_clip(); +} + +inline LIST<CVAR*> console_cvar_list( const char* query, U32 query_length ) { + LIST<CVAR*> matches; + + if( !varl || !query || !query_length ) + return matches; + + matches.reserve( varl->vars.size ); + for( U32 idx = 0; idx < varl->vars.size; ++idx ) { + CVAR* var = varl->vars[idx]; + if( !var || !var->name.data ) + continue; + + U32 name_length = (U32)strlen( var->name.data ); + + if( name_length >= query_length + && !strncmp( var->name.data, query, query_length ) + ) matches.push( var ); + } + return matches; +} + +inline void console_close_input_menu( GUI_CONSOLEWINDOW* wnd ) { + if( !wnd ) + return; + + if( wnd->menus.input.menu ) { + gui_contextmenu_hide( wnd->menus.input.menu ); + wnd->menus.input.menu = 0; + } + + if( wnd->menus.input.items.size ) + wnd->menus.input.items.clear(); + + if( wnd->menus.input.cvars.size ) + wnd->menus.input.cvars.clear(); + + wnd->menus.input.mode = CONSOLE_MENU_INPUT_NONE; + wnd->menus.input.history_size = 0; + wnd->menus.input.cvar_count = 0; + wnd->menus.input.query[0] = 0; + wnd->menus.input.suppressed = 0; + wnd->menus.input.suppressed_value[0] = 0; +} + +inline void console_close_output_menu( GUI_CONSOLEWINDOW* wnd ) { + if( wnd && wnd->menus.output.menu ) + gui_contextmenu_hide( wnd->menus.output.menu ); +} + +inline U8 console_mouse_over_menu( + GUI_CONTEXTMENU* menu, + VEC2 mouse +) { + if( !menu || !gui_contextmenu_open( menu ) ) + return 0; + I32 x = gui_relx( menu ); + I32 y = gui_rely( menu ); + return mouse.x >= x + && mouse.x < x + menu->w + && mouse.y >= y + && mouse.y < y + menu->h; +} + +void console_copy_output_selection( void* data ) { + GUI_CONSOLEWINDOW* wnd = (GUI_CONSOLEWINDOW*)data; + CONSOLE_DATA* state = game ? &game->console : 0; + + if( !console_validate_output_selection( wnd, state ) ) + return; + + U64 selection_start = min( + wnd->output_selection_anchor, + wnd->output_selection_cursor + ); + U64 selection_end = max( + wnd->output_selection_anchor, + wnd->output_selection_cursor + ); + + U64 first_complete = state->lines.total_pushed - state->lines.count; + U8 copied_line = 0; + STR copied; + + for( U64 source_id = selection_start;; ++source_id ) { + const STR* line = 0; + + if( source_id < state->lines.total_pushed + && source_id >= first_complete + ) { + U64 logical_index = source_id - first_complete; + if( logical_index < state->lines.count ) + line = &state->lines[(U32)logical_index]; + } + else if( source_id == state->lines.total_pushed + && state->partial_stdout_line.size + ) line = &state->partial_stdout_line; + + if( line ) { + if( copied_line ) + copied.push( '\n' ); + copied.append( *line ); + copied_line = 1; + } + + if( source_id == selection_end ) + break; + } + + if( copied_line ) + SDL_SetClipboardText( copied.data ); +} + +inline void console_open_output_menu( + GUI_CONSOLEWINDOW* wnd, + I32 mouse_x, I32 mouse_y +) { + if( !wnd || !wnd->output_selection_active ) + return; + + console_close_input_menu( wnd ); + console_close_output_menu( wnd ); + wnd->menus.output.menu = gui_show_contextmenu( + mouse_x, mouse_y, + &wnd->menus.output.items, + wnd, 0, 1, &wnd->menus.output.menu + ); + + if( !wnd->menus.output.menu ) + return; + + I32 canvas_w = game->gl->canvas_size[0]; + I32 canvas_h = game->gl->canvas_size[1]; + + I32 screen_x = m_clamp( mouse_x, + 0, + max( 0, + canvas_w - wnd->menus.output.menu->w + ) + ); + I32 screen_y = m_clamp( mouse_y, + 0, + max( 0, + canvas_h - wnd->menus.output.menu->h + ) + ); + wnd->menus.output.menu->x = screen_x - gui_relx( wnd ); + wnd->menus.output.menu->y = screen_y - gui_rely( wnd ); +} + +inline I32 console_input_menu_visible_rows( + GUI_CONTEXTMENU* menu, + I32 item_count, + I32 available_h +) { + I32 rows = min( + menu->max_visible_rows, + item_count + ); + + while( rows > 0 + && gui_contextmenu_height( + rows, menu->title + ) > available_h + ) --rows; + return rows; +} + +inline void console_reset_input_menu_view( + GUI_CONSOLEWINDOW* wnd, + GUI_CONTEXTMENU* menu, + I32 item_count +) { + if( menu->selected_idx >= 0 ) { + gui_contextmenu_reveal_selection( menu ); + return; + } + menu->first_visible = wnd->menus.input.mode == CONSOLE_MENU_INPUT_HIST + ? max( 0, item_count - menu->visible_rows ) + : 0; +} + +inline void console_position_input_menu( GUI_CONSOLEWINDOW* wnd ) { + if( !wnd || !wnd->input || !wnd->menus.input.menu ) + return; + + GUI_CONTEXTMENU* menu = wnd->menus.input.menu; + if( !menu->items || !menu->items->size ) + return; + + I32 canvas_w = max( 1, game->gl->canvas_size[0] ); + I32 canvas_h = max( 1, game->gl->canvas_size[1] ); + + I32 input_x = gui_relx( wnd->input ); + I32 input_bottom = gui_rely( wnd->input ) + + GUI_TEXTBOX_TITLE_OFFSET + + wnd->input->h; + I32 item_count = (I32)menu->items->size; + + menu->visible_rows = console_input_menu_visible_rows( + menu, + item_count, + max( 0, canvas_h - input_bottom ) + ); + menu->enabled = menu->visible_rows > 0; + if( !menu->enabled ) + return; + + menu->h = gui_contextmenu_height( menu->visible_rows, menu->title ); + + console_reset_input_menu_view( + wnd, menu, + item_count + ); + + menu->w = min( + max( 1, wnd->input->w ), + max( 1, canvas_w - input_x ) + ); + menu->y = input_bottom - gui_rely( wnd ); + menu->x = input_x - gui_relx( wnd ); + menu->xbound = menu->w; + menu->ybound = menu->h; +} + +inline U8 console_show_input_menu( GUI_CONSOLEWINDOW* wnd ) { + if( !wnd || !wnd->input || !wnd->menus.input.items.size ) + return 0; + + I32 max_rows = min( + GUI_CONSOLE_HISTORY_MENU_ROWS, + (I32)wnd->menus.input.items.size + ); + + I32 input_x = gui_relx( wnd->input ); + I32 input_bottom = gui_rely( wnd->input ) + + GUI_TEXTBOX_TITLE_OFFSET + + wnd->input->h; + wnd->menus.input.menu = gui_show_contextmenu( + input_x, input_bottom, + &wnd->menus.input.items, + wnd, 0, max_rows, + &wnd->menus.input.menu + ); + + if( !wnd->menus.input.menu ) + return 0; + + console_position_input_menu( wnd ); + return 1; +} + +inline CONSOLE_INPUT_SEGMENT console_input_segment( GUI_TEXTBOX* textbox ) { + CONSOLE_INPUT_SEGMENT segment{}; + + if( !textbox ) + return segment; + + U32 total_length = (U32)strlen( textbox->value ); + U8 in_string = 0; + + for( U32 idx = 0; idx < total_length; ++idx ) { + char c = textbox->value[idx]; + + if( c == '"' ) + in_string ^= 1; + else if( c == ';' && !in_string ) + segment.start = idx + 1; + } + + while( segment.start < total_length + && textbox->value[segment.start] == ' ' + ) ++segment.start; + + segment.length = total_length - segment.start; + segment.valid = !in_string; + return segment; +} + +inline const char* console_input_menu_completion( + GUI_CONSOLEWINDOW* wnd, + const CONSOLE_INPUT_SEGMENT& segment, + CONSOLE_MENU_INPUT_MODE* selected_mode +) { + if( !wnd || !wnd->input || !wnd->menus.input.menu || !selected_mode ) + return 0; + + I32 selected = wnd->menus.input.menu->selected_idx; + *selected_mode = wnd->menus.input.mode; + + if( *selected_mode == CONSOLE_MENU_INPUT_HIST ) { + if( segment.length + || !game + || selected < 0 + || selected >= (I32)game->console.history.size + ) return 0; + + return game->console.history[selected].data; + } + + if( *selected_mode != CONSOLE_MENU_INPUT_CVAR + || !segment.length + || selected < 0 + || selected >= (I32)wnd->menus.input.cvars.size + ) return 0; + + U32 cached_length = (U32)strlen( wnd->menus.input.query ); + + if( cached_length != segment.length + || strncmp( + wnd->menus.input.query, + wnd->input->value + segment.start, + segment.length + ) + ) return 0; + + CVAR* var = wnd->menus.input.cvars[selected]; + return var && var->name.data + ? var->name.data + : 0; +} + +inline U8 console_apply_input_completion( + GUI_TEXTBOX* textbox, + U32 start, + const char* completion +) { + if( !textbox || !completion ) + return 0; + + U32 capacity = min( + textbox->maxlen, + GUI_TEXTBOX_MAX + ); + + if( !capacity || start >= capacity - 1 ) + return 0; + + snprintf( + textbox->value + start, + capacity - start, + "%s", completion + ); + + U32 length = (U32)strlen( textbox->value ); + + textbox->active = 1; + textbox->blink = 1; + textbox->blinktime = gui_time(); + textbox->cursorpxin = 0; + textbox->cursorpxout = 0; + textbox->cursorin = length; + textbox->cursorout = length; + textbox->cursor_goal_valid = 0; + return 1; +} + +inline void console_suppress_input_menu( + GUI_CONSOLEWINDOW* wnd, + const char* value +) { + wnd->menus.input.suppressed = 1; + snprintf( + wnd->menus.input.suppressed_value, + sizeof( wnd->menus.input.suppressed_value ), + "%s", value + ); +} + +inline U8 console_apply_input_menu_selection( + GUI_CONSOLEWINDOW* wnd +) { + if( !wnd || !wnd->input || !wnd->menus.input.menu ) + return 0; + + if( !gui_contextmenu_selected( wnd->menus.input.menu ) ) + return 0; + + CONSOLE_INPUT_SEGMENT segment = console_input_segment( wnd->input ); + if( !segment.valid ) + return 0; + + CONSOLE_MENU_INPUT_MODE selected_mode = wnd->menus.input.mode; + const char* completion = console_input_menu_completion( + wnd, segment, + &selected_mode + ); + if( !completion ) + return 0; + + U8 applied = console_apply_input_completion( + wnd->input, + segment.start, + completion + ); + + char completed_value[GUI_TEXTBOX_MAX]{}; + + if( applied && selected_mode == CONSOLE_MENU_INPUT_CVAR ) { + snprintf( + completed_value, + sizeof( completed_value ), + "%s", wnd->input->value + ); + } + + console_close_input_menu( wnd ); + + if( applied && selected_mode == CONSOLE_MENU_INPUT_CVAR ) + console_suppress_input_menu( wnd, completed_value ); + + return 1; +} + +void console_input_menu_item_cb( void* data ) { + console_apply_input_menu_selection( + (GUI_CONSOLEWINDOW*)data + ); +} + +inline void console_open_cvar_menu( + GUI_CONSOLEWINDOW* wnd, + const char* query, + U32 query_length +) { + console_close_input_menu( wnd ); + + if( !wnd || !wnd->input || !query || !query_length ) + return; + + wnd->menus.input.mode = CONSOLE_MENU_INPUT_CVAR; + wnd->menus.input.cvar_count = varl + ? varl->vars.size + : 0; + + I32 copy_length = (I32)min( + query_length, + GUI_TEXTBOX_MAX - 1 + ); + + snprintf( + wnd->menus.input.query, + sizeof( wnd->menus.input.query ), + "%.*s", copy_length, query + ); + + wnd->menus.input.cvars = console_cvar_list( query, query_length ); + if( !wnd->menus.input.cvars.size ) + return; + + wnd->menus.input.items.reserve( + wnd->menus.input.cvars.size + ); + + for( U32 idx = 0; idx < wnd->menus.input.cvars.size; ++idx ) { + CVAR* var = wnd->menus.input.cvars[idx]; + if( !var || !var->name.data ) + continue; + + GUI_CONTEXTMENU_ITEM* item = wnd->menus.input.items.push( {} ); + gui_contextmenu_set_item( + item, + var->name.data, + console_input_menu_item_cb, + wnd + ); + } + console_show_input_menu( wnd ); +} + +inline void console_open_history_menu( GUI_CONSOLEWINDOW* wnd ) { + console_close_input_menu( wnd ); + + if( !wnd || !wnd->input || !game || !game->console.history.size ) + return; + + wnd->menus.input.mode = CONSOLE_MENU_INPUT_HIST; + wnd->menus.input.history_size = game->console.history.size; + wnd->menus.input.items.reserve( game->console.history.size ); + + for( U32 idx = 0; idx < game->console.history.size; ++idx ) { + GUI_CONTEXTMENU_ITEM* item = wnd->menus.input.items.push( {} ); + gui_contextmenu_set_item( + item, + game->console.history[idx].data, + console_input_menu_item_cb, + wnd + ); + } + + console_show_input_menu( wnd ); +} + +inline U8 console_input_menu_suppressed( + GUI_CONSOLEWINDOW* wnd, + GUI_TEXTBOX* textbox +) { + if( !wnd->menus.input.suppressed ) + return 0; + + if( !strcmp( + wnd->menus.input.suppressed_value, + textbox->value + ) + ) return 1; + + wnd->menus.input.suppressed = 0; + wnd->menus.input.suppressed_value[0] = 0; + return 0; +} + +inline void console_sync_history_menu( + GUI_CONSOLEWINDOW* wnd +) { + if( !game->console.history.size ) { + console_close_input_menu( wnd ); + return; + } + + U8 stale = wnd->menus.input.mode != CONSOLE_MENU_INPUT_HIST + || wnd->menus.input.history_size != game->console.history.size + || !wnd->menus.input.items.size; + + if( stale ) { + console_open_history_menu( wnd ); + return; + } + + if( !wnd->menus.input.menu ) + console_show_input_menu( wnd ); + console_position_input_menu( wnd ); +} + +inline void console_sync_cvar_menu( + GUI_CONSOLEWINDOW* wnd, + const CONSOLE_INPUT_SEGMENT& segment +) { + const char* query = wnd->input->value + segment.start; + U32 cvar_count = varl ? varl->vars.size : 0; + U32 cached_length = (U32)strlen( wnd->menus.input.query ); + + U8 stale = wnd->menus.input.mode != CONSOLE_MENU_INPUT_CVAR + || wnd->menus.input.cvar_count != cvar_count + || cached_length != segment.length; + + if( !stale + && strncmp( + wnd->menus.input.query, + query, + segment.length + ) + ) stale = 1; + + if( stale ) { + console_open_cvar_menu( + wnd, + query, + segment.length + ); + return; + } + + if( !wnd->menus.input.menu && wnd->menus.input.items.size ) + console_show_input_menu( wnd ); + console_position_input_menu( wnd ); +} + +inline void console_sync_input_menu( GUI_CONSOLEWINDOW* wnd ) { + if( !wnd ) + return; + + U8 menu_interacting = wnd->menus.input.menu + && wnd->menus.input.menu->held; + if( !wnd->enabled + || !wnd->input + || ( !wnd->input->active && !menu_interacting ) + ) { + console_close_input_menu( wnd ); + return; + } + + if( console_input_menu_suppressed( wnd, wnd->input ) ) + return; + + CONSOLE_INPUT_SEGMENT segment = console_input_segment( wnd->input ); + if( !segment.valid ) { + console_close_input_menu( wnd ); + return; + } + + if( !segment.length ) + console_sync_history_menu( wnd ); + else + console_sync_cvar_menu( wnd, segment ); +} + +inline U8 console_textbox_key_pressed( GUI_TEXTBOX* textbox, U32 key ) { + U32 idx = key & 0xff; + return textbox->keys[idx] && !textbox->prevkeys[idx]; +} + +inline U8 console_handle_input_menu_keys( GUI_CONSOLEWINDOW* wnd ) { + if( !wnd || !wnd->input || !wnd->menus.input.menu ) + return 0; + + GUI_CONTEXTMENU* menu = wnd->menus.input.menu; + GUI_TEXTBOX* textbox = wnd->input; + + if( !gui_contextmenu_open( menu ) ) + return 0; + + CONSOLE_INPUT_SEGMENT segment = console_input_segment( textbox ); + if( !segment.valid ) + return 0; + + U8 valid_mode = 0; + if( wnd->menus.input.mode == CONSOLE_MENU_INPUT_HIST ) + valid_mode = !segment.length; + else if( wnd->menus.input.mode == CONSOLE_MENU_INPUT_CVAR + && segment.length + ) { + U32 cached_length = (U32)strlen( wnd->menus.input.query ); + valid_mode = cached_length == segment.length + && !strncmp( + wnd->menus.input.query, + textbox->value + segment.start, + segment.length + ); + } + + if( !valid_mode ) + return 0; + + U8 moved = 0; + if( console_textbox_key_pressed( textbox, SDLK_UP ) ) { + gui_contextmenu_move_selection( menu, -1, 1 ); + moved = 1; + } + else if( console_textbox_key_pressed( textbox, SDLK_DOWN ) ) { + gui_contextmenu_move_selection( menu, 1, 1 ); + moved = 1; + } + + if( moved ) { + textbox->active = 1; + textbox->blink = 1; + textbox->blinktime = gui_time(); + } + + if( !console_textbox_key_pressed( textbox, SDLK_RETURN ) + && !console_textbox_key_pressed( textbox, SDLK_KP_ENTER ) + ) return moved; + return console_apply_input_menu_selection( wnd ); +} + +void gui_consolewindow_draw_fn( void* ptr ) { + GUI_CONSOLEWINDOW* wnd = (GUI_CONSOLEWINDOW*)ptr; + I32 max_x = game->gl->canvas_size[0]; + I32 max_y = game->gl->canvas_size[1]; + + if( constrain_console( wnd, max_x, max_y ) + && wnd->active_edges != RESIZE_NONE + ) store_console_drag_origin( wnd ); + layout_console_window( wnd ); + console_position_input_menu( wnd ); + + I32 x = gui_relx( wnd ); + I32 y = gui_rely( wnd ); + + CLR clr = gui_is_fg_window( wnd ) ? ui_clr.border : ui_clr.border_inactive; + gui_draw_frect( x + wnd->w, y + 4, 4, wnd->h, ui_clr.shadow ); + gui_draw_frect( x + 4, y + wnd->h, wnd->w - 2, 4, CLR::BLACK() ); + gui_draw_frect( x, y, wnd->w, wnd->h, clr ); + gui_draw_frect( + x + GUI_CONSOLE_BORDER_WIDTH, + y + GUI_CONSOLE_BORDER_WIDTH, + wnd->w - GUI_CONSOLE_BORDER_WIDTH * 2, + wnd->h - GUI_CONSOLE_BORDER_WIDTH * 2, + ui_clr.bg + ); + gui_draw_frect( x, y, wnd->w, GUI_TITLE_HEIGHT, clr ); + gui_draw_frect( + x + GUI_CONSOLE_BORDER_WIDTH, + y + GUI_CONSOLE_BORDER_WIDTH, + wnd->w - GUI_CONSOLE_BORDER_WIDTH * 2, + GUI_TITLE_HEIGHT - GUI_CONSOLE_BORDER_WIDTH * 2, + ui_clr.bg + ); + + draw_console_output( wnd ); + + wnd->children.each( fn( GUI_BASE** ptr ) { + GUI_BASE* it = *ptr; + if( !it->enabled ) return; + if( it->draw_fn ) it->draw_fn( it ); + else dlog( "gui_editorwindow_draw_fn(): child %p has no draw_fn", it ); + } ); +} + +inline void console_accept_input( GUI_CONSOLEWINDOW* wnd ) { + GUI_TEXTBOX* textbox = wnd->input; + if( !textbox ) + return; + U32 enter = SDLK_RETURN & 0xff; + U32 kp_enter = SDLK_KP_ENTER & 0xff; + + U8 enter_pressed = + ( textbox->keys[enter] && !textbox->prevkeys[enter] ) || + ( textbox->keys[kp_enter] && !textbox->prevkeys[kp_enter] ); + + if( !enter_pressed ) + return; + + if( !console_submit( &game->console, textbox->value ) ) { + textbox->active = 1; + return; + } + + textbox->blinktime = gui_time(); + textbox->cursorpxout = 0; + textbox->cursorpxin = 0; + textbox->cursorout = 0; + textbox->cursorin = 0; + textbox->value[0] = 0; + textbox->active = 1; + textbox->blink = 1; + + wnd->scroll_offset = 0; +} + +inline I32 console_max_scroll( GUI_CONSOLEWINDOW* wnd ) { + if( !wnd ) + return 0; + I32 row_count = (I32)wnd->wrapped_lines.size; + I32 visible = max( 1, wnd->visible_rows ); + return max( 0, row_count - visible ); +} + +inline U8 console_mouse_over_output( + GUI_CONSOLEWINDOW* wnd, + VEC2 mouse +) { + if( !wnd ) + return 0; + + VEC2 pos = get_console_output_pos( wnd ); + VEC2 sz = get_console_output_sz( wnd ); + + return sz.x > 0 + && sz.y > 0 + && mouse.x >= pos.x + && mouse.x < pos.x + sz.x + && mouse.y >= pos.y + && mouse.y < pos.y + sz.y; +} + +inline I32 console_output_hit_test( + GUI_CONSOLEWINDOW* wnd, + VEC2 mouse +) { + if( !console_mouse_over_output( wnd, mouse ) ) + return -1; + + VEC2 pos = get_console_output_pos( wnd ); + VEC2 sz = get_console_output_sz( wnd ); + I32 line_height = console_output_line_height(); + if( line_height <= 0 ) + return -1; + + console_rebuild_wrapped_lines( + wnd, + &game->console, + (I32)sz.x + ); + + wnd->visible_rows = (I32)sz.y / line_height; + if( wnd->visible_rows <= 0 ) + return -1; + + wnd->scroll_offset = m_clamp( + wnd->scroll_offset, + 0, + console_max_scroll( wnd ) + ); + + I32 from_bottom = ( + (I32)( pos.y + sz.y ) + - 1 + - (I32)mouse.y + ) / line_height; + + if( from_bottom < 0 || from_bottom >= wnd->visible_rows ) + return -1; + + I32 index = (I32)wnd->wrapped_lines.size + - 1 + - wnd->scroll_offset + - from_bottom; + if( index < 0 || index >= (I32)wnd->wrapped_lines.size ) + return -1; + return index; +} + +inline U8 console_handle_output_left_mouse( + GUI_CONSOLEWINDOW* wnd, + U8 left_down, + U8 left_pressed, + U8 over_output, + I32 hit +) { + if( !left_down ) + wnd->output_selecting = 0; + + if( left_pressed && !over_output ) { + console_close_output_menu( wnd ); + wnd->output_selection_active = 0; + wnd->output_selecting = 0; + return 1; + } + + if( !left_pressed ) { + if( left_down && wnd->output_selecting && hit >= 0 ) + wnd->output_selection_cursor = wnd->wrapped_lines[hit].source_id; + return 0; + } + + console_close_output_menu( wnd ); + console_close_input_menu( wnd ); + + if( wnd->input ) + wnd->input->active = 0; + + if( hit < 0 ) { + wnd->output_selection_active = 0; + wnd->output_selecting = 0; + return 1; + } + + U64 source_id = wnd->wrapped_lines[hit].source_id; + U8 extend = input.keys[SDLK_LSHIFT & 0xff] + || input.keys[SDLK_RSHIFT & 0xff]; + + if( !extend || !wnd->output_selection_active ) + wnd->output_selection_anchor = source_id; + + wnd->output_selection_cursor = source_id; + wnd->output_selection_active = 1; + wnd->output_selecting = 1; + return 0; +} + +inline void console_handle_output_right_mouse( + GUI_CONSOLEWINDOW* wnd, + U8 right_pressed, + U8 over_output, + I32 hit, + VEC2 mouse +) { + if( !right_pressed || !over_output || hit < 0 ) + return; + + U64 source_id = wnd->wrapped_lines[hit].source_id; + + if( !console_output_line_selected( wnd, source_id ) ) { + wnd->output_selection_anchor = source_id; + wnd->output_selection_cursor = source_id; + wnd->output_selection_active = 1; + } + + wnd->output_selecting = 0; + + if( wnd->input ) + wnd->input->active = 0; + + console_open_output_menu( + wnd, + (I32)mouse.x, + (I32)mouse.y + ); +} + +inline void console_handle_output_mouse( + GUI_CONSOLEWINDOW* wnd, + U8 left_down, + U8 left_pressed, + U8 right_pressed +) { + if( !wnd ) + return; + + if( wnd->output_selection_active + && !console_validate_output_selection( wnd, &game->console ) + ) console_close_output_menu( wnd ); + + VEC2 mouse = input.mouse.pos; + + if( console_mouse_over_menu( wnd->menus.input.menu, mouse ) + || console_mouse_over_menu( wnd->menus.output.menu, mouse ) + ) { + if( !left_down ) + wnd->output_selecting = 0; + return; + } + + U8 over_output = console_mouse_over_output( wnd, mouse ); + I32 hit = over_output + ? console_output_hit_test( wnd, mouse ) + : -1; + + if( console_handle_output_left_mouse( + wnd, + left_down, + left_pressed, + over_output, + hit + ) + ) return; + + console_handle_output_right_mouse( + wnd, + right_pressed, + over_output, + hit, + mouse + ); +} + +inline void console_handle_output_copy_key( GUI_CONSOLEWINDOW* wnd ) { + if( !wnd ) + return; + + U8 c_down = input.keys['c']; + U8 c_pressed = c_down && !wnd->copy_key_held; + + wnd->copy_key_held = c_down; + + U8 ctrl_down = input.keys[SDLK_LCTRL & 0xff] + || input.keys[SDLK_RCTRL & 0xff]; + + if( c_pressed && ctrl_down && wnd->output_selection_active ) + console_copy_output_selection( wnd ); +} + +void console_handle_scroll( GUI_CONSOLEWINDOW* wnd ) { + if( !wnd + || !input.mouse.wheel + || !console_mouse_over_output( wnd, input.mouse.pos ) + ) return; + + I32 delta = 0; + + if( input.mouse.wheel == 255 ) + delta = -1; + else if( input.mouse.wheel == 1 ) + delta = 1; + else + return; + + wnd->scroll_offset = m_clamp( + wnd->scroll_offset + delta, + 0, + console_max_scroll( wnd ) + ); +} + +inline U8 console_handle_resize_input( + GUI_CONSOLEWINDOW* wnd, + I32 max_x, + I32 max_y, + U8 left_down, + U8 left_pressed, + RESIZE_EDGES* hover_edges +) { + *hover_edges = RESIZE_NONE; + + if( constrain_console( wnd, max_x, max_y ) + && wnd->active_edges != RESIZE_NONE + ) store_console_drag_origin( wnd ); + layout_console_window( wnd ); + + if( wnd->active_edges != RESIZE_NONE ) { + U8 lost_focus = SDL_GetKeyboardFocus() != game->gl->window; + + if( !left_down || lost_focus ) + console_resize_end( wnd ); + else { + resize_console_window( wnd, max_x, max_y ); + set_console_cursor( wnd->active_edges ); + return 1; + } + } + *hover_edges = get_console_active_edges( + wnd, input.mouse.pos, + max_x, max_y + ); + if( left_pressed && *hover_edges != RESIZE_NONE ) { + console_resize_begin( wnd, *hover_edges ); + set_console_cursor( *hover_edges ); + return 1; + } + return 0; +} + +void gui_consolewindow_input_fn( void* ptr ) { + GUI_CONSOLEWINDOW* wnd = (GUI_CONSOLEWINDOW*)ptr; + I32 max_x = game->gl->canvas_size[0]; + I32 max_y = game->gl->canvas_size[1]; + + U8 left_down = input.mouse.left; + U8 left_pressed = left_down && !wnd->lmb_held; + wnd->lmb_held = left_down; + + U8 right_down = input.mouse.right; + U8 right_pressed = right_down && !wnd->rmb_held; + wnd->rmb_held = right_down; + + console_handle_output_copy_key( wnd ); + + if( input.mouselock ) + input_capture_mouse( 0 ); + else + input_reset_mouse_accumulator(); + + RESIZE_EDGES active_edges = RESIZE_NONE; + if( console_handle_resize_input( wnd, + max_x, max_y, + left_down, left_pressed, + &active_edges + ) + ) return; + + U8 over_input = is_over_console_input( wnd, input.mouse.pos ); + console_handle_scroll( wnd ); + console_handle_output_mouse( wnd, left_down, left_pressed, right_pressed ); + + RESIZE_EDGES cursor_edges = + !left_down || left_pressed + ? active_edges + : RESIZE_NONE; + set_console_cursor( cursor_edges, over_input ); + + console_sync_input_menu( wnd ); + + gui_base_input_fn( wnd ); + + if( !wnd->enabled ) + return; + + U8 menu_consumed = console_handle_input_menu_keys( wnd ); + if( !menu_consumed ) + console_accept_input( wnd ); + + console_sync_input_menu( wnd ); + + constrain_console( wnd, max_x, max_y ); + layout_console_window( wnd ); + console_position_input_menu( wnd ); +} + +inline void console_set_input_focused( + GUI_CONSOLEWINDOW* wnd, + U8 focused +) { + if( !wnd || !wnd->input ) + return; + + GUI_TEXTBOX* textbox = wnd->input; + textbox->active = focused; + textbox->heldkey = 0; + textbox->m1held = 0; + + memcpy( + textbox->keys, + input.keys, + sizeof( textbox->keys ) + ); + memcpy( + textbox->prevkeys, + input.keys, + sizeof( textbox->prevkeys ) + ); + if( focused ) { + textbox->blink = 1; + textbox->blinktime = gui_time(); + } +} + +void console_close_cb( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + GUI_VIEW* view = (GUI_VIEW*)btn->parent; + GUI_CONSOLEWINDOW* wnd = (GUI_CONSOLEWINDOW*)view->parent; + console_close_output_menu( wnd ); + console_close_input_menu( wnd ); + console_resize_end( wnd ); + wnd->output_selecting = 0; + wnd->copy_key_held = 0; + console_set_input_focused( wnd, 0 ); + wnd->enabled = 0; +} + +GUI_CONSOLEWINDOW* gui_consolewindow_create( I32 x, I32 y, I32 w, I32 h ) { + GUI_CONSOLEWINDOW* wnd = new GUI_CONSOLEWINDOW{}; + wnd->x = x; + wnd->y = y; + wnd->w = w; + wnd->h = h; + wnd->locked = 0; + wnd->draw_fn = gui_consolewindow_draw_fn; + wnd->input_fn = gui_consolewindow_input_fn; + strcpy( wnd->name, "CONSOLE" ); + _gui.windows.push( wnd ); + gui_set_view( wnd ); + gui_view( + GUI_CONSOLE_BORDER_WIDTH, + GUI_CONSOLE_BORDER_WIDTH, + w - GUI_CONSOLE_BORDER_WIDTH * 2, + h - GUI_CONSOLE_BORDER_WIDTH * 2 + ); + return wnd; +} + +GUI_CONSOLEWINDOW* gui_consolewindow( I32 x, I32 y, I32 w, I32 h ) { + GUI_CONSOLEWINDOW* wnd = gui_consolewindow_create( x, y, w, h ); + + wnd->close = gui_button( + get_close_x( wnd->w ), + GUI_CONSOLE_PADDING, + GUI_BUTTON_SIZE, + GUI_BUTTON_SIZE, + "X", + console_close_cb + ); + wnd->input = gui_textbox( + GUI_CONSOLE_PADDING, + get_input_y( wnd->h ), + get_input_w( wnd->w ), + GUI_CONSOLE_INPUT_HEIGHT, + "", 0x400, 0 + ); + wnd->input->active = 1; + wnd->title = gui_title( "console" ); + + GUI_CONTEXTMENU_ITEM* copy = wnd->menus.output.items.push( {} ); + gui_contextmenu_set_item( copy, "copy", console_copy_output_selection, wnd ); + + layout_console_window( wnd ); + return wnd; +} + +U8 gui_consolewindow_input_focused() { + GUI_CONSOLEWINDOW* wnd = game + ? game->console_window + : 0; + + return wnd + && wnd->input + && wnd->enabled + && wnd->input->active; +} + +U8 gui_consolewindow_is_open() { + GUI_CONSOLEWINDOW* wnd = game + ? game->console_window + : 0; + return wnd + && wnd->enabled; +} + +void gui_consolewindow_toggle() { + GUI_CONSOLEWINDOW* wnd = game + ? game->console_window + : 0; + if( !wnd ) + return; + + set_console_cursor( RESIZE_NONE ); + + if( wnd->enabled ) { + console_close_output_menu( wnd ); + console_close_input_menu( wnd ); + console_resize_end( wnd ); + wnd->output_selecting = 0; + wnd->copy_key_held = 0; + console_set_input_focused( wnd, 0 ); + } + + wnd->enabled ^= 1; + + if( wnd->enabled ) { + gui_bring_to_top( wnd ); + console_set_input_focused( wnd, 1 ); + wnd->copy_key_held = input.keys['c']; + wnd->rmb_held = input.mouse.right; + wnd->lmb_held = input.mouse.left; + } +} |
