diff options
-rw-r--r-- | desktop/textarea.c | 428 |
1 files changed, 310 insertions, 118 deletions
diff --git a/desktop/textarea.c b/desktop/textarea.c index e782fd6fd..185a878d9 100644 --- a/desktop/textarea.c +++ b/desktop/textarea.c @@ -29,6 +29,7 @@ #include "desktop/textarea.h" #include "desktop/textinput.h" #include "desktop/plotters.h" +#include "desktop/scrollbar.h" #include "render/font.h" #include "utils/log.h" #include "utils/utf8.h" @@ -49,34 +50,34 @@ struct line_info { struct textarea { - int scroll_x, scroll_y; /**< scroll offsets of the textarea - * content - */ + int scroll_x, scroll_y; /**< scroll offsets for the textarea */ + + struct scrollbar *bar_x; /**< Horizontal scroll. */ + struct scrollbar *bar_y; /**< Vertical scroll. */ unsigned int flags; /**< Textarea flags */ int vis_width; /**< Visible width, in pixels */ int vis_height; /**< Visible height, in pixels */ - int pad_top; - int pad_right; - int pad_bottom; - int pad_left; + int pad_top; /**< Top padding, inside border, in pixels */ + int pad_right; /**< Right padding, inside border, in pixels */ + int pad_bottom; /**< Bottom padding, inside border, in pixels */ + int pad_left; /**< Left padding, inside border, in pixels */ - int border_width; - colour border_col; + int border_width; /**< Border width, in pixels */ + colour border_col; /**< Border colour */ - plot_font_style_t fstyle; /**< Text style */ - plot_font_style_t sel_fstyle; /**< Text style */ + plot_font_style_t fstyle; /**< Text style, inc. textarea bg col */ + plot_font_style_t sel_fstyle; /**< Selected text style */ char *text; /**< UTF-8 text */ unsigned int text_alloc; /**< Size of allocated text */ unsigned int text_len; /**< Length of text, in bytes */ unsigned int text_utf8_len; /**< Length of text, in characters - * without the trailing NUL */ + * without the trailing NULL */ struct { int line; /**< Line caret is on */ - int char_off; /**< Character index of caret within the - * specified line */ + int char_off; /**< Character index of caret on line */ } caret_pos; int caret_x; /**< cached X coordinate of the caret */ @@ -85,6 +86,8 @@ struct textarea { int sel_start; /**< Character index of sel start(inclusive) */ int sel_end; /**< Character index of sel end(exclusive) */ + int h_extent; /**< Width of content in px */ + int v_extent; /**< Height of content in px */ int line_count; /**< Count of lines */ #define LINE_CHUNK_SIZE 16 struct line_info *lines; /**< Line info array */ @@ -228,7 +231,11 @@ static bool textarea_select_fragment(struct textarea * ta) */ static bool textarea_scroll_visible(struct textarea *ta) { - int x0, x1, y0, y1, x, y; + int x0, x1, y0, y1; /* area we want caret inside */ + int xc, yc; /* area centre */ + int x, y; /* caret pos */ + int xs = ta->scroll_x; + int ys = ta->scroll_y; bool scrolled = false; if (ta->caret_pos.char_off == -1) @@ -237,58 +244,114 @@ static bool textarea_scroll_visible(struct textarea *ta) x0 = ta->border_width + ta->pad_left; x1 = ta->vis_width - (ta->border_width + ta->pad_right); y0 = 0; - y1 = ta->vis_height - 2 * ta->border_width; - - x = ta->caret_x - ta->scroll_x; - y = ta->caret_y - ta->scroll_y; + y1 = ta->vis_height - 2 * ta->border_width - + ta->pad_top - ta->pad_bottom; + xc = (x1 - x0) / 2 + x0; + yc = (y1 - y0) / 2 + y0; + + x = ta->caret_x - xs; + y = ta->caret_y + ta->line_height / 2 - ys; + + /* horizontal scroll; centre caret */ + xs += x - xc; + + /* force back into range */ + if (xs < 0) + xs = 0; + else if (xs > ta->h_extent - (x1 - x0)) + xs = ta->h_extent - (x1 - x0); + + /* If scrolled, set new pos. */ + if (xs != ta->scroll_x && ta->bar_x != NULL) { + scrollbar_set(ta->bar_x, xs, false); + ta->scroll_x = scrollbar_get_offset(ta->bar_x); + scrolled = true; - /* check and change vertical scroll */ - if (y < y0) { - ta->scroll_y -= y0 - y; + } else if (ta->flags & TEXTAREA_MULTILINE && ta->bar_x == NULL && + ta->scroll_x != 0) { + ta->scroll_x = 0; scrolled = true; - } else if (y + ta->line_height > y1) { - ta->scroll_y += y + ta->line_height - y1; + + } else if (xs != ta->scroll_x && !(ta->flags & TEXTAREA_MULTILINE)) { + ta->scroll_x = xs; scrolled = true; } - - /* check and change horizontal scroll */ - if (x < x0) { - ta->scroll_x -= x0 - x ; - scrolled = true; - } else if (x > x1) { - ta->scroll_x += x - x1; - scrolled = true; + /* check and change vertical scroll */ + if (ta->flags & TEXTAREA_MULTILINE) { + /* vertical scroll; centre caret */ + ys += y - yc; + + /* force back into range */ + if (ys < 0) + ys = 0; + else if (ys > ta->v_extent - (y1 - y0)) + ys = ta->v_extent - (y1 - y0); + + /* If scrolled, set new pos. */ + if (ys != ta->scroll_y && ta->bar_y != NULL) { + scrollbar_set(ta->bar_y, ys, false); + ta->scroll_y = scrollbar_get_offset(ta->bar_y); + scrolled = true; + + } else if (ta->bar_y == NULL && ta->scroll_y != 0) { + ta->scroll_y = 0; + scrolled = true; + } } return scrolled; } +/** + * Callback for scrollbar widget. + */ +static void textarea_scrollbar_callback(void *client_data, + struct scrollbar_msg_data *scrollbar_data) +{ + struct textarea *ta = client_data; + + switch(scrollbar_data->msg) { + case SCROLLBAR_MSG_MOVED: + /* Scrolled; redraw everything */ + ta->scroll_x = scrollbar_get_offset(ta->bar_x); + ta->scroll_y = scrollbar_get_offset(ta->bar_y); + + ta->redraw_request(ta->data, 0, 0, + ta->vis_width, + ta->vis_height); + break; + case SCROLLBAR_MSG_SCROLL_START: + /* TODO: Tell textarea client we're handling a drag */ + break; + case SCROLLBAR_MSG_SCROLL_FINISHED: + /* TODO: Tell textarea client drag finished */ + break; + } +} + + /** * Reflow a text area from the given line onwards * * \param ta Text area to reflow - * \param line Line number to begin reflow on + * \param start Line number to begin reflow on * \return true on success false otherwise */ -static bool textarea_reflow(struct textarea *ta, unsigned int line) +static bool textarea_reflow(struct textarea *ta, unsigned int start) { char *text; unsigned int len; size_t b_off; int x; char *space, *para_end; - unsigned int line_count = 0; - int avail_width = ta->vis_width - 2 * ta->border_width - - ta->pad_left - ta->pad_right; - if (avail_width < 0) - avail_width = 0; - - /** \todo pay attention to line parameter */ - /** \todo create horizontal scrollbar if needed */ - - ta->line_count = 0; + unsigned int line; /* line count */ + unsigned int scroll_lines; + int avail_width; + int h_extent; /* horizontal extent */ + int v_extent; /* vertical extent */ + bool restart; if (ta->lines == NULL) { ta->lines = @@ -301,78 +364,190 @@ static bool textarea_reflow(struct textarea *ta, unsigned int line) if (!(ta->flags & TEXTAREA_MULTILINE)) { /* Single line */ - ta->lines[line_count].b_start = 0; - ta->lines[line_count++].b_length = ta->text_len - 1; + int w = ta->vis_width - 2 * ta->border_width - + ta->pad_left - ta->pad_right; + ta->lines[0].b_start = 0; + ta->lines[0].b_length = ta->text_len - 1; - ta->line_count = line_count; + nsfont.font_width(&ta->fstyle, ta->text, ta->text_len, &x); + if (x > w) + w = x; + ta->h_extent = w + ta->pad_left - ta->pad_right; + ta->line_count = 1; return true; } - for (len = ta->text_len - 1, text = ta->text; len > 0; - len -= b_off, text += b_off) { + /* Find max number of lines before vertical scrollbar is required */ + scroll_lines = (ta->vis_height - 2 * ta->border_width - + ta->pad_top - ta->pad_bottom) / + ta->line_height; + + if ((signed)start > ta->line_count) + start = 0; + /** \todo pay attention to start param, for now force start at zero */ + start = 0; + + do { + /* Set line count to start point */ + line = start; + + /* Find available width */ + avail_width = ta->vis_width - 2 * ta->border_width - + ta->pad_left - ta->pad_right; + if (avail_width < 0) + avail_width = 0; + h_extent = avail_width; + + restart = false; + for (len = ta->text_len - 1, text = ta->text; len > 0; + len -= b_off, text += b_off) { + + /* Find end of paragraph */ + for (para_end = text; para_end < text + len; + para_end++) { + if (*para_end == '\n') + break; + } + + /* Wrap current line in paragraph */ + nsfont.font_split(&ta->fstyle, text, para_end - text, + avail_width, &b_off, &x); + /* b_off now marks space, or end of paragraph */ - /* Find end of paragraph */ - for (para_end = text; para_end < text + len; para_end++) { - if (*para_end == '\n') - break; - } + if (x > h_extent) { + h_extent = x; + } + if (x > avail_width && ta->bar_x == NULL) { + /* We need to insert a horizontal scrollbar */ + int w = ta->vis_width - 2 * ta->border_width; + if (!scrollbar_create(true, w, w, w, + ta, textarea_scrollbar_callback, + &(ta->bar_x))) + return false; + if (ta->bar_y != NULL) + scrollbar_make_pair(ta->bar_x, + ta->bar_y); + ta->pad_bottom += SCROLLBAR_WIDTH; + + /* Find new max visible lines */ + scroll_lines = (ta->vis_height - + 2 * ta->border_width - + ta->pad_top - ta->pad_bottom) / + ta->line_height; + } - /* Wrap current line in paragraph */ - nsfont.font_split(&ta->fstyle, text, para_end - text, - avail_width, &b_off, &x); + if (line > 0 && line % LINE_CHUNK_SIZE == 0) { + struct line_info *temp = realloc(ta->lines, + (line + LINE_CHUNK_SIZE) * + sizeof(struct line_info)); + if (temp == NULL) { + LOG(("realloc failed")); + return false; + } - if (b_off == 0) { - /* Text wasn't split */ - b_off = para_end - text; - } - /* b_off now marks space, or end of paragraph */ - - if (line_count > 0 && line_count % LINE_CHUNK_SIZE == 0) { - struct line_info *temp = realloc(ta->lines, - (line_count + LINE_CHUNK_SIZE) * - sizeof(struct line_info)); - if (temp == NULL) { - LOG(("realloc failed")); - return false; + ta->lines = temp; } - ta->lines = temp; - } + if (para_end == text + b_off && *para_end == '\n') { + /* Not found any spaces to wrap at, and we + * have a newline char */ + ta->lines[line].b_start = text - ta->text; + ta->lines[line++].b_length = para_end - text; + + /* Jump newline */ + b_off++; + + if (len - b_off == 0) { + /* reached end of input; + * add last line */ + ta->lines[line].b_start = + text + b_off - ta->text; + ta->lines[line++].b_length = 0; + } - if (para_end == text + b_off && *para_end == '\n') { - /* Not found any spaces to wrap at, and we - * have a newline char */ - ta->lines[line_count].b_start = text - ta->text; - ta->lines[line_count++].b_length = para_end - text; + if (line > scroll_lines && ta->bar_y == NULL) + break; + + continue; - /* Jump newline */ - b_off++; + } else if (len - b_off > 0) { + /* soft wraped, find last space (if any) */ + for (space = text + b_off; space > text; + space--) { + if (*space == ' ') + break; + } - if (len - b_off == 0) { - /* reached end of input => add last line */ - ta->lines[line_count].b_start = - text + b_off - ta->text; - ta->lines[line_count++].b_length = 0; + if (space != text) + b_off = space + 1 - text; } - continue; + ta->lines[line].b_start = text - ta->text; + ta->lines[line++].b_length = b_off; - } else if (len - b_off > 0) { - /* soft wraped, find last space (if any) */ - for (space = text + b_off; space > text; space--) - if (*space == ' ') - break; + if (line > scroll_lines && ta->bar_y == NULL) + break; + } + + if (h_extent <= avail_width && ta->bar_x != NULL) { + /* We need to remove a horizontal scrollbar */ + scrollbar_destroy(ta->bar_x); + ta->bar_x = NULL; + ta->pad_bottom -= SCROLLBAR_WIDTH; - if (space != text) - b_off = space + 1 - text; + /* Find new max visible lines */ + scroll_lines = (ta->vis_height - 2 * ta->border_width - + ta->pad_top - ta->pad_bottom) / + ta->line_height; } - ta->lines[line_count].b_start = text - ta->text; - ta->lines[line_count++].b_length = b_off; + if (line > scroll_lines && ta->bar_y == NULL) { + /* Add vertical scrollbar */ + int h = ta->vis_height - 2 * ta->border_width; + if (!scrollbar_create(false, h, h, h, + ta, textarea_scrollbar_callback, + &(ta->bar_y))) + return false; + if (ta->bar_x != NULL) + scrollbar_make_pair(ta->bar_x, + ta->bar_y); + ta->pad_right += SCROLLBAR_WIDTH; + restart = true; + + } else if (line <= scroll_lines && ta->bar_y != NULL) { + /* Remove vertical scrollbar */ + scrollbar_destroy(ta->bar_y); + ta->bar_y = NULL; + ta->pad_right -= SCROLLBAR_WIDTH; + restart = true; + } + } while (restart); + + h_extent += ta->pad_left + ta->pad_right - + (ta->bar_y != NULL ? SCROLLBAR_WIDTH : 0); + v_extent = line * ta->line_height + ta->pad_top + + ta->pad_bottom - + (ta->bar_x != NULL ? SCROLLBAR_WIDTH : 0); + + if (ta->bar_x != NULL) { + /* Set horizontal scrollbar extents */ + int w = ta->vis_width - 2 * ta->border_width - + (ta->bar_y != NULL ? SCROLLBAR_WIDTH : 0); + scrollbar_set_extents(ta->bar_x, w, w, h_extent); } - ta->line_count = line_count; + if (ta->bar_y != NULL) { + /* Set vertical scrollbar extents */ + int h = ta->vis_height - 2 * ta->border_width; + scrollbar_set_extents(ta->bar_y, h, + h - (ta->bar_x != NULL ? SCROLLBAR_WIDTH : 0), + v_extent); + } + + ta->h_extent = h_extent; + ta->v_extent = v_extent; + ta->line_count = line; return true; } @@ -641,6 +816,8 @@ struct textarea *textarea_create(const textarea_setup *setup, ret->scroll_x = 0; ret->scroll_y = 0; + ret->bar_x = NULL; + ret->bar_y = NULL; ret->drag_start_char = 0; @@ -676,6 +853,10 @@ struct textarea *textarea_create(const textarea_setup *setup, /* exported interface, documented in textarea.h */ void textarea_destroy(struct textarea *ta) { + if (ta->bar_x) + scrollbar_destroy(ta->bar_x); + if (ta->bar_y) + scrollbar_destroy(ta->bar_y); free(ta->text); free(ta->lines); free(ta); @@ -811,13 +992,8 @@ bool textarea_set_caret(struct textarea *ta, int caret) y = ta->line_height * ta->caret_pos.line; ta->caret_y = y; - if (textarea_scroll_visible(ta)) { - /* Scrolled; redraw everything */ - ta->redraw_request(ta->data, 0, 0, - ta->vis_width, - ta->vis_height); - } else { - /* Just caret moved, redraw it */ + if (!textarea_scroll_visible(ta)) { + /* No scroll, just caret moved, redraw it */ x -= ta->scroll_x; y -= ta->scroll_y; x0 = max(x - 1, ta->border_width); @@ -939,8 +1115,10 @@ void textarea_redraw(struct textarea *ta, int x, int y, colour bg, r.x1 = x + ta->vis_width - ta->border_width; if (r.y0 < y + ta->border_width) r.y0 = y + ta->border_width; - if (r.y1 > y + ta->vis_width - ta->border_width) - r.y1 = y + ta->vis_height - ta->border_width; + if (r.y1 > y + ta->vis_height - ta->border_width - + (ta->bar_x != NULL ? SCROLLBAR_WIDTH : 0)) + r.y1 = y + ta->vis_height - ta->border_width - + (ta->bar_x != NULL ? SCROLLBAR_WIDTH : 0); if (line0 > 0) c_pos = utf8_bounded_length(ta->text, @@ -977,7 +1155,7 @@ void textarea_redraw(struct textarea *ta, int x, int y, colour bg, ta->lines[line].b_length); b_end = 0; - right = x + ta->border_width + ta->pad_left; + right = x + ta->border_width + ta->pad_left - ta->scroll_x; do { sel_start = ta->sel_start; @@ -1028,7 +1206,8 @@ void textarea_redraw(struct textarea *ta, int x, int y, colour bg, left = right; nsfont.font_width(&ta->fstyle, line_text, b_end, &right); - right += x + ta->border_width + ta->pad_left; + right += x + ta->border_width + ta->pad_left - + ta->scroll_x; /* set clip rectangle for line part */ s = r; @@ -1040,10 +1219,10 @@ void textarea_redraw(struct textarea *ta, int x, int y, colour bg, if (selected) { /* draw selection fill */ - plot->rectangle(s.x0 - ta->scroll_x, + plot->rectangle(s.x0, y + line * ta->line_height + 1 - ta->scroll_y + text_y_offset, - s.x1 - ta->scroll_x, + s.x1, y + (line + 1) * ta->line_height + 1 - ta->scroll_y + text_y_offset, &plot_style_fill_bg); @@ -1070,19 +1249,32 @@ void textarea_redraw(struct textarea *ta, int x, int y, colour bg, c_pos++; } - plot->clip(&r); - x -= ta->scroll_x; - y -= ta->scroll_y; + plot->clip(clip); - if (ta->sel_end == -1 || ta->sel_start == ta->sel_end) { + if (ta->sel_end == -1 || ta->sel_start == ta->sel_end && + ta->caret_pos.char_off >= 0) { /* There is no selection; draw caret */ - int caret_y = y + ta->caret_y + text_y_offset; + int caret_y = y - ta->scroll_y + ta->caret_y + text_y_offset; int caret_height = caret_y + ta->line_height; - plot->line(x + ta->caret_x, caret_y, - x + ta->caret_x, caret_height, + plot->line(x - ta->scroll_x + ta->caret_x, caret_y, + x - ta->scroll_x + ta->caret_x, caret_height, &pstyle_stroke_caret); } + + if (ta->bar_x != NULL) + scrollbar_redraw(ta->bar_x, + x + ta->border_width, + y + ta->vis_height - ta->border_width - + SCROLLBAR_WIDTH, + clip, 1.0, ctx); + + if (ta->bar_y != NULL) + scrollbar_redraw(ta->bar_y, + x + ta->vis_width - ta->border_width - + SCROLLBAR_WIDTH, + y + ta->border_width, + clip, 1.0, ctx); } @@ -1352,7 +1544,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) break; if (ta->sel_start != -1) { if (!textarea_replace_text(ta, - ta->sel_start, + ta->sel_start, ta->sel_end, "", 0, false)) return false; @@ -1420,7 +1612,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) break; if (ta->sel_start != -1) { if (!textarea_replace_text(ta, - ta->sel_start, + ta->sel_start, ta->sel_end, "", 0, false)) return false; ta->sel_start = ta->sel_end = -1; @@ -1440,7 +1632,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) break; if (ta->sel_start != -1) { if (!textarea_replace_text(ta, - ta->sel_start, + ta->sel_start, ta->sel_end, "", 0, false)) return false; ta->sel_start = ta->sel_end = -1; |