diff options
-rw-r--r-- | buffer.c | 425 | ||||
-rw-r--r-- | main.c | 77 | ||||
-rw-r--r-- | math.c | 8 |
3 files changed, 299 insertions, 211 deletions
@@ -26,6 +26,8 @@ typedef struct { double scroll_x, scroll_y; // number of characters scrolled in the x/y direction Font *font; BufferPos cursor_pos; + BufferPos selection_pos; // if selection is true, the text between selection_pos and cursor_pos is selected. + bool selection; u8 tab_width; bool store_undo_events; // set to false to disable undo events float x1, y1, x2, y2; @@ -39,24 +41,6 @@ typedef struct { -// for debugging -#if DEBUG -static void buffer_pos_check_valid(TextBuffer *buffer, BufferPos p) { - assert(p.line < buffer->nlines); - assert(p.index <= buffer->lines[p.line].len); -} - -// perform a series of checks to make sure the buffer doesn't have any invalid values -static void buffer_check_valid(TextBuffer *buffer) { - assert(buffer->nlines); - buffer_pos_check_valid(buffer, buffer->cursor_pos); -} -#else -static void buffer_check_valid(TextBuffer *buffer) { - (void)buffer; -} -#endif - void buffer_create(TextBuffer *buffer, Font *font) { util_zero_memory(buffer, sizeof *buffer); buffer->font = font; @@ -695,19 +679,21 @@ void buffer_scroll(TextBuffer *buffer, double dx, double dy) { buffer_correct_scroll(buffer); } -// sets *x and *y to the position of the character at the given position in the buffer. -// x/y can be NULL. -void buffer_pos_to_pixels(TextBuffer *buffer, BufferPos pos, float *x, float *y) { +// returns the position of the character at the given position in the buffer. +v2 buffer_pos_to_pixels(TextBuffer *buffer, BufferPos pos) { u32 line = pos.line, index = pos.index; - u32 col = buffer_index_to_column(buffer, line, index); // we need to convert the index to a column - if (x) *x = (float)((double)col - buffer->scroll_x) * text_font_char_width(buffer->font) + buffer->x1; - if (y) *y = (float)((double)line - buffer->scroll_y) * text_font_char_height(buffer->font) + buffer->y1; + u32 col = buffer_index_to_column(buffer, line, index); + float x = (float)((double)col - buffer->scroll_x) * text_font_char_width(buffer->font) + buffer->x1; + float y = (float)((double)line - buffer->scroll_y) * text_font_char_height(buffer->font) + buffer->y1 + + text_font_char_height(buffer->font) * 0.2f; // slightly nudge + return V2(x, y); } // convert pixel coordinates to a position in the buffer, selecting the closest character. // returns false if the position is not inside the buffer. -bool buffer_pixels_to_pos(TextBuffer *buffer, float x, float y, BufferPos *pos) { +bool buffer_pixels_to_pos(TextBuffer *buffer, v2 pixel_coords, BufferPos *pos) { + float x = pixel_coords.x, y = pixel_coords.y; pos->line = pos->index = 0; x -= buffer->x1; @@ -731,161 +717,21 @@ bool buffer_pixels_to_pos(TextBuffer *buffer, float x, float y, BufferPos *pos) } // clip the rectangle so it's all inside the buffer. returns true if there's any rectangle left. -static bool buffer_clip_rect(TextBuffer *buffer, float *x1, float *y1, float *x2, float *y2) { - if (*x1 > buffer->x2 || *y1 > buffer->y2 || *x2 < buffer->x1 || *y2 < buffer->y1) { - *x1 = *y1 = *x2 = *y2 = 0; +static bool buffer_clip_rect(TextBuffer *buffer, Rect *r) { + float x1, y1, x2, y2; + rect_coords(*r, &x1, &y1, &x2, &y2); + if (x1 > buffer->x2 || y1 > buffer->y2 || x2 < buffer->x1 || y2 < buffer->y1) { + r->pos = r->size = V2(0, 0); return false; } - if (*x1 < buffer->x1) *x1 = buffer->x1; - if (*y1 < buffer->y1) *y1 = buffer->y1; - if (*x2 > buffer->x2) *x2 = buffer->x2; - if (*y2 > buffer->y2) *y2 = buffer->y2; + if (x1 < buffer->x1) x1 = buffer->x1; + if (y1 < buffer->y1) y1 = buffer->y1; + if (x2 > buffer->x2) x2 = buffer->x2; + if (y2 > buffer->y2) y2 = buffer->y2; + *r = rect4(x1, y1, x2, y2); return true; } -// Render the text buffer in the given rectangle -// NOTE: also corrects scroll -void buffer_render(TextBuffer *buffer, float x1, float y1, float x2, float y2) { - Font *font = buffer->font; - u32 nlines = buffer->nlines; - Line *lines = buffer->lines; - float char_width = text_font_char_width(font), - char_height = text_font_char_height(font); - float header_height = char_height; - - // get screen coordinates of cursor - float cur_x1 = 0, cur_y1 = 0; - buffer_pos_to_pixels(buffer, buffer->cursor_pos, &cur_x1, &cur_y1); - cur_y1 += 0.2f * char_height; - - u32 border_color = 0x7f7f7fff; // color of border around buffer - - // bounding box around buffer & header - gl_color_rgba(border_color); - glBegin(GL_LINE_STRIP); - glVertex2f(x1,y1); - glVertex2f(x1,y2); - glVertex2f(x2,y2); - glVertex2f(x2,y1); - glVertex2f(x1-1,y1); - glEnd(); - - TextRenderState text_state = { - .x = 0, .y = 0, - .min_x = x1, .min_y = y1, - .max_x = x2, .max_y = y2 - }; - - - { // header - glColor3f(1,1,1); - float x = x1, y = y1 + char_height * 0.8f; - text_render_with_state(font, &text_state, buffer->filename, x, y); - #if DEBUG - // show checksum - char checksum[32] = {0}; - snprintf(checksum, sizeof checksum - 1, "%08llx", (ullong)buffer_checksum(buffer)); - gl_color1f(0.5f); - float checksum_w = 0; - text_get_size(font, checksum, &checksum_w, NULL); - x = x2 - checksum_w; - text_render_with_state(font, &text_state, checksum, x, y); - #endif - } - - y1 += header_height; - - buffer->x1 = x1; buffer->y1 = y1; buffer->x2 = x2; buffer->y2 = y2; - // line separating header from buffer proper - glBegin(GL_LINES); - gl_color_rgba(border_color); - glVertex2f(x1, y1); - glVertex2f(x2, y1); - glEnd(); - - - // highlight line cursor is on - { - gl_color1f(0.2f); // @SETTINGS - glBegin(GL_QUADS); - float line_y1 = cur_y1; - float line_y2 = cur_y1 + char_height; - glVertex2f(x1, line_y1); - glVertex2f(x2, line_y1); - glVertex2f(x2, line_y2); - glVertex2f(x1, line_y2); - glEnd(); - } - - - glColor3f(1,1,1); - text_chars_begin(font); - - - // what x coordinate to start rendering the text from - float render_start_x = x1 - (float)buffer->scroll_x * char_width; - - text_state = (TextRenderState){ - .x = render_start_x, .y = y1 + text_font_char_height(font), - .min_x = x1, .min_y = y1, - .max_x = x2, .max_y = y2 - }; - - u32 column = 0; - - u32 start_line = (u32)buffer->scroll_y; // line to start rendering from - text_state.y -= (float)(buffer->scroll_y - start_line) * char_height; - - //debug_print("Rendering from " U32_FMT, start_line); - - for (u32 line_idx = start_line; line_idx < nlines; ++line_idx) { - Line *line = &lines[line_idx]; - for (char32_t *p = line->str, *end = p + line->len; p != end; ++p) { - char32_t c = *p; - - switch (c) { - case U'\n': assert(0); - case U'\r': break; // for CRLF line endings - case U'\t': - do { - text_render_char(font, &text_state, U' '); - ++column; - } while (column % buffer->tab_width); - break; - default: - text_render_char(font, &text_state, c); - ++column; - break; - } - } - - // next line - text_state.x = render_start_x; - if (text_state.y > text_state.max_y) { - // made it to the bottom of the buffer view. - //debug_println(" to " U32_FMT ".", line_idx); - break; - } - text_state.y += text_font_char_height(font); - column = 0; - } - - - text_chars_end(font); - - { // render cursor - float cur_x2 = cur_x1 + 1.0f, cur_y2 = cur_y1 + char_height; - if (buffer_clip_rect(buffer, &cur_x1, &cur_y1, &cur_x2, &cur_y2)) { - glColor3f(0,1,1); - glBegin(GL_QUADS); - glVertex2f(cur_x1,cur_y1); - glVertex2f(cur_x2,cur_y1); - glVertex2f(cur_x2,cur_y2); - glVertex2f(cur_x1,cur_y2); - glEnd(); - } - } -} // if the cursor is offscreen, this will scroll to make it onscreen. static void buffer_scroll_to_cursor(TextBuffer *buffer) { @@ -1279,6 +1125,35 @@ void buffer_insert_utf8_at_cursor(TextBuffer *buffer, char const *utf8) { } } +// like shift+right in most editors, move cursor n chars to the right, selecting everything in between +void buffer_select_right(TextBuffer *buffer, i64 n) { + if (!buffer->selection) + buffer->selection_pos = buffer->cursor_pos; + if (buffer_cursor_move_right(buffer, n)) // if we actually moved at all + buffer->selection = true; +} + +void buffer_select_left(TextBuffer *buffer, i64 n) { + if (!buffer->selection) + buffer->selection_pos = buffer->cursor_pos; + if (buffer_cursor_move_left(buffer, n)) + buffer->selection = true; +} + +void buffer_select_down(TextBuffer *buffer, i64 n) { + if (!buffer->selection) + buffer->selection_pos = buffer->cursor_pos; + if (buffer_cursor_move_down(buffer, n)) + buffer->selection = true; +} + +void buffer_select_up(TextBuffer *buffer, i64 n) { + if (!buffer->selection) + buffer->selection_pos = buffer->cursor_pos; + if (buffer_cursor_move_up(buffer, n)) + buffer->selection = true; +} + static void buffer_shorten_line(Line *line, u32 new_len) { assert(line->len >= new_len); line->len = new_len; // @OPTIMIZE(memory): decrease line capacity @@ -1538,3 +1413,203 @@ void buffer_redo(TextBuffer *buffer, i64 ntimes) { } } +// for debugging +#if DEBUG +static void buffer_pos_check_valid(TextBuffer *buffer, BufferPos p) { + assert(p.line < buffer->nlines); + assert(p.index <= buffer->lines[p.line].len); +} + +// perform a series of checks to make sure the buffer doesn't have any invalid values +void buffer_check_valid(TextBuffer *buffer) { + assert(buffer->nlines); + buffer_pos_check_valid(buffer, buffer->cursor_pos); + if (buffer->selection) { + buffer_pos_check_valid(buffer, buffer->selection_pos); + // you shouldn't be able to select nothing + assert(buffer_pos_cmp(buffer->cursor_pos, buffer->selection_pos) != 0); + } +} +#else +void buffer_check_valid(TextBuffer *buffer) { + (void)buffer; +} +#endif + +// Render the text buffer in the given rectangle +void buffer_render(TextBuffer *buffer, float x1, float y1, float x2, float y2) { + Font *font = buffer->font; + u32 nlines = buffer->nlines; + Line *lines = buffer->lines; + float char_width = text_font_char_width(font), + char_height = text_font_char_height(font); + float header_height = char_height; + + // get screen coordinates of cursor + v2 cursor_display_pos = buffer_pos_to_pixels(buffer, buffer->cursor_pos); + // the rectangle that the cursor is rendered as + Rect cursor_rect = rect(cursor_display_pos, V2(1.0f, char_height)); // @SETTINGS: cursor width + + u32 border_color = 0x7f7f7fff; // color of border around buffer + + // bounding box around buffer & header + gl_color_rgba(border_color); + glBegin(GL_LINE_STRIP); + glVertex2f(x1,y1); + glVertex2f(x1,y2); + glVertex2f(x2,y2); + glVertex2f(x2,y1); + glVertex2f(x1-1,y1); + glEnd(); + + TextRenderState text_state = { + .x = 0, .y = 0, + .min_x = x1, .min_y = y1, + .max_x = x2, .max_y = y2 + }; + + + { // header + glColor3f(1,1,1); + float x = x1, y = y1 + char_height * 0.8f; + text_render_with_state(font, &text_state, buffer->filename, x, y); + #if DEBUG + // show checksum + char checksum[32] = {0}; + snprintf(checksum, sizeof checksum - 1, "%08llx", (ullong)buffer_checksum(buffer)); + gl_color1f(0.5f); + float checksum_w = 0; + text_get_size(font, checksum, &checksum_w, NULL); + x = x2 - checksum_w; + text_render_with_state(font, &text_state, checksum, x, y); + #endif + } + + y1 += header_height; + + buffer->x1 = x1; buffer->y1 = y1; buffer->x2 = x2; buffer->y2 = y2; + // line separating header from buffer proper + glBegin(GL_LINES); + gl_color_rgba(border_color); + glVertex2f(x1, y1); + glVertex2f(x2, y1); + glEnd(); + + + // highlight line cursor is on + { + gl_color1f(0.15f); // @SETTINGS + glBegin(GL_QUADS); + Rect hl_rect = rect(V2(x1, cursor_display_pos.y), V2(x2-x1-1, char_height)); + buffer_clip_rect(buffer, &hl_rect); + rect_render(hl_rect); + glEnd(); + } + + + // what x coordinate to start rendering the text from + float render_start_x = x1 - (float)buffer->scroll_x * char_width; + u32 column = 0; + + u32 start_line = (u32)buffer->scroll_y; // line to start rendering from + //debug_print("Rendering from " U32_FMT, start_line); + + if (buffer->selection) { // draw selection + glBegin(GL_QUADS); + gl_color_rgba(0x3366aa88); // @SETTINGS + BufferPos sel_start = {0}, sel_end = {0}; + int cmp = buffer_pos_cmp(buffer->cursor_pos, buffer->selection_pos); + if (cmp < 0) { + // cursor_pos comes first + sel_start = buffer->cursor_pos; + sel_end = buffer->selection_pos; + } else if (cmp > 0) { + // selection_pos comes first + sel_end = buffer->cursor_pos; + sel_start = buffer->selection_pos; + } else assert(0); + + u32 index1 = sel_start.index; + for (u32 line_idx = maxu32(sel_start.line, start_line); line_idx <= sel_end.line; ++line_idx) { + Line *line = &buffer->lines[line_idx]; + u32 index2 = line_idx == sel_end.line ? sel_end.index : line->len; + assert(index2 >= index1); + + // highlight everything from index1 to index2 + u32 n_columns_highlighted = buffer_index_to_column(buffer, line_idx, index2) + - buffer_index_to_column(buffer, line_idx, index1); + + if (n_columns_highlighted) { + BufferPos p1 = {.line = line_idx, .index = index1}; + v2 hl_p1 = buffer_pos_to_pixels(buffer, p1); + Rect hl_rect = rect( + hl_p1, + V2((float)n_columns_highlighted * char_width, char_height) + ); + buffer_clip_rect(buffer, &hl_rect); + rect_render(hl_rect); + } + index1 = 0; + } + + glEnd(); + } + + + text_chars_begin(font); + glColor3f(1,1,1); + + text_state = (TextRenderState){ + .x = render_start_x, .y = y1 + text_font_char_height(font), + .min_x = x1, .min_y = y1, + .max_x = x2, .max_y = y2 + }; + + text_state.y -= (float)(buffer->scroll_y - start_line) * char_height; + + + for (u32 line_idx = start_line; line_idx < nlines; ++line_idx) { + Line *line = &lines[line_idx]; + for (char32_t *p = line->str, *end = p + line->len; p != end; ++p) { + char32_t c = *p; + + switch (c) { + case U'\n': assert(0); + case U'\r': break; // for CRLF line endings + case U'\t': + do { + text_render_char(font, &text_state, U' '); + ++column; + } while (column % buffer->tab_width); + break; + default: + text_render_char(font, &text_state, c); + ++column; + break; + } + } + + // next line + text_state.x = render_start_x; + if (text_state.y > text_state.max_y) { + // made it to the bottom of the buffer view. + //debug_println(" to " U32_FMT ".", line_idx); + break; + } + text_state.y += text_font_char_height(font); + column = 0; + } + + + text_chars_end(font); + + { // render cursor + if (buffer_clip_rect(buffer, &cursor_rect)) { + glColor3f(0,1,1); + glBegin(GL_QUADS); + rect_render(cursor_rect); + glEnd(); + } + } +} + @@ -74,10 +74,11 @@ int main(void) { bool quit = false; TextBuffer text_buffer; - buffer_create(&text_buffer, font); + TextBuffer *buffer = &text_buffer; + buffer_create(buffer, font); - if (!buffer_load_file(&text_buffer, "buffer.c")) - die("Error loading file: %s", buffer_geterr(&text_buffer)); + if (!buffer_load_file(buffer, "buffer.c")) + die("Error loading file: %s", buffer_geterr(buffer)); Uint32 time_at_last_frame = SDL_GetTicks(); @@ -102,74 +103,78 @@ int main(void) { // scroll with mouse wheel Sint32 dx = event.wheel.x, dy = -event.wheel.y; double scroll_speed = 2.5; - buffer_scroll(&text_buffer, dx * scroll_speed, dy * scroll_speed); + buffer_scroll(buffer, dx * scroll_speed, dy * scroll_speed); } break; case SDL_MOUSEBUTTONDOWN: switch (event.button.button) { case SDL_BUTTON_LEFT: { BufferPos pos; - if (buffer_pixels_to_pos(&text_buffer, (float)event.button.x, (float)event.button.y, &pos)) - buffer_cursor_move_to_pos(&text_buffer, pos); + if (buffer_pixels_to_pos(buffer, V2((float)event.button.x, (float)event.button.y), &pos)) + buffer_cursor_move_to_pos(buffer, pos); } break; } break; case SDL_KEYDOWN: { switch (event.key.keysym.sym) { case SDLK_PAGEUP: - buffer_scroll(&text_buffer, 0, -buffer_display_lines(&text_buffer)); + buffer_scroll(buffer, 0, -buffer_display_lines(buffer)); break; case SDLK_PAGEDOWN: - buffer_scroll(&text_buffer, 0, +buffer_display_lines(&text_buffer)); + buffer_scroll(buffer, 0, +buffer_display_lines(buffer)); break; case SDLK_RIGHT: - if (ctrl) - buffer_cursor_move_right_words(&text_buffer, 1); - else - buffer_cursor_move_right(&text_buffer, 1); + if (shift) { + buffer_select_right(buffer, 1); + } else { + if (ctrl) + buffer_cursor_move_right_words(buffer, 1); + else + buffer_cursor_move_right(buffer, 1); + } break; case SDLK_LEFT: if (ctrl) - buffer_cursor_move_left_words(&text_buffer, 1); + buffer_cursor_move_left_words(buffer, 1); else - buffer_cursor_move_left(&text_buffer, 1); + buffer_cursor_move_left(buffer, 1); break; case SDLK_UP: - buffer_cursor_move_up(&text_buffer, 1); + buffer_cursor_move_up(buffer, 1); break; case SDLK_DOWN: - buffer_cursor_move_down(&text_buffer, 1); + buffer_cursor_move_down(buffer, 1); break; case SDLK_RETURN: - buffer_insert_char_at_cursor(&text_buffer, U'\n'); + buffer_insert_char_at_cursor(buffer, U'\n'); break; case SDLK_TAB: - buffer_insert_char_at_cursor(&text_buffer, U'\t'); + buffer_insert_char_at_cursor(buffer, U'\t'); break; case SDLK_DELETE: if (ctrl) - buffer_delete_words_at_cursor(&text_buffer, 1); + buffer_delete_words_at_cursor(buffer, 1); else - buffer_delete_chars_at_cursor(&text_buffer, 1); + buffer_delete_chars_at_cursor(buffer, 1); break; case SDLK_BACKSPACE: if (ctrl) - buffer_backspace_words_at_cursor(&text_buffer, 1); + buffer_backspace_words_at_cursor(buffer, 1); else - buffer_backspace_at_cursor(&text_buffer, 1); + buffer_backspace_at_cursor(buffer, 1); break; case SDLK_s: if (ctrl) { - if (!buffer_save(&text_buffer)) { - printf("Error saving: %s.", buffer_geterr(&text_buffer)); + if (!buffer_save(buffer)) { + printf("Error saving: %s.", buffer_geterr(buffer)); } } break; case SDLK_z: if (ctrl) { if (shift) { - buffer_redo(&text_buffer, 1); + buffer_redo(buffer, 1); } else { - buffer_undo(&text_buffer, 1); + buffer_undo(buffer, 1); } } break; @@ -177,7 +182,7 @@ int main(void) { } break; case SDL_TEXTINPUT: { char *text = event.text.text; - buffer_insert_utf8_at_cursor(&text_buffer, text); + buffer_insert_utf8_at_cursor(buffer, text); } break; } } @@ -195,13 +200,13 @@ int main(void) { double scroll_amount_x = scroll_speed * frame_dt * 1.5; // characters are taller than they are wide double scroll_amount_y = scroll_speed * frame_dt; if (keyboard_state[SDL_SCANCODE_UP]) - buffer_scroll(&text_buffer, 0, -scroll_amount_y); + buffer_scroll(buffer, 0, -scroll_amount_y); if (keyboard_state[SDL_SCANCODE_DOWN]) - buffer_scroll(&text_buffer, 0, +scroll_amount_y); + buffer_scroll(buffer, 0, +scroll_amount_y); if (keyboard_state[SDL_SCANCODE_LEFT]) - buffer_scroll(&text_buffer, -scroll_amount_x, 0); + buffer_scroll(buffer, -scroll_amount_x, 0); if (keyboard_state[SDL_SCANCODE_RIGHT]) - buffer_scroll(&text_buffer, +scroll_amount_x, 0); + buffer_scroll(buffer, +scroll_amount_x, 0); } @@ -222,7 +227,7 @@ int main(void) { { float x1 = 50, y1 = 50, x2 = window_widthf-50, y2 = window_heightf-50; - buffer_render(&text_buffer, x1, y1, x2, y2); + buffer_render(buffer, x1, y1, x2, y2); if (text_has_err()) { debug_println("Text error: %s\n", text_get_err()); break; @@ -230,9 +235,9 @@ int main(void) { } #if DEBUG - //buffer_print_debug(&text_buffer); - buffer_check_valid(&text_buffer); - buffer_print_undo_history(&text_buffer); + //buffer_print_debug(buffer); + buffer_check_valid(buffer); + buffer_print_undo_history(buffer); #endif SDL_GL_SwapWindow(window); @@ -241,7 +246,7 @@ int main(void) { SDL_GL_DeleteContext(glctx); SDL_DestroyWindow(window); SDL_Quit(); - buffer_free(&text_buffer); + buffer_free(buffer); text_font_free(font); return 0; @@ -66,6 +66,14 @@ static float maxf(float a, float b) { return a > b ? a : b; } +static u32 minu32(u32 a, u32 b) { + return a < b ? a : b; +} + +static u32 maxu32(u32 a, u32 b) { + return a > b ? a : b; +} + static float sgnf(float x) { if (x < 0) return -1; if (x > 0) return +1; |