/* * Quick and Dirty Partition System */ struct Partition_Cursor{ u8 *memory_base; u8 *memory_cursor; i32 max_size; }; internal Partition_Cursor partition_open(void *memory, i32 size){ Partition_Cursor partition = {}; partition.memory_base = partition.memory_cursor = (u8*)memory; partition.max_size = size; return partition; } internal void* partition_allocate(Partition_Cursor *data, i32 size){ void *ret = 0; if ((data->memory_cursor - data->memory_base) + size <= data->max_size && size > 0){ ret = data->memory_cursor; data->memory_cursor += size; } return ret; } /* * Plaform Independent File Name Helpers */ struct File_List_Iterator{ bool32 folder_stage; u8 **filename_ptr; }; internal File_List_Iterator files_iterator_init(File_List *files){ File_List_Iterator files_it = {}; if (files->folder_count > 0){ files_it.filename_ptr = files->folder_names; files_it.folder_stage = 1; } else if (files->file_count > 0){ files_it.filename_ptr = files->file_names; } return files_it; } internal void files_iterator_step(File_List *files, File_List_Iterator *files_it){ ++files_it->filename_ptr; if (files_it->folder_stage){ if (files_it->filename_ptr >= files->folder_names + files->folder_count){ if (files->file_count > 0){ files_it->filename_ptr = files->file_names; } else{ files_it->filename_ptr = 0; } files_it->folder_stage = 0; } } else{ if (files_it->filename_ptr >= files->file_names + files->file_count){ files_it->filename_ptr = 0; } } } /* * Drawing Functions */ internal u32 style_token_color(Editing_Style *style, Cpp_Token_Type type){ u32 result; switch (type){ case CPP_TOKEN_COMMENT: result = style->comment_color; break; case CPP_TOKEN_KEYWORD: result = style->keyword_color; break; case CPP_TOKEN_STRING_CONSTANT: case CPP_TOKEN_CHARACTER_CONSTANT: case CPP_TOKEN_INTEGER_CONSTANT: case CPP_TOKEN_FLOATING_CONSTANT: case CPP_TOKEN_BOOLEAN_CONSTANT: case CPP_TOKEN_INCLUDE_FILE: result = style->constant_color; break; default: result = style->default_color; } return result; } internal void panel_draw(Thread_Context *thread, Render_Target *target, Editing_Panel *panel, bool32 is_active){ Editing_File *file = panel->file; Editing_Style *style = file->style; Font *font = style->font; i32 character_w = (i32)(style_get_character_width(style)); i32 character_h = (i32)(font->line_skip); i32 offset_x = (i32)(panel->x); i32 offset_y = (i32)(panel->y); i32 max_x = (i32)(panel->w); i32 max_y = (i32)(panel->h); Blit_Rect panel_area; panel_area.x_start = offset_x; panel_area.y_start = offset_y; panel_area.x_end = offset_x + max_x; panel_area.y_end = offset_y + max_y; if (!file || !file->data || file->is_dummy){ i32 start_x = (panel_area.x_start + panel_area.x_end)/2; i32 start_y = (panel_area.y_start + panel_area.y_end)/2; persist String null_file_message = make_lit_string("NULL FILE"); start_x -= (character_w*null_file_message.size)/2; start_y -= (character_h)/2; real32 pos_x = 0; real32 pos_y = 0; for (i32 i = 0; i < null_file_message.size; ++i){ u8 to_render = null_file_message.str[i]; if (font->glyphs[to_render].data){ font_draw_glyph_clipped(target, font, to_render, (real32)start_x + pos_x, (real32)start_y + pos_y, style->special_character_color, panel_area); pos_x += character_w; } } } else{ u32 tab_width = style->tab_width; i32 size = (i32)file->size; u8 *data = (u8*)file->data; real32 shift_x = 0; shift_x = -panel->scroll_x * character_w; i32 truncated_start_y = (i32)panel->scroll_y; real32 scroll_amount = panel->scroll_y - truncated_start_y; Panel_Cursor_Data start_cursor; start_cursor = panel->scroll_y_cursor; start_cursor = panel_compute_cursor_from_xy(panel, 0, truncated_start_y); panel->scroll_y_cursor = start_cursor; i32 start_character = start_cursor.pos; real32 pos_x = 0; real32 pos_y = -character_h*scroll_amount; Cpp_Token_Stack token_stack = file->token_stack; u32 main_color = style->default_color; u32 highlight_color = 0x00000000; i32 token_i = 0; bool32 tokens_exist = file->tokens_exist; if (tokens_exist){ // TODO(allen): Use cpp_get_token, it binary searches this shit! while (token_i < token_stack.count && start_character > token_stack.tokens[token_i].start){ ++token_i; } if (token_i != 0){ main_color = style_token_color(style, token_stack.tokens[token_i-1].type); if (token_stack.tokens[token_i-1].type == CPP_TOKEN_JUNK){ highlight_color = style->highlight_junk_color; } } } // TODO(allen): Render through NULLS for (i32 i = start_character; i < size && data[i]; ++i){ u8 to_render = data[i]; u32 fade_color = 0xFFFF00FF; real32 fade_amount = 0.f; if (style->use_paste_color && panel->paste_effect.tick_down > 0 && panel->paste_effect.start <= i && i < panel->paste_effect.end){ fade_color = style->paste_color; fade_amount = (real32)(panel->paste_effect.tick_down) / panel->paste_effect.tick_max; } if (tokens_exist && token_i < token_stack.count){ if (i == token_stack.tokens[token_i].start){ main_color = style_token_color(style, token_stack.tokens[token_i].type); ++token_i; } if (token_i > 0 && i >= token_stack.tokens[token_i-1].start + token_stack.tokens[token_i-1].size){ main_color = 0xFFFFFFFF; } } highlight_color = 0x00000000; if (tokens_exist && token_i > 0 && token_stack.tokens[token_i-1].type == CPP_TOKEN_JUNK && i >= token_stack.tokens[token_i-1].start && i <= token_stack.tokens[token_i-1].start + token_stack.tokens[token_i-1].size){ highlight_color = style->highlight_junk_color; } else if (panel->show_whitespace && character_is_any_whitespace(data[i])){ highlight_color = style->highlight_white_color; } i32 cursor_mode = 0; if (panel->show_temp_highlight){ if (panel->temp_highlight.pos <= i && i < panel->temp_highlight_end_pos){ cursor_mode = 2; } } else{ if (panel->cursor.pos == i){ cursor_mode = 1; } } if (!panel->unwrapped_lines && pos_x + character_w > max_x){ pos_x = 0; pos_y += font->line_skip; } if (highlight_color != 0){ if (file->endline_mode == ENDLINE_RN_COMBINED && to_render == '\r' && i + 1 < size && data[i+1] == '\n'){ // DO NOTHING } else{ draw_rectangle_clipped(target, (i32)shift_x + offset_x + (i32)pos_x, offset_y + (i32)pos_y, character_w, font->line_skip, highlight_color, panel_area); } } if (cursor_mode){ if (is_active){ u32 color = 0x00000000; switch (cursor_mode){ case 1: color = style->cursor_color; break; case 2: color = style->highlight_color; break; } draw_rectangle_clipped(target, (i32)shift_x + offset_x + (i32)pos_x, offset_y + (i32)pos_y, character_w, font->line_skip, color, panel_area); } else{ draw_rectangle_outline_clipped(target, (i32)shift_x + offset_x + (i32)pos_x, offset_y + (i32)pos_y, character_w, font->line_skip, style->cursor_color, panel_area); } } if (i == panel->mark){ draw_rectangle_outline_clipped(target, (i32)shift_x + offset_x + (i32)pos_x, offset_y + (i32)pos_y, character_w, font->line_skip, style->mark_color, panel_area); } if (to_render == '\r'){ switch (file->endline_mode){ case ENDLINE_RN_COMBINED: { if (i + 1 < size && data[i+1] == '\n'){ // DO NOTHING } else{ u32 char_color = style->special_character_color; if (cursor_mode && is_active){ switch (cursor_mode){ case 1: char_color = style->at_cursor_color; break; case 2: char_color = style->at_highlight_color; break; } } char_color = color_blend(char_color, fade_amount, fade_color); font_draw_glyph_clipped(target, font, '\\', shift_x + offset_x + pos_x, (real32)offset_y + pos_y, char_color, panel_area); pos_x += character_w; draw_rectangle_clipped(target, (i32)shift_x + offset_x + (i32)pos_x, offset_y + (i32)pos_y, character_w, font->line_skip, highlight_color, panel_area); font_draw_glyph_clipped(target, font, 'r', shift_x + offset_x + pos_x, (real32)offset_y + pos_y, char_color, panel_area); pos_x += character_w; } }break; case ENDLINE_RN_SEPARATE: { pos_x = 0; pos_y += font->line_skip; }break; case ENDLINE_RN_SHOWALLR: { u32 char_color = style->special_character_color; if (cursor_mode && is_active){ switch (cursor_mode){ case 1: char_color = style->at_cursor_color; break; case 2: char_color = style->at_highlight_color; break; } } char_color = color_blend(char_color, fade_amount, fade_color); font_draw_glyph_clipped(target, font, '\\', shift_x + offset_x + pos_x, (real32)offset_y + pos_y, char_color, panel_area); pos_x += character_w; draw_rectangle_clipped(target, (i32)shift_x + offset_x + (i32)pos_x, offset_y + (i32)pos_y, character_w, font->line_skip, highlight_color, panel_area); font_draw_glyph_clipped(target, font, 'r', shift_x + offset_x + pos_x, (real32)offset_y + pos_y, char_color, panel_area); pos_x += character_w; }break; } } else if (to_render == '\n'){ pos_x = 0; pos_y += font->line_skip; } else if (to_render == '\t'){ if (highlight_color != 0){ draw_rectangle_clipped(target, (i32)shift_x + offset_x + (i32)pos_x + character_w, offset_y + (i32)pos_y, character_w*(tab_width-1), font->line_skip, highlight_color, panel_area); } pos_x += character_w*tab_width; } else if (font->glyphs[to_render].data){ u32 char_color = main_color; if (cursor_mode && is_active){ switch (cursor_mode){ case 1: char_color = style->at_cursor_color; break; case 2: char_color = style->at_highlight_color; break; } } char_color = color_blend(char_color, fade_amount, fade_color); font_draw_glyph_clipped(target, font, to_render, shift_x + offset_x + pos_x, (real32)offset_y + pos_y, char_color, panel_area); pos_x += character_w; } else{ pos_x += character_w; } if (pos_y > max_y){ break; } } } } /* * Hot Directory */ struct Hot_Directory{ String string; File_List file_list; }; internal void hot_directory_init(Hot_Directory *hot_directory){ hot_directory->string = make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256); i32 dir_size = system_get_working_directory(hot_directory->string.str, hot_directory->string.memory_size); if (dir_size <= 0){ dir_size = system_get_easy_directory(hot_directory->string.str); } hot_directory->string.size = dir_size; append(&hot_directory->string, (u8*)"\\"); } internal void hot_directory_reload_list(Hot_Directory *hot_directory){ if (hot_directory->file_list.block){ system_free_file_list(hot_directory->file_list); } hot_directory->file_list = system_get_files(hot_directory->string); } internal bool32 hot_directory_set(Hot_Directory *hot_directory, String str){ bool32 did_set = 0; if (copy_checked(&hot_directory->string, str)){ did_set = 1; system_free_file_list(hot_directory->file_list); hot_directory->file_list = system_get_files(str); } return did_set; } struct Hot_Directory_Match{ u8 *filename; bool32 is_folder; }; internal Hot_Directory_Match hot_directory_first_match(Hot_Directory *hot_directory, String str, bool32 include_files, bool32 exact_match){ Hot_Directory_Match result = {}; File_List files = hot_directory->file_list; File_List_Iterator files_it = files_iterator_init(&files); while (files_it.filename_ptr && (include_files || files_it.folder_stage)){ u8 *filename = *files_it.filename_ptr; bool32 is_match = 0; if (exact_match){ if (match(filename, str)){ is_match = 1; } } else{ if (str.size == 0 || has_substr_unsensitive(filename, str)){ is_match = 1; } } if (is_match){ result.is_folder = files_it.folder_stage; result.filename = filename; break; } files_iterator_step(&files, &files_it); } return result; } /* * App Structs */ struct Command_Data; typedef void (*Command_Function)(Command_Data *command); enum Input_Request_Type{ REQUEST_SYS_FILE, REQUEST_LIVE_FILE, // never below this REQUEST_COUNT }; struct Input_Request{ Input_Request_Type type; union{ struct{ String query; String dest; bool32 hit_ctrl_newline; bool32 fast_folder; } sys_file; struct{ String query; String dest; bool32 hit_ctrl_newline; } live_file; }; }; enum App_State{ APP_STATE_EDIT, APP_STATE_SEARCH, APP_STATE_RESIZING, // never below this APP_STATE_COUNT }; struct App_State_Incremental_Search{ String str; bool32 reverse; i32 pos; }; struct App_State_Resizing{ Editing_Panel *left, *right; }; struct Command_Map{ Command_Function basic_mode_key[1+sizeof(Key_Codes)/2]; Command_Function control_ascii[128]; Command_Function control_key[1+sizeof(Key_Codes)/2]; }; struct App_Vars{ Command_Map map; Font font; Editing_Style style; Interactive_Style command_style; Editing_Working_Set working_set; Editing_Layout layout; real32 last_click_x, last_click_y; Hot_Directory hot_directory; Input_Request request_queue[16]; i32 request_count, request_filled, request_max; Command_Function pending_command; App_State state; union{ App_State_Incremental_Search isearch; App_State_Resizing resizing; }; }; /* * Commands */ struct Command_Data{ Editing_Panel *panel; Editing_Working_Set *working_set; Editing_Layout *layout; Editing_Style *style; App_Vars *vars; i32 screen_width, screen_height; i32 screen_y_off; Key_Event_Data key; Input_Request *requests; i32 request_count, request_filled; }; internal void app_clear_request_queue(App_Vars *vars){ for (i32 i = 0; i < vars->request_count; ++i){ Input_Request *request = vars->request_queue + i; switch (request->type){ case REQUEST_SYS_FILE: { system_free_memory(request->sys_file.query.str); system_free_memory(request->sys_file.dest.str); }break; case REQUEST_LIVE_FILE: { system_free_memory(request->live_file.query.str); system_free_memory(request->live_file.dest.str); }break; } } vars->request_count = 0; vars->request_filled = 0; } internal void command_write_character(Command_Data *command){ Editing_Panel *panel = command->panel; panel_write_character(panel, (u8)command->key.character); if (panel->unwrapped_lines){ panel->preferred_x = panel->cursor.unwrapped_x; } else{ panel->preferred_x = panel->cursor.wrapped_x; } panel->file->cursor.pos = panel->cursor.pos; switch ((u8)command->key.character){ case '{': case '}': case '(': case ')': case ';': case ':': case '#': case '\n': { panel_auto_tab(panel, panel->cursor.pos, panel->cursor.pos); }break; } panel_measure_all_wrapped_y(panel); } internal void command_move_left(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_File *file = panel->file; u8 *data = (u8*)file->data; i32 pos = panel->cursor.pos; if (pos > 0){ if (file->endline_mode == ENDLINE_RN_COMBINED){ pos = pos_adjust_to_left(pos, data); if (pos > 0){ --pos; } else{ pos = pos_adjust_to_self(pos, data, file->size); } } else{ --pos; } } panel_cursor_move(panel, pos); } internal void command_move_right(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_File *file = panel->file; i32 size = file->size; u8* data = (u8*)file->data; i32 pos = panel->cursor.pos; if (pos < size){ ++pos; if (file->endline_mode == ENDLINE_RN_COMBINED){ pos = pos_adjust_to_self(pos, data, size); } } panel_cursor_move(panel, pos); } internal void command_backspace(Command_Data *command){ Editing_Panel *panel = command->panel; i32 cursor_pos = panel->cursor.pos; Editing_File *file = panel->file; i8 *data = (i8*)file->data; if (cursor_pos > 0 && cursor_pos <= (i32)file->size){ if (file->endline_mode == ENDLINE_RN_COMBINED){ i32 target_pos = cursor_pos; if (cursor_pos > 1 && data[cursor_pos] == '\n' && data[cursor_pos-1] == '\r'){ --target_pos; } if (target_pos > 1 && data[target_pos-1] == '\n' && data[target_pos-2] == '\r'){ buffer_delete_range(file, target_pos-2, target_pos); if (panel->mark >= cursor_pos){ panel->mark -= 2; } cursor_pos -= 2; } else{ if (target_pos > 0){ buffer_delete(file, target_pos-1); if (panel->mark >= cursor_pos){ --panel->mark; } --cursor_pos; } } } else{ buffer_delete(file, cursor_pos-1); if (panel->mark >= cursor_pos){ --panel->mark; } --cursor_pos; } panel_measure_all_wrapped_y(panel); panel_cursor_move(panel, cursor_pos); } } internal void command_delete(Command_Data *command){ Editing_Panel *panel = command->panel; i32 cursor_pos = panel->cursor.pos; Editing_File *file = panel->file; i8 *data = (i8*)file->data; if (file->size > 0){ if (file->endline_mode == ENDLINE_RN_COMBINED){ if (cursor_pos > 0 && data[cursor_pos-1] == '\r' && data[cursor_pos] == '\n'){ buffer_delete_range(file, cursor_pos-1, cursor_pos+1); if (panel->mark > cursor_pos){ panel->mark -= 2; } cursor_pos -= 1; } else{ buffer_delete(file, cursor_pos); if (panel->mark > cursor_pos){ --panel->mark; } } } else{ buffer_delete(file, cursor_pos); if (panel->mark > cursor_pos){ --panel->mark; } } panel_measure_all_wrapped_y(panel); panel_cursor_move(panel, cursor_pos); } } internal void command_move_up(Command_Data *command){ Editing_Panel *panel = command->panel; i32 cy = panel_get_cursor_y(panel)-1; i32 px = panel->preferred_x; if (cy >= 0){ panel->cursor = panel_compute_cursor_from_xy(panel, px, cy); panel->file->cursor.pos = panel->cursor.pos; } } internal void command_move_down(Command_Data *command){ Editing_Panel *panel = command->panel; i32 cy = panel_get_cursor_y(panel)+1; i32 px = panel->preferred_x; panel->cursor = panel_compute_cursor_from_xy(panel, px, cy); panel->file->cursor.pos = panel->cursor.pos; } internal void command_seek_end_of_line(Command_Data *command){ Editing_Panel *panel = command->panel; i32 pos = panel_find_end_of_line(panel, panel->cursor.pos); panel_cursor_move(panel, pos); } internal void command_seek_beginning_of_line(Command_Data *command){ Editing_Panel *panel = command->panel; i32 pos = panel_find_beginning_of_line(panel, panel->cursor.pos); panel_cursor_move(panel, pos); } internal void command_seek_whitespace_right(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_File *file = panel->file; u32 size = file->size; u8* data = (u8*)file->data; u32 pos = panel->cursor.pos; while (pos < size && character_is_any_whitespace(data[pos])){ ++pos; } while (pos < size && !character_is_any_whitespace(data[pos])){ ++pos; } panel_cursor_move(panel, pos); } internal void command_seek_whitespace_left(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_File *file = panel->file; u8* data = (u8*)file->data; u32 pos = panel->cursor.pos; --pos; while (pos > 0 && character_is_any_whitespace(data[pos])){ --pos; } while (pos > 0 && !character_is_any_whitespace(data[pos])){ --pos; } if (pos != 0){ ++pos; } panel_cursor_move(panel, pos); } internal void command_seek_whitespace_up(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_File *file = panel->file; u8* data = (u8*)file->data; u32 pos = panel->cursor.pos; while (pos > 0 && character_is_any_whitespace(data[pos])){ --pos; } bool32 no_hard_character = 0; while (pos > 0){ if (starts_new_line(data[pos], file->endline_mode)){ if (no_hard_character){ break; } else{ no_hard_character = 1; } } else{ if (!character_is_any_whitespace(data[pos])){ no_hard_character = 0; } } --pos; } if (pos != 0){ ++pos; } if (file->endline_mode == ENDLINE_RN_COMBINED){ pos = pos_adjust_to_self(pos, data, file->size); } panel_cursor_move(panel, pos); } internal void command_seek_whitespace_down(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_File *file = panel->file; i32 size = file->size; u8* data = (u8*)file->data; i32 pos = panel->cursor.pos; while (pos < size && character_is_any_whitespace(data[pos])){ ++pos; } bool32 no_hard_character = 0; i32 prev_endline = -1; while (pos < size){ if (starts_new_line(data[pos], file->endline_mode)){ if (no_hard_character){ break; } else{ no_hard_character = 1; prev_endline = pos; } } else{ if (!character_is_any_whitespace(data[pos])){ no_hard_character = 0; } } ++pos; } if (prev_endline == -1 || prev_endline+1 >= size){ pos = size-1; } else{ pos = prev_endline+1; } panel_cursor_move(panel, pos); } internal void command_seek_token_left(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_File *file = panel->file; TentativeAssert(file->tokens_exist); i32 current_token; Cpp_Get_Token_Result get_result = cpp_get_token(&file->token_stack, panel->cursor.pos); current_token = get_result.token_index; // TODO(allen): Make nulltoken? if (current_token == -1){ current_token = 0; } Cpp_Token *token = &file->token_stack.tokens[current_token]; if (token->start == panel->cursor.pos && current_token > 0){ --token; } panel_cursor_move(panel, token->start); } internal void command_seek_token_right(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_File *file = panel->file; TentativeAssert(file->tokens_exist); // TODO(allen): Make nulltoken? i32 current_token; Cpp_Get_Token_Result get_result = cpp_get_token(&file->token_stack, panel->cursor.pos); current_token = get_result.token_index; if (get_result.in_whitespace){ ++current_token; } if (current_token >= file->token_stack.count){ current_token = file->token_stack.count - 1; } Cpp_Token *token = &file->token_stack.tokens[current_token]; panel_cursor_move(panel, token->start + token->size); } internal void command_begin_search_state(Command_Data *command){ App_Vars *vars = command->vars; vars->state = APP_STATE_SEARCH; vars->isearch.str = make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256); vars->isearch.reverse = 0; vars->isearch.pos = command->panel->cursor.pos; } internal void command_begin_rsearch_state(Command_Data *command){ App_Vars *vars = command->vars; vars->state = APP_STATE_SEARCH; vars->isearch.str = make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256); vars->isearch.reverse = 1; vars->isearch.pos = command->panel->cursor.pos; } internal void command_set_mark(Command_Data *command){ Editing_Panel *panel = command->panel; panel->mark = (i32)panel->cursor.pos; } internal void command_copy(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_Working_Set *working_set = command->working_set; Range range = get_range(panel->cursor.pos, panel->mark); if (range.smaller < range.larger){ u8 *data = (u8*)panel->file->data; if (panel->file->endline_mode == ENDLINE_RN_COMBINED){ range = range_adjust_to_left(range, data); } clipboard_copy(working_set, data, range); } } internal void command_cut(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_Working_Set *working_set = command->working_set; Range range = get_range(panel->cursor.pos, panel->mark); if (range.smaller < range.larger){ Editing_File *file = panel->file; u8 *data = (u8*)file->data; if (file->endline_mode == ENDLINE_RN_COMBINED){ range = range_adjust_to_left(range, data); } clipboard_copy(working_set, data, range); buffer_delete_range(file, range); panel->mark = pos_universal_fix(range.smaller, file->data, file->size, file->endline_mode); panel_measure_all_wrapped_y(panel); panel_cursor_move(panel, panel->mark); } } internal void command_paste(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_Working_Set *working_set = command->working_set; if (working_set->clipboard_size > 0){ panel->next_mode.rewrite = 1; Editing_File *file = panel->file; String *src = working_set_clipboard_head(working_set); i32 pos_left = panel->cursor.pos; if (file->endline_mode == ENDLINE_RN_COMBINED){ pos_left = pos_adjust_to_left(pos_left, file->data); } panel_write_chunk(panel, src, pos_left); panel_measure_all_wrapped_y(panel); panel->mark = pos_universal_fix(pos_left, file->data, file->size, file->endline_mode); i32 ticks = 20; panel->paste_effect.start = pos_left; panel->paste_effect.end = pos_left + src->size; panel->paste_effect.color = file->style->paste_color; panel->paste_effect.tick_down = ticks; panel->paste_effect.tick_max = ticks; } } internal void command_paste_next(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_Working_Set *working_set = command->working_set; if (working_set->clipboard_size > 0 && panel->mode.rewrite){ panel->next_mode.rewrite = 1; Range range = get_range(panel->mark, panel->cursor.pos); if (range.smaller < range.larger){ Editing_File *file = panel->file; if (file->endline_mode == ENDLINE_RN_COMBINED){ range = range_adjust_to_left(range, file->data); } String *src = working_set_clipboard_roll_down(working_set); buffer_replace_range(file, range.smaller, range.larger, src->str, src->size); panel->cursor = panel_compute_cursor_from_pos(panel, range.smaller+src->size); panel->preferred_x = panel_get_cursor_x(panel); panel->file->cursor.pos = panel->cursor.pos; // TODO(allen): faster way to recompute line measurements afterwards buffer_measure_all_lines(file); panel_measure_all_wrapped_y(panel); panel->mark = pos_universal_fix(range.smaller, file->data, file->size, file->endline_mode); i32 ticks = 20; panel->paste_effect.start = range.smaller; panel->paste_effect.end = range.smaller + src->size; panel->paste_effect.color = file->style->paste_color; panel->paste_effect.tick_down = ticks; panel->paste_effect.tick_max = ticks; } } } internal void command_delete_chunk(Command_Data *command){ Editing_Panel *panel = command->panel; Range range = get_range(panel->cursor.pos, panel->mark); if (range.smaller < range.larger){ Editing_File *file = panel->file; if (file->endline_mode == ENDLINE_RN_COMBINED){ range = range_adjust_to_left(range, file->data); } buffer_delete_range(file, range); panel_measure_all_wrapped_y(panel); panel->cursor = panel_compute_cursor_from_pos(panel, range.smaller); panel->mark = pos_universal_fix(range.smaller, file->data, file->size, file->endline_mode); panel->file->cursor.pos = panel->cursor.pos; } } // TODO(allen): Make this preserve order (look at layout_open_panel) // Make this preserve old ratios or sizes of panels instead of // resizing them all. internal void command_open_panel(Command_Data *command){ Editing_Layout *layout = command->layout; Editing_Working_Set *working_set = command->working_set; // TODO(allen): This is wrong. We should not be passing in the style // like this. The system should be looking it up here. Editing_Style *style = command->style; // TODO(allen): change the screen info to real32 at the base level? real32 screen_full_width = (real32)command->screen_width; real32 screen_full_height = (real32)command->screen_height; real32 screen_y_off = (real32)command->screen_y_off; layout_open_panel(layout, working_set->files, style); real32 panel_w = ((real32)screen_full_width / layout->panel_count); real32 panel_x_pos = 0; for (i32 panel_i = 0; panel_i < layout->panel_count; ++panel_i){ Editing_Panel *panel = &layout->panels[panel_i]; panel->full_x = Floor(panel_x_pos); panel->full_w = Floor(panel_w); panel->full_y = Floor(screen_y_off); panel->full_h = Floor(screen_full_height - screen_y_off); panel_fix_internal_area(panel); panel_x_pos += panel_w; } } internal void command_close_panel(Command_Data *command){ Editing_Layout *layout = command->layout; i32 share_w = layout->panels[layout->active_panel].full_w; layout_close_panel(layout, layout->active_panel); layout_redistribute_width(layout, share_w); } internal Input_Request* app_request_sys_file(App_Vars *vars, String query, String dest_init, bool32 fast_folder){ Input_Request *request = vars->request_queue + (vars->request_count++); *request = {}; request->type = REQUEST_SYS_FILE; request->sys_file.fast_folder = 1; // TODO(allen): Where does this memory come from IRL? I don't like calling to // a system function all the time, knowing it might start doing weird things. // Better to put some limitations on the system so we can gaurantee the memory // this needs will exist. request->sys_file.query = make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256); request->sys_file.dest = make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256); copy(&request->sys_file.query, query); copy(&request->sys_file.dest, dest_init); return request; } internal Input_Request* app_request_live_file(App_Vars *vars, String query, String dest_init){ Input_Request *request = vars->request_queue + (vars->request_count++); *request = {}; request->type = REQUEST_LIVE_FILE; // TODO(allen): Where does this memory come from IRL? I don't like calling to // a system function all the time, knowing it might start doing weird things. // Better to put some limitations on the system so we can gaurantee the memory // this needs will exist. request->live_file.query = make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256); request->live_file.dest = make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256); copy(&request->live_file.query, query); copy(&request->live_file.dest, dest_init); return request; } internal void panel_set_to_new(Editing_Panel *panel){ panel->cursor = {}; panel->cursor.pos = pos_adjust_to_self(0, panel->file->data, panel->file->size); panel->scroll_y = 0; panel->target_y = 0; panel->vel_y = 1.f; panel->scroll_x = 0; panel->target_x = 0; panel->vel_x = 1.f; } internal void command_reopen(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_File *file = panel->file; if (!file->is_dummy){ Editing_File temp_file; if (buffer_load(&temp_file, (u8*)make_c_str(file->source_path))){ buffer_close(file); // TODO(allen): Deduplicate!! Cpp_File cpp_file; cpp_file.data = temp_file.data; cpp_file.size = temp_file.size; { i32 size = cpp_lex_file_token_count(cpp_file); size = cpp_get_token_stack_size(size); temp_file.token_stack = cpp_make_token_stack(size); } cpp_lex_file(cpp_file, &temp_file.token_stack); temp_file.tokens_complete = 1; temp_file.tokens_exist = 1; *file = temp_file; panel_set_to_new(panel); panel_measure_all_wrapped_y(panel); // TODO(allen): Update all other panels that also view this file. } } } internal void command_interactive_open(Command_Data *command){ App_Vars *vars = command->vars; if (command->request_count == 0){ // TODO(allen): Can all of this be simplified? I don't like having this much // complexity for issuing a single request. vars->pending_command = command_interactive_open; Assert(vars->request_count == 0); app_request_sys_file(vars, make_lit_string("Open: "), vars->hot_directory.string, 1); hot_directory_reload_list(&vars->hot_directory); } else{ String *string = &command->requests[0].sys_file.dest; Editing_File *new_file; new_file = buffer_open(&vars->working_set, string->str, command->style); if (!new_file){ // TODO(allen): Here's a really tough one. This crap is now happening twice. // Once here and also in the single_file_input function which checks all of it // whenever the fast_folder_select option is on to see if the user is selecting // a folder. It would be nice to be able to share that information to here when it // is available so we could avoid doing the exact same computations here. u8 front_name_space[256]; String front_name = make_string(front_name_space, 0, ArrayCount(front_name_space)); get_front_of_directory(&front_name, *string); Hot_Directory_Match match = hot_directory_first_match(&vars->hot_directory, front_name, 1, 0); if (match.filename){ // NOTE(allen): is_folder case is handled by the single_file_input function // which would only pass control to this command if the user did not match to // a folder. Assert(!match.is_folder); new_file = buffer_open(&vars->working_set, string->str, command->style); } } if (new_file){ Editing_Panel *active_panel = command->panel; active_panel->file = new_file; panel_set_to_new(active_panel); panel_measure_all_wrapped_y(active_panel); Cpp_File file; file.data = new_file->data; file.size = new_file->size; // TODO(allen): Where should the memory for tokens come from IRL? { i32 size = cpp_lex_file_token_count(file); size = cpp_get_token_stack_size(size); new_file->token_stack = cpp_make_token_stack(size); } cpp_lex_file(file, &new_file->token_stack); new_file->tokens_complete = 1; new_file->tokens_exist = 1; } } } internal void command_save(Command_Data *command){ Editing_Panel *panel = command->panel; String *file_path = &panel->file->source_path; if (file_path->size > 0){ buffer_save(panel->file, file_path->str); } } internal void command_interactive_save_as(Command_Data *command){ App_Vars *vars = command->vars; if (command->request_count == 0){ vars->pending_command = command_interactive_save_as; Assert(vars->request_count == 0); app_request_sys_file(vars, make_lit_string("Save As: "), vars->hot_directory.string, 1); hot_directory_reload_list(&vars->hot_directory); } else{ String *string = &command->requests[0].sys_file.dest; buffer_save_and_set_names(command->panel->file, string->str); } } internal void command_change_active_panel(Command_Data *command){ Editing_Layout *layout = command->layout; if (layout->panel_count > 1){ ++layout->active_panel; if (layout->active_panel >= layout->panel_count){ layout->active_panel = 0; } } } internal void command_interactive_switch_file(Command_Data *command){ App_Vars *vars = command->vars; if (command->request_count == 0){ vars->pending_command = command_interactive_switch_file; Assert(vars->request_count == 0); app_request_live_file(vars, make_lit_string("Switch To: "), make_lit_string("")); } else{ String *string = &command->requests[0].live_file.dest; Editing_File *file; file = working_set_lookup_file(command->working_set, *string); if (file){ Editing_Panel *active_panel = command->panel; active_panel->file = file; Panel_Cursor_Data cursor_data; cursor_data = panel_compute_cursor_from_pos(active_panel, file->cursor.pos); active_panel->cursor = cursor_data; } } } internal void command_kill_file(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_File *file = panel->file; if (file && file->max_size != 0){ buffer_close(file); buffer_get_dummy(file, command->style); } } internal void command_interactive_kill_file(Command_Data *command){ App_Vars *vars = command->vars; if (command->request_count == 0){ vars->pending_command = command_interactive_kill_file; Assert(vars->request_count == 0); app_request_live_file(vars, make_lit_string("Kill: "), make_lit_string("")); } else{ String *string = &command->requests[0].live_file.dest; Editing_File *file; file = working_set_lookup_file(&vars->working_set, *string); if (file){ buffer_close(file); buffer_get_dummy(file, command->style); } } } internal void command_toggle_line_wrap(Command_Data *command){ Editing_Panel *panel = command->panel; if (panel->unwrapped_lines){ panel->unwrapped_lines = 0; panel->target_x = 0; panel->cursor = panel_compute_cursor_from_pos(panel, panel->cursor.pos); } else{ panel->unwrapped_lines = 1; panel->cursor = panel_compute_cursor_from_pos(panel, panel->cursor.pos); } } internal void command_toggle_endline_mode(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_File *file = panel->file; switch (file->endline_mode){ case ENDLINE_RN_COMBINED: { panel->cursor.pos = pos_adjust_to_left(panel->cursor.pos, panel->file->data); file->endline_mode = ENDLINE_RN_SEPARATE; panel->cursor = panel_compute_cursor_from_pos(panel, panel->cursor.pos); }break; case ENDLINE_RN_SEPARATE: { file->endline_mode = ENDLINE_RN_SHOWALLR; panel->cursor = panel_compute_cursor_from_pos(panel, panel->cursor.pos); }break; case ENDLINE_RN_SHOWALLR: { panel->cursor.pos = pos_adjust_to_self(panel->cursor.pos, panel->file->data, panel->file->size); file->endline_mode = ENDLINE_RN_COMBINED; panel->cursor = panel_compute_cursor_from_pos(panel, panel->cursor.pos); }break; } } internal void command_to_uppercase(Command_Data *command){ Editing_Panel *panel = command->panel; Range range = get_range(panel->cursor.pos, panel->mark); if (range.smaller < range.larger){ Editing_File *file = panel->file; u8 *data = file->data; for (i32 i = range.smaller; i < range.larger; ++i){ if (data[i] >= 'a' && data[i] <= 'z'){ data[i] += (u8)('A' - 'a'); } } // TODO(allen): RELEX POINT if (file->token_stack.tokens){ Cpp_File cpp_file; cpp_file.size = file->size; cpp_file.data = (u8*)file->data; cpp_relex_file(cpp_file, &file->token_stack, range.smaller, range.larger, 0); file->tokens_complete = 1; file->tokens_exist = 1; } } } internal void command_to_lowercase(Command_Data *command){ Editing_Panel *panel = command->panel; Range range = get_range(panel->cursor.pos, panel->mark); if (range.smaller < range.larger){ Editing_File *file = panel->file; u8 *data = file->data; for (i32 i = range.smaller; i < range.larger; ++i){ if (data[i] >= 'A' && data[i] <= 'Z'){ data[i] -= (u8)('A' - 'a'); } } // TODO(allen): RELEX POINT if (file->token_stack.tokens){ Cpp_File cpp_file; cpp_file.size = file->size; cpp_file.data = (u8*)file->data; cpp_relex_file(cpp_file, &file->token_stack, range.smaller, range.larger, 0); file->tokens_complete = 1; file->tokens_exist = 1; } } } internal void command_toggle_show_whitespace(Command_Data *command){ Editing_Panel *panel = command->panel; panel->show_whitespace = !panel->show_whitespace; } internal void command_clean_line(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_File *file = panel->file; u8 *data = (u8*)file->data; i32 pos = panel_find_beginning_of_line(panel, panel->cursor.pos); i32 last_hard_start = pos-1; i32 last_hard = last_hard_start; while (pos < file->size && data[pos] != '\n'){ if (!character_is_any_whitespace(data[pos])){ last_hard = pos; } ++pos; } if (last_hard != last_hard_start){ pos = pos_adjust_to_left(pos, data); if (last_hard + 1 < pos){ buffer_replace_range(file, last_hard+1, pos, 0, 0, REP_WHITESPACE); panel_measure_all_wrapped_y(panel); if (panel->cursor.pos > last_hard){ panel->cursor = panel_compute_cursor_from_pos(panel, last_hard + 1); } if (panel->mark > last_hard && panel->mark <= pos){ panel->mark = pos_adjust_to_self(last_hard+1, file->data, file->size); } else if (panel->mark > pos){ panel->mark -= pos - (last_hard + 1); } } } else{ panel_measure_all_wrapped_y(panel); panel_auto_tab(panel, pos, pos); } } internal void command_clean_all_lines(Command_Data *command){ Editing_Panel *panel = command->panel; Editing_File *file = panel->file; u8 *data = (u8*)file->data; i32 cursor_pos = panel->cursor.pos; i32 pos = 0; i32 last_hard = -1; bool32 is_all_white = 1; while (pos <= file->size){ if (pos == file->size || data[pos] == '\n'){ i32 line_pos = pos; if (pos < file->size && data[pos] == '\n'){ line_pos = pos_adjust_to_left(pos, data); } // TODO(allen): This should be optimized by either keeping track of the nesting level // in this funciton, or by at least having a nesting hint that is used and updated // every time an auto tab happens. Also auto tab should take a nesting hint. if (is_all_white){ panel_auto_tab(panel, pos, pos); } else{ if (last_hard + 1 < line_pos){ buffer_replace_range(file, last_hard+1, line_pos, 0, 0, REP_WHITESPACE); if (cursor_pos > last_hard && cursor_pos <= pos){ cursor_pos = pos_adjust_to_self(last_hard+1, file->data, file->size); } else if (cursor_pos > pos){ cursor_pos -= line_pos - (last_hard + 1); } if (panel->mark > last_hard && panel->mark <= pos){ panel->mark = pos_adjust_to_self(last_hard+1, file->data, file->size); } else if (panel->mark > pos){ panel->mark -= line_pos - (last_hard + 1); } pos -= line_pos - (last_hard + 1); } } last_hard = pos; is_all_white = 1; } else if (!character_is_any_whitespace(data[pos])){ last_hard = pos; is_all_white = 0; } ++pos; } panel_measure_all_wrapped_y(panel); panel->cursor = panel_compute_cursor_from_pos(panel, cursor_pos); } internal void command_eol_dosify(Command_Data *command){ Editing_Panel *panel = command->panel; panel_endline_convert(panel, ENDLINE_RN, ENDLINE_ERASE, ENDLINE_RN); panel_measure_all_wrapped_y(panel); } internal void command_eol_nixify(Command_Data *command){ Editing_Panel *panel = command->panel; panel_endline_convert(panel, ENDLINE_N, ENDLINE_ERASE, ENDLINE_N); panel_measure_all_wrapped_y(panel); } internal void command_auto_tab(Command_Data *command){ Editing_Panel *panel = command->panel; Range range = get_range(panel->cursor.pos, panel->mark); panel_auto_tab(panel, range.smaller, range.larger); panel_measure_all_wrapped_y(panel); } internal void setup_commands(Command_Map *commands, Key_Codes *codes){ commands->basic_mode_key[codes->left] = command_move_left; commands->basic_mode_key[codes->right] = command_move_right; commands->basic_mode_key[codes->del] = command_delete; commands->basic_mode_key[codes->back] = command_backspace; commands->basic_mode_key[codes->up] = command_move_up; commands->basic_mode_key[codes->down] = command_move_down; commands->basic_mode_key[codes->end] = command_seek_end_of_line; commands->basic_mode_key[codes->home] = command_seek_beginning_of_line; #if 0 commands->control_key[codes->right] = command_seek_token_right; commands->control_ascii['m'] = command_seek_token_right; commands->control_key[codes->left] = command_seek_token_left; commands->control_ascii['n'] = command_seek_token_left; #else commands->control_key[codes->right] = command_seek_whitespace_right; commands->control_ascii['m'] = command_seek_whitespace_right; commands->control_key[codes->left] = command_seek_whitespace_left; commands->control_ascii['n'] = command_seek_whitespace_left; #endif commands->control_key[codes->up] = command_seek_whitespace_up; commands->control_ascii['y'] = command_seek_whitespace_up; commands->control_key[codes->down] = command_seek_whitespace_down; commands->control_ascii['h'] = command_seek_whitespace_down; commands->control_ascii['\t'] = command_auto_tab; commands->control_ascii[' '] = command_set_mark; commands->control_ascii['c'] = command_copy; commands->control_ascii['x'] = command_cut; commands->control_ascii['v'] = command_paste; commands->control_ascii['V'] = command_paste_next; commands->control_ascii['d'] = command_delete_chunk; commands->control_ascii['p'] = command_open_panel; commands->control_ascii['P'] = command_close_panel; commands->control_ascii['o'] = command_interactive_open; commands->control_ascii['O'] = command_reopen; commands->control_ascii['s'] = command_save; commands->control_ascii['w'] = command_interactive_save_as; commands->control_ascii[','] = command_change_active_panel; commands->control_ascii['i'] = command_interactive_switch_file; commands->control_ascii['k'] = command_interactive_kill_file; commands->control_ascii['K'] = command_kill_file; commands->control_ascii['l'] = command_toggle_line_wrap; commands->control_ascii['L'] = command_toggle_endline_mode; commands->control_ascii['u'] = command_to_uppercase; commands->control_ascii['j'] = command_to_lowercase; commands->control_ascii['?'] = command_toggle_show_whitespace; commands->control_ascii['`'] = command_clean_line; commands->control_ascii['~'] = command_clean_all_lines; commands->control_ascii['1'] = command_eol_dosify; commands->control_ascii['!'] = command_eol_nixify; commands->control_ascii['f'] = command_begin_search_state; commands->control_ascii['r'] = command_begin_rsearch_state; } /* * Interactive Bar */ internal void intbar_draw_string(Render_Target *target, Interactive_Bar *bar, u8 *str, u32 char_color){ i32 char_w = font_get_character_width(bar->style.font); for (i32 i = 0; str[i]; ++i){ font_draw_glyph(target, bar->style.font, str[i], (real32)bar->pos_x, (real32)bar->pos_y, char_color); bar->pos_x += char_w; } } internal void intbar_draw_string(Render_Target *target, Interactive_Bar *bar, String str, u32 char_color){ i32 char_w = font_get_character_width(bar->style.font); for (i32 i = 0; i < str.size; ++i){ font_draw_glyph(target, bar->style.font, str.str[i], (real32)bar->pos_x, (real32)bar->pos_y, char_color); bar->pos_x += char_w; } } internal void hot_directory_draw_helper(Render_Target *target, Hot_Directory *hot_directory, Interactive_Bar *bar, String *string, bool32 include_files){ persist u8 str_open_bracket[] = " {"; persist u8 str_close_bracket[] = "}"; persist u8 str_comma[] = ", "; intbar_draw_string(target, bar, *string, bar->style.pop1_color); u8 front_name_space[256]; String front_name = make_string(front_name_space, 0, ArrayCount(front_name_space)); get_front_of_directory(&front_name, *string); intbar_draw_string(target, bar, str_open_bracket, bar->style.base_color); bool32 is_first_string = 1; File_List files = hot_directory->file_list; File_List_Iterator files_it = files_iterator_init(&files); while (files_it.filename_ptr && (include_files || files_it.folder_stage)){ u8 *filename = *files_it.filename_ptr; if (front_name.size == 0 || has_substr_unsensitive(filename, front_name)){ if (is_first_string){ is_first_string = 0; } else{ intbar_draw_string(target, bar, str_comma, bar->style.base_color); } if (files_it.folder_stage){ intbar_draw_string(target, bar, filename, bar->style.pop1_color); intbar_draw_string(target, bar, (u8*)"/", bar->style.pop1_color); } else{ intbar_draw_string(target, bar, filename, bar->style.base_color); } } files_iterator_step(&files, &files_it); } intbar_draw_string(target, bar, str_close_bracket, bar->style.base_color); } internal void live_file_draw_helper(Render_Target *target, Editing_Working_Set *working_set, Interactive_Bar *bar, String *string){ persist u8 str_open_bracket[] = " {"; persist u8 str_close_bracket[] = "}"; persist u8 str_comma[] = ", "; intbar_draw_string(target, bar, *string, bar->style.base_color); intbar_draw_string(target, bar, str_open_bracket, bar->style.base_color); bool32 is_first_string = 1; for (i32 file_i = 0; file_i < working_set->file_index_count; ++file_i){ Editing_File *file = &working_set->files[file_i]; if (file->live_name.str && (string->size == 0 || has_substr_unsensitive(file->live_name, *string))){ if (is_first_string){ is_first_string = 0; } else{ intbar_draw_string(target, bar, str_comma, bar->style.base_color); } intbar_draw_string(target, bar, file->live_name, bar->style.base_color); } } intbar_draw_string(target, bar, str_close_bracket, bar->style.base_color); } /* * App Functions */ internal bool32 app_init(Thread_Context *thread, Application_Memory *memory, Key_Codes *loose_codes, Clipboard_Contents clipboard){ Partition_Cursor partition = partition_open(memory->vars_memory, memory->vars_memory_size); App_Vars *vars = (App_Vars*) partition_allocate(&partition, sizeof(App_Vars)); Assert(vars); *vars = {}; u32 panel_max_count = vars->layout.panel_max_count = 4; u32 panel_count = vars->layout.panel_count = 1; Assert(panel_max_count >= 1); Editing_Panel *panels = vars->layout.panels = (Editing_Panel*) partition_allocate(&partition, sizeof(Editing_Panel)*panel_max_count); Assert(panels); // TODO(allen): improved Assert // NOTE(allen): command map setup setup_commands(&vars->map, loose_codes); // NOTE(allen): font setup if (font_init() != 0){ FatalError("Error initializing fonts"); return 0; } i32 memory_used = 0; if (font_load(&vars->font, 15, memory->font_memory, font_predict_size(15), &memory_used) != 0){ FatalError("Could not find any fonts"); } // NOTE(allen): file setup vars->working_set.file_index_count = 1; vars->working_set.file_max_count = 29; vars->working_set.files = (Editing_File*) partition_allocate(&partition, sizeof(Editing_File)*vars->working_set.file_max_count); buffer_get_dummy(&vars->working_set.files[0], &vars->style); // NOTE(allen): clipboard setup vars->working_set.clipboard_max_size = ArrayCount(vars->working_set.clipboards); vars->working_set.clipboard_size = 0; vars->working_set.clipboard_current = 0; vars->working_set.clipboard_rolling = 0; if (clipboard.str){ String *dest = working_set_next_clipboard_string(&vars->working_set, clipboard.size); copy(dest, make_string(clipboard.str, clipboard.size)); } // TODO(allen): more robust allocation solution for the clipboard? // NOTE(allen): style setup // TODO(allen): style_set_font function vars->style.font = &vars->font; vars->style.font_metrics.character_width = vars->font.glyphs[' '].advance; vars->style.font_metrics.line_skip = vars->font.line_skip; vars->style.back_color = 0xFF0C0C0C; vars->style.margin_color = 0xFF181818; vars->style.cursor_color = 0xFF00EE00; vars->style.highlight_color = 0xFFDDEE00; vars->style.mark_color = 0xFF494949; vars->style.default_color = 0xFF90B080; vars->style.at_cursor_color = vars->style.back_color; vars->style.at_highlight_color = 0xFFFF44DD; vars->style.comment_color = 0xFF2090F0; vars->style.keyword_color = 0xFFD08F20; vars->style.constant_color = 0xFF50FF30; vars->style.special_character_color = 0xFFFF0000; vars->style.use_paste_color = 1; vars->style.paste_color = 0xFFDDEE00; vars->style.highlight_junk_color = 0x44FF0000; vars->style.highlight_white_color = 0x1100FFFF; vars->style.tab_width = 4; vars->style.margin_width = 5; Interactive_Style file_info_style; file_info_style.font = &vars->font; file_info_style.bar_color = 0xFF888888; file_info_style.base_color = 0xFF000000; file_info_style.pop1_color = 0xFF4444AA; file_info_style.pop2_color = 0xFFFF0000; file_info_style.height = vars->style.font->line_skip + 2; vars->style.file_info_style = file_info_style; Interactive_Style command_style; command_style.font = &vars->font; command_style.bar_color = 0xFF0C0C0C; command_style.base_color = 0xFFDDDDBB; command_style.pop1_color = 0xFF4444AA; command_style.pop2_color = 0xFF44FF44; command_style.height = vars->style.font->line_skip + 2; vars->command_style = command_style; // NOTE(allen): panel setup AllowLocal(panel_count); panel_init(&panels[0], &vars->working_set.files[0]); // NOTE(allen): hot directory setup hot_directory_init(&vars->hot_directory); // NOTE(allen): request stack setup vars->request_count = 0; vars->request_filled = 0; vars->request_max = ArrayCount(vars->request_queue); return 1; } struct Single_Line_Input_Step{ bool32 hit_newline; bool32 hit_ctrl_newline; bool32 hit_a_character; bool32 hit_backspace; bool32 hit_esc; bool32 made_a_change; bool32 did_command; }; enum Single_Line_Input_Type{ SINGLE_LINE_STRING, SINGLE_LINE_FILE }; struct Single_Line_Mode{ Single_Line_Input_Type type; String *string; Hot_Directory *hot_directory; bool32 fast_folder_select; }; internal Single_Line_Input_Step app_single_line_input_core(Key_Codes *codes, Key_Input_Data *input, Single_Line_Mode mode){ Single_Line_Input_Step result = {}; if (input->has_press){ if (input->press.keycode == codes->back){ result.hit_backspace = 1; switch (mode.type){ case SINGLE_LINE_STRING: { // TODO(allen): Is this still okay? It looks like it's keeping a NULL terminator // which is at least unecessary given the new protocol. if (mode.string->size > 0){ --mode.string->size; mode.string->str[mode.string->size] = 0; result.made_a_change = 1; } }break; case SINGLE_LINE_FILE: { if (mode.string->size > 0){ --mode.string->size; i8 end_character = mode.string->str[mode.string->size]; if (character_is_slash(end_character)){ mode.string->size = reverse_seek_slash(*mode.string) + 1; // TODO(allen): Is this still okay? It looks like it's keeping a NULL terminator // which is at least unecessary given the new protocol. // TODO(allen): What to do when the string becomes empty though? mode.string->str[mode.string->size] = 0; hot_directory_set(mode.hot_directory, *mode.string); } else{ // TODO(allen): Is this still okay? It looks like it's keeping a NULL terminator // which is at least unecessary given the new protocol. mode.string->str[mode.string->size] = 0; } result.made_a_change = 1; } }break; } } else if (input->press.keycode == codes->newline){ if (input->control_keys[CONTROL_KEY_CONTROL]){ result.hit_ctrl_newline = 1; result.made_a_change = 1; } else{ result.made_a_change = 1; if (mode.fast_folder_select){ u8 front_name_space[256]; String front_name = make_string(front_name_space, 0, ArrayCount(front_name_space)); get_front_of_directory(&front_name, *mode.string); Hot_Directory_Match match; match = hot_directory_first_match(mode.hot_directory, front_name, 1, 1); if (!match.filename){ match = hot_directory_first_match(mode.hot_directory, front_name, 1, 0); } if (match.filename){ if (match.is_folder){ set_last_folder(mode.string, match.filename); hot_directory_set(mode.hot_directory, *mode.string); } else{ mode.string->size = reverse_seek_slash(*mode.string) + 1; append(mode.string, match.filename); result.hit_newline = 1; } } else{ result.hit_newline = 1; } } else{ result.hit_newline = 1; } } } else if (input->press.keycode == codes->esc){ result.hit_esc = 1; result.made_a_change = 1; } else if (input->press.character){ result.hit_a_character = 1; if (!input->control_keys[CONTROL_KEY_CONTROL]){ switch (mode.type){ case SINGLE_LINE_STRING: { if (mode.string->size+1 < mode.string->memory_size){ mode.string->str[mode.string->size] = (i8)input->press.character; mode.string->size++; // TODO(allen): More of this keeping a NULL terminator business... // I want out of this business for the String struct. mode.string->str[mode.string->size] = 0; result.made_a_change = 1; } }break; case SINGLE_LINE_FILE: { if (mode.string->size+1 < mode.string->memory_size){ i8 new_character = (i8)input->press.character; mode.string->str[mode.string->size] = new_character; mode.string->size++; // TODO(allen): More of this keeping a NULL terminator business... // I want out of this business for the String struct. mode.string->str[mode.string->size] = 0; if (character_is_slash(new_character)){ hot_directory_set(mode.hot_directory, *mode.string); } result.made_a_change = 1; } }break; } } else{ result.did_command = 1; result.made_a_change = 1; } } } return result; } inline internal Single_Line_Input_Step app_single_line_input_step(Key_Codes *codes, Key_Input_Data *input, String *string){ Single_Line_Mode mode = {}; mode.type = SINGLE_LINE_STRING; mode.string = string; return app_single_line_input_core(codes, input, mode); } inline internal Single_Line_Input_Step app_single_file_input_step(Key_Codes *codes, Key_Input_Data *input, String *string, Hot_Directory *hot_directory, bool32 fast_folder_select){ Single_Line_Mode mode = {}; mode.type = SINGLE_LINE_FILE; mode.string = string; mode.hot_directory = hot_directory; mode.fast_folder_select = fast_folder_select; return app_single_line_input_core(codes, input, mode); } inline internal Command_Function app_get_command(App_Vars *vars, Key_Input_Data *input, Key_Event_Data key){ Command_Function function = 0; if (input->control_keys[CONTROL_KEY_CONTROL]){ if (key.character_no_caps_lock){ function = vars->map.control_ascii[key.character_no_caps_lock]; } else{ function = vars->map.control_key[key.keycode]; } } else if (!input->control_keys[CONTROL_KEY_ALT]){ if (key.character != 0){ function = command_write_character; } else{ function = vars->map.basic_mode_key[key.keycode]; } } return function; } internal bool32 smooth_camera_step(real32 *target, real32 *current, real32 *vel, real32 S, real32 T){ bool32 result = 0; real32 targ = *target; real32 curr = *current; real32 v = *vel; if (curr != targ){ real32 L = lerp(curr, T, targ); i32 sign = (targ > curr) - (targ < curr); real32 V = curr + sign*v; if (sign > 0){ curr = Min(L, V); } else{ curr = Max(L, V); } if (curr == V){ v *= S; } if (curr > targ - .0001f && curr < targ + .0001f){ curr = targ; v = 1.f; } *target = targ; *current = curr; *vel = v; result = 1; } return result; } #if FRED_INTERNAL Application_Memory *GLOBAL; #endif internal Application_Step_Result app_step(Thread_Context *thread, Key_Codes *codes, Key_Input_Data *input, Mouse_State *mouse, bool32 time_step, Render_Target *target, Application_Memory *memory, Clipboard_Contents clipboard, bool32 first_step){ #if FRED_INTERNAL GLOBAL = memory; #endif ProfileStart(app_step); ProfileSection(thread, app_step, "start"); Application_Step_Result app_result = {}; app_result.redraw = 1; App_Vars *vars = (App_Vars*)memory->vars_memory; // TODO(allen): Let's find another way to do first_step // so we can get it out of app_step and the platform layer. if (first_step || !time_step){ app_result.redraw = 1; } Editing_Panel *panels = vars->layout.panels; Editing_Panel *active_panel = &panels[vars->layout.active_panel]; ProfileSection(thread, app_step, "setup"); // NOTE(allen): OS clipboard event handling if (clipboard.str){ String *dest = working_set_next_clipboard_string(&vars->working_set, clipboard.size); copy(dest, make_string(clipboard.str, clipboard.size)); } // NOTE(allen): check files are up to date for (i32 i = 0; i < vars->working_set.file_index_count; ++i){ Editing_File *file = vars->working_set.files + i; if (!file->is_dummy){ Time_Stamp time_stamp; time_stamp = system_file_time_stamp((u8*)make_c_str(file->source_path)); if (time_stamp.success){ file->last_sys_write_time = time_stamp.time; if (file->last_sys_write_time != file->last_4ed_write_time){ app_result.redraw = 1; } } } } ProfileSection(thread, app_step, "OS syncing"); // NOTE(allen): keyboard input handling if (time_step){ switch (vars->state){ case APP_STATE_EDIT: { Command_Data command_data; command_data.panel = active_panel; command_data.working_set = &vars->working_set; command_data.layout = &vars->layout; command_data.style = &vars->style; command_data.vars = vars; command_data.screen_width = target->width; command_data.screen_height = target->height; command_data.screen_y_off = vars->command_style.height; command_data.requests = 0; command_data.request_count = 0; command_data.request_filled = 0; if (vars->request_count == 0){ Command_Function function = 0; if (input->has_press){ command_data.key = input->press; function = app_get_command(vars, input, command_data.key); } else if (input->has_hold){ command_data.key = input->hold; function = app_get_command(vars, input, command_data.key); } if (function){ command_data.panel->next_mode = {}; function(&command_data); app_result.redraw = 1; command_data.panel->mode = command_data.panel->next_mode; } } else{ Input_Request *active_request = vars->request_queue + vars->request_filled; bool32 killed_command = 0; switch (active_request->type){ case REQUEST_SYS_FILE: { String *string = &active_request->sys_file.dest; Single_Line_Input_Step result = app_single_file_input_step(codes, input, string, &vars->hot_directory, 1); if (result.made_a_change){ app_result.redraw = 1; } if (result.hit_ctrl_newline){ active_request->sys_file.hit_ctrl_newline = 1; } if (result.hit_newline || result.hit_ctrl_newline){ ++vars->request_filled; } if (result.hit_esc){ app_clear_request_queue(vars); killed_command = 1; } }break; case REQUEST_LIVE_FILE: { String *string = &active_request->live_file.dest; Single_Line_Input_Step result = app_single_line_input_step(codes, input, string); if (result.made_a_change){ app_result.redraw = 1; } if (result.hit_ctrl_newline){ active_request->live_file.hit_ctrl_newline = 1; } if (result.hit_newline || result.hit_ctrl_newline){ ++vars->request_filled; } if (result.hit_esc){ app_clear_request_queue(vars); killed_command = 1; } }break; } if (vars->request_filled == vars->request_count && !killed_command){ command_data.requests = vars->request_queue; command_data.request_count = vars->request_count; command_data.request_filled = vars->request_filled; vars->pending_command(&command_data); app_clear_request_queue(vars); } } }break; case APP_STATE_SEARCH: { String *string = &vars->isearch.str; Single_Line_Input_Step result = app_single_line_input_step(codes, input, string); if (result.made_a_change){ app_result.redraw = 1; Editing_File *file = active_panel->file; bool32 step_forward = 0; bool32 step_backward = 0; if (input->has_press){ Key_Event_Data key = input->press; Command_Function function = app_get_command(vars, input, key); if (function == command_begin_search_state){ step_forward = 1; } if (function == command_begin_rsearch_state){ step_backward = 1; } } if (!step_forward && !step_backward && !input->has_press && input->has_hold){ Key_Event_Data key = input->hold; Command_Function function = app_get_command(vars, input, key); if (function == command_begin_search_state){ step_forward = 1; } if (function == command_begin_rsearch_state){ step_backward = 1; } } i32 start_pos = vars->isearch.pos; if (step_forward){ if (vars->isearch.reverse){ start_pos = active_panel->temp_highlight.pos - 1; vars->isearch.pos = start_pos; vars->isearch.reverse = 0; } } if (step_backward){ if (!vars->isearch.reverse){ start_pos = active_panel->temp_highlight.pos + 1; vars->isearch.pos = start_pos; vars->isearch.reverse = 1; } } String file_string = make_string(file->data, file->size); i32 pos; if (vars->isearch.reverse){ if (result.hit_backspace){ start_pos = active_panel->temp_highlight.pos + 1; vars->isearch.pos = start_pos; } else{ pos = rfind_substr(file_string, start_pos - 1, *string); if (pos >= 0){ if (step_backward){ vars->isearch.pos = pos; start_pos = pos; pos = rfind_substr(file_string, start_pos - 1, *string); if (pos == -1){ pos = start_pos; } } panel_set_temp_highlight(active_panel, pos, pos+string->size); } } } else{ if (result.hit_backspace){ start_pos = active_panel->temp_highlight.pos - 1; vars->isearch.pos = start_pos; } else{ pos = find_substr(file_string, start_pos + 1, *string); if (pos < file->size){ if (step_forward){ vars->isearch.pos = pos; start_pos = pos; pos = find_substr(file_string, start_pos + 1, *string); if (pos == file->size){ pos = start_pos; } } panel_set_temp_highlight(active_panel, pos, pos+string->size); } } } } if (result.hit_newline || result.hit_ctrl_newline){ panel_cursor_move(active_panel, active_panel->temp_highlight); system_free_memory(vars->isearch.str.str); vars->state = APP_STATE_EDIT; } if (result.hit_esc){ active_panel->show_temp_highlight = 0; system_free_memory(vars->isearch.str.str); vars->state = APP_STATE_EDIT; } }break; case APP_STATE_RESIZING: { if (input->has_press){ vars->state = APP_STATE_EDIT; } }break; } } ProfileSection(thread, app_step, "keyboard input"); // NOTE(allen): reorganizing panels on screen i32 prev_width = vars->layout.full_width; i32 prev_height = vars->layout.full_height; i32 full_width = vars->layout.full_width = target->width; i32 full_height = vars->layout.full_height = target->height; if (prev_width != full_width || prev_height != full_height){ layout_refit(&vars->layout, 0, vars->command_style.height, full_width, full_height, prev_width, prev_height); for (i32 panel_i = 0; panel_i < vars->layout.panel_count && vars->layout.panel_count > 1; ++panel_i){ Editing_Panel *panel = &panels[panel_i]; Editing_Style *style = panel->file->style; i32 character_w = style_get_character_width(style); i32 margin_width = style->margin_width; if (panel->full_w < character_w*6 + margin_width*2){ i32 share_w = panel->full_w; layout_close_panel(&vars->layout, panel_i); layout_redistribute_width(&vars->layout, share_w); panel_i = 0; } } for (i32 panel_i = 0; panel_i < vars->layout.panel_count; ++panel_i){ Editing_Panel *panel = panels + panel_i; if (!panel->file->is_dummy){ panel->cursor = panel_compute_cursor_from_pos(panel, panel->cursor.pos); } } app_result.redraw = 1; } ProfileSection(thread, app_step, "reorganizing panels"); // NOTE(allen): mouse input handling if (time_step && !mouse->out_of_window){ i32 mx = mouse->x; i32 my = mouse->y; bool32 mouse_press_event = 0; if (mouse->left_button && !mouse->left_button_prev){ // TODO(allen): // This means any mouse use will be impossible, I guess it will have // to depend on the type of the request seen at the top??? app_clear_request_queue(vars); mouse_press_event = 1; switch (vars->state){ case APP_STATE_SEARCH: { active_panel->show_temp_highlight = 0; system_free_memory(vars->isearch.str.str); vars->state = APP_STATE_EDIT; }break; } } switch (vars->state){ case APP_STATE_EDIT: { for (i32 panel_i = 0; panel_i < vars->layout.panel_count; ++panel_i){ Editing_Panel *panel = &panels[panel_i]; if (mx >= panel->x && mx < panel->w + panel->x && my >= panel->y && my < panel->h + panel->y){ app_result.mouse_cursor_type = APP_MOUSE_CURSOR_IBEAM; if (mouse_press_event){ if (!panel->file->is_dummy){ app_result.redraw = 1; Font *font = &vars->font; i32 character_w = style_get_character_width(panel->file->style); i32 character_h = font->line_skip; i32 max_line_length = panel_compute_max_line_length(panel); i32 max_lines = panel_compute_max_lines(panel); real32 grid_x = ((real32)mx - panel->x) / character_w; real32 grid_y = ((real32)my - panel->y) / character_h; vars->last_click_x = grid_x; vars->last_click_y = grid_y; if (grid_x >= 0 && grid_x <= max_line_length && grid_y >= 0 && grid_y < max_lines){ i32 pos_x = (i32)(grid_x + active_panel->scroll_x); i32 pos_y = (i32)(grid_y + active_panel->scroll_y); panel_cursor_move(active_panel, pos_x, pos_y); } } vars->layout.active_panel = panel_i; active_panel = panel; } else{ // NOTE(allen): mouse inside editing area but no click } } else if (mx >= panel->full_x && mx < panel->full_w + panel->full_x && my >= panel->full_y && my < panel->full_h + panel->full_y){ // NOTE(allen): not inside the editing area but within the margins bool32 resize_area = 0; app_result.mouse_cursor_type = APP_MOUSE_CURSOR_ARROW; if (mx >= (panel->full_x+panel->full_w+panel->x+panel->w)/2){ if (panel_i != vars->layout.panel_count-1){ app_result.mouse_cursor_type = APP_MOUSE_CURSOR_LEFTRIGHT; resize_area = 1; } } else if (mx <= (panel->full_x+panel->x)/2){ if (panel_i != 0){ app_result.mouse_cursor_type = APP_MOUSE_CURSOR_LEFTRIGHT; resize_area = -1; } } if (resize_area != 0 && mouse_press_event){ vars->state = APP_STATE_RESIZING; if (resize_area == 1){ vars->resizing.left = panel; vars->resizing.right = panel+1; } else if (resize_area == -1){ vars->resizing.left = panel-1; vars->resizing.right = panel; } } } } i32 cursor_y = panel_get_cursor_y(active_panel); real32 target_y = active_panel->target_y; i32 max_lines = panel_compute_max_lines(active_panel); bool32 wheel_used; real32 delta_target_y = Max(1, max_lines / 3.f); delta_target_y *= -mouse->wheel; target_y += delta_target_y; if (target_y < 0){ target_y = 0; } if (mouse->wheel == 0){ wheel_used = 0; } else{ wheel_used = 1; if (cursor_y >= target_y + max_lines){ cursor_y = (i32)target_y + max_lines - 1; } if (cursor_y < target_y){ cursor_y = (i32)target_y + 1; } } active_panel->target_y = target_y; if (cursor_y != panel_get_cursor_y(active_panel)){ active_panel->cursor = panel_compute_cursor_from_xy(active_panel, active_panel->preferred_x, cursor_y); } if (wheel_used){ app_result.redraw = 1; } }break; case APP_STATE_RESIZING: { if (mouse->left_button){ Editing_Panel *left = vars->resizing.left; Editing_Panel *right = vars->resizing.right; i32 left_x = left->full_x; i32 left_w = left->full_w; i32 right_x = right->full_x; i32 right_w = right->full_w; AllowLocal(right_x); i32 new_left_x = left_x; i32 new_left_w = mx - left_x; i32 new_right_w = right_w - (new_left_w - left_w); i32 new_right_x = mx; if (left_w != new_left_w){ app_result.redraw = 1; Editing_Style *left_style = left->file->style; Editing_Style *right_style = right->file->style; i32 left_character_w = style_get_character_width(left_style); i32 right_character_w = style_get_character_width(right_style); i32 left_margin_width = left_style->margin_width; i32 right_margin_width = right_style->margin_width; if (new_left_w > left_margin_width*2 + left_character_w*6 && new_right_w > right_margin_width*2 + right_character_w*6){ left->full_x = new_left_x; left->full_w = new_left_w; right->full_x = new_right_x; right->full_w = new_right_w; panel_fix_internal_area(left); panel_fix_internal_area(right); } } } else{ app_result.redraw = 1; vars->state = APP_STATE_EDIT; } }break; } } ProfileSection(thread, app_step, "mouse input"); // NOTE(allen): fix scrolling on all panels for (i32 panel_i = 0; panel_i < vars->layout.panel_count; ++panel_i){ Editing_Panel *panel = &panels[panel_i]; i32 cursor_y; if (panel->show_temp_highlight){ if (panel->unwrapped_lines){ cursor_y = panel->temp_highlight.unwrapped_y; } else{ cursor_y = panel->temp_highlight.wrapped_y; } } else{ if (panel->unwrapped_lines){ cursor_y = panel->cursor.unwrapped_y; } else{ cursor_y = panel->cursor.wrapped_y; } } real32 target_y = panel->target_y; real32 original_target_y = target_y; i32 max_lines = panel_compute_max_lines(panel); while (cursor_y >= Floor(target_y) + max_lines){ target_y += 3.f; } while (cursor_y < target_y){ target_y -= 3.f; } if (target_y < 0){ target_y = 0; } panel->target_y = target_y; i32 cursor_x = panel_get_cursor_x(panel); real32 target_x = panel->target_x; real32 original_target_x = target_x; i32 max_x = panel_compute_max_line_length(panel); if (cursor_x < target_x){ target_x = (real32)Max(0, cursor_x - max_x/2); } else if (cursor_x >= target_x + max_x){ target_x = (real32)(cursor_x - max_x/2); } panel->target_x = target_x; if (original_target_y != panel->target_y || original_target_x != panel->target_x){ app_result.redraw; } } ProfileSection(thread, app_step, "fix scrolling"); // NOTE(allen): execute animations for (i32 i = 0; i < vars->layout.panel_count; ++i){ Editing_Panel *panel = vars->layout.panels + i; // TODO(allen): Scrolling parameterization in style? if (smooth_camera_step(&panel->target_y, &panel->scroll_y, &panel->vel_y, 2.f, 1.f/9.f)){ app_result.redraw = 1; } if (smooth_camera_step(&panel->target_x, &panel->scroll_x, &panel->vel_x, 2.f, 1.f/6.f)){ app_result.redraw = 1; } if (panel->paste_effect.tick_down > 0){ --panel->paste_effect.tick_down; app_result.redraw = 1; } } ProfileSection(thread, app_step, "execute animations"); if (app_result.redraw){ // NOTE(allen): render the panels for (i32 panel_i = 0; panel_i < vars->layout.panel_count; ++panel_i){ Editing_Panel *panel = &panels[panel_i]; Editing_Style *style = panel->file->style; i32 full_left = panel->full_x; i32 full_top = panel->full_y; i32 full_right = full_left + panel->full_w; i32 full_bottom = full_top + panel->full_h; u32 back_color = style->back_color; draw_rectangle_2corner(target, full_left, full_top, full_right, full_bottom, back_color); u32 side_margin_color = style->margin_color; panel_draw(thread, target, panel, vars->layout.active_panel == panel_i); // NOTE(allen): file info bar { Interactive_Bar bar; bar.style = style->file_info_style; bar.pos_x = panel->x; bar.pos_y = full_top; draw_rectangle(target, full_left, bar.pos_y, panel->full_w, bar.style.height, bar.style.bar_color); Editing_File *file = panel->file; if (!file->is_dummy){ intbar_draw_string(target, &bar, panel->file->live_name, bar.style.base_color); intbar_draw_string(target, &bar, make_lit_string(" - "), bar.style.base_color); u8 line_number_space[30]; String line_number = make_string(line_number_space, 0, 30); append(&line_number, (u8*)"L#"); append_int_to_str(panel->cursor.line, &line_number); intbar_draw_string(target, &bar, line_number, bar.style.base_color); if (file->last_4ed_write_time != file->last_sys_write_time){ persist String out_of_sync = make_lit_string(" FILE SYNC"); intbar_draw_string(target, &bar, out_of_sync, bar.style.pop2_color); } } } // L draw_rectangle_2corner(target, full_left, panel->y, panel->x, full_bottom, side_margin_color); // R draw_rectangle_2corner(target, panel->x + panel->w, panel->y, full_right, full_bottom, side_margin_color); // B draw_rectangle_2corner(target, full_left, panel->y + panel->h, full_right, full_bottom, side_margin_color); if (panel_i != 0){ draw_rectangle_2corner(target, panel->full_x-1, panel->full_y, panel->full_x+1, panel->full_y+panel->full_h, 0xFFFFFFFF); } } ProfileSection(thread, app_step, "render files"); // NOTE (allen): command bar { Interactive_Bar bar; bar.style = vars->command_style; bar.pos_x = 0; bar.pos_y = 0; draw_rectangle(target, 0, 0, target->width, bar.style.height, bar.style.bar_color); switch (vars->state){ case APP_STATE_EDIT: { if (vars->request_count > 0){ Input_Request *request = vars->request_queue + vars->request_filled; switch (request->type){ case REQUEST_SYS_FILE: { intbar_draw_string(target, &bar, request->sys_file.query, bar.style.pop2_color); String *string = &request->sys_file.dest; hot_directory_draw_helper(target, &vars->hot_directory, &bar, string, 1); }break; case REQUEST_LIVE_FILE: { intbar_draw_string(target, &bar, request->sys_file.query, bar.style.pop2_color); String *string = &request->sys_file.dest; live_file_draw_helper(target, &vars->working_set, &bar, string); }break; } } }break; case APP_STATE_SEARCH: { persist String search_str = make_lit_string("I-Search: "); persist String rsearch_str = make_lit_string("Reverse-I-Search: "); if (vars->isearch.reverse){ intbar_draw_string(target, &bar, rsearch_str, bar.style.pop2_color); } else{ intbar_draw_string(target, &bar, search_str, bar.style.pop2_color); } intbar_draw_string(target, &bar, vars->isearch.str, bar.style.base_color); }break; case APP_STATE_RESIZING: { intbar_draw_string(target, &bar, make_lit_string("Resizing!"), bar.style.pop2_color); }break; } } ProfileSection(thread, app_step, "interaction bar"); } ProfileEnd(thread, app_step, "total"); return app_result; } // BOTTOM E d i t i n g _ P a n e l * p a n e l = c o m m a n d - > p a n e l