summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Drake <tlsa@netsurf-browser.org>2013-01-15 19:40:32 +0000
committerMichael Drake <tlsa@netsurf-browser.org>2013-01-15 19:40:32 +0000
commit348e9789a42ce5479dcd38057d9d9d37fedaff6f (patch)
tree5cafc1a25801ef70a87f2e94e2e20aa1ebc0d553
parent4e756f6088bce4cd634702d02ccdae5389392a17 (diff)
downloadnetsurf-348e9789a42ce5479dcd38057d9d9d37fedaff6f.tar.gz
netsurf-348e9789a42ce5479dcd38057d9d9d37fedaff6f.tar.bz2
Add scrollbars to textarea, and improve scrolling to try to keep caret in centre. Currently the scrollbars are mostly decoration, although they show accuratly where you are scrolled to in the area. TODO: Pass mouse input to scrollbars.
-rw-r--r--desktop/textarea.c428
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;