commit e9c86a9b95b0a349d3f34f2a15dd3afffafc3b52
parent 155d5f07022e7d2fa4de2374b4ba6c1e9023e124
Author: lumidify <nobody@lumidify.org>
Date: Sun, 4 Apr 2021 22:29:52 +0200
Cache pre-rendered text
Diffstat:
M | .gitignore | | | 1 | + |
M | ledit.c | | | 296 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------ |
2 files changed, 231 insertions(+), 66 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,4 +1,5 @@
tmp
ledit
+ledit_old
*.core
*.o
diff --git a/ledit.c b/ledit.c
@@ -1,3 +1,4 @@
+/* FIXME: horizontal scrolling (also need cache to avoid too large pixmaps) */
#include <math.h>
#include <stdio.h>
#include <errno.h>
@@ -31,6 +32,8 @@ struct key {
void (*func)(void); /* callback function */
};
+#define MAX_CACHE_PIXELS 100
+
static struct {
Display *dpy;
GC gc;
@@ -53,6 +56,17 @@ static struct {
Atom wm_delete_msg;
} state;
+/* FIXME: possibly use at least 32 bits int? */
+static struct {
+ Pixmap pix;
+ XftDraw *draw;
+ int pix_w, pix_h;
+ long start_offset;
+ int valid_height;
+ int begin_line, begin_softline;
+ int dirty;
+} cache;
+
static void mainloop(void);
static void setup(int argc, char *argv[]);
static void cleanup(void);
@@ -74,17 +88,21 @@ main(int argc, char *argv[]) {
static struct line {
PangoLayout *layout;
- XftDraw *draw;
char *text;
size_t cap;
size_t len;
- Pixmap pix;
int w;
int h;
- int pix_w;
- int pix_h;
+ long y_offset;
+ /*
+ XftDraw *draw;
+ Pixmap pix;
+ unsigned int pix_w;
+ unsigned int pix_h;
+ */
char dirty;
} *lines = NULL;
+
static size_t lines_num = 0;
static size_t lines_cap = 0;
@@ -92,7 +110,7 @@ static int cur_line = 0;
static int cur_subline = 0;
static int cur_index = 0;
static int trailing = 0;
-static int total_height = 0;
+static long total_height = 0;
static double cur_display_offset = 0;
static void
@@ -104,13 +122,96 @@ init_line(struct line *l) {
pango_layout_set_wrap(l->layout, PANGO_WRAP_WORD_CHAR);
l->text = NULL;
l->cap = l->len = 0;
- l->pix = None;
+ /*l->pix = None;*/
/* FIXME: does this set line height reasonably when no text yet? */
pango_layout_get_pixel_size(l->layout, &l->w, &l->h);
- l->dirty = 1;
+ l->y_offset = 0;
+ //l->dirty = 1;
}
-static void recalc_height_absolute(void);
+static void recalc_cur_line_size(void);
+static void recalc_line_size_absolute(void);
+
+enum CachePosition {
+ CACHE_NONE,
+ CACHE_BOTH,
+ CACHE_TOP,
+ CACHE_BOTTOM
+};
+
+static void redraw_cache_complete(enum CachePosition pos);
+static void redraw_cache_after_cur_line(void);
+static void redraw_cache_only_cur_line(void);
+
+#define MAX_INT(a, b) ((a) > (b) ? (a) : (b))
+#define MIN_INT(a, b) ((a) < (b) ? (a) : (b))
+
+/* FIXME: Implement pos */
+static void
+redraw_cache_complete(enum CachePosition pos) {
+ long start = cur_display_offset > MAX_CACHE_PIXELS ? (long)(cur_display_offset) - MAX_CACHE_PIXELS : 0;
+ long end = (long)(cur_display_offset) + state.h + MAX_CACHE_PIXELS;
+ int start_line = 0;
+ int end_line = 0;
+
+ /* FIXME: make this more efficient - we can't start at cur_line
+ * because that may be off screen */
+ /*
+ for (start_line = cur_line;
+ lines[start_line].y_offset > cur_display_offset;
+ start_line--) {
+ }
+
+ for (end_line = cur_line;
+ end_line < lines_num &&
+ lines[end_line].y_offset + lines[end_line].h < cur_display_offset + state.h;
+ end_line++) {
+ }
+ */
+
+ for (start_line = 0;
+ start_line < lines_num - 1 &&
+ lines[start_line].y_offset + lines[start_line].h <= cur_display_offset;
+ start_line++) {
+ /* NOP */
+ }
+ for (end_line = start_line;
+ end_line < lines_num - 1 &&
+ lines[end_line].y_offset + lines[end_line].h < cur_display_offset + state.h;
+ end_line++) {
+ /* NOP */
+ }
+
+ if (lines[start_line].y_offset < start) {
+ printf("FIX THIS CODE 1!\n");
+ }
+ if (lines[end_line].y_offset + lines[end_line].h > end) {
+ printf("FIX THIS CODE 2!\n");
+ }
+
+ /* FIXME: only wipe what is necessary */
+ XftDrawRect(cache.draw, &state.bg, 0, 0, cache.pix_w, cache.pix_h);
+ int cur_y = 0;
+ for (int i = start_line; i <= end_line; i++) {
+ if (lines[i].w > cache.pix_w || cur_y + lines[i].h > cache.pix_h) {
+ break; /* should never happen */
+ }
+ pango_xft_render_layout(cache.draw, &state.fg, lines[i].layout, 0, cur_y * PANGO_SCALE);
+ cur_y += lines[i].h;
+ }
+ cache.valid_height = cur_y;
+ cache.start_offset = lines[start_line].y_offset;
+ cache.begin_line = start_line;
+ cache.dirty = 0;
+}
+
+static void
+redraw_cache_after_cur_line(void) {
+}
+
+static void
+redraw_cache_only_cur_line(void) {
+}
static void
insert_text(struct line *l, int index, char *text, int len) {
@@ -127,8 +228,8 @@ insert_text(struct line *l, int index, char *text, int len) {
memcpy(l->text + index, text, len);
l->len += len;
pango_layout_set_text(l->layout, l->text, l->len);
- recalc_height_absolute();
- l->dirty = 1;
+ recalc_cur_line_size();
+ cache.dirty = 1;
}
static void insert_line_entry(int index);
@@ -136,6 +237,7 @@ static void insert_line_entry(int index);
static void
render_line(struct line *l) {
/* FIXME: check for <= 0 on size */
+ /*
if (l->pix == None) {
l->pix = XCreatePixmap(state.dpy, state.back_buf, l->w + 10, l->h + 10, state.depth);
l->pix_w = l->w + 10;
@@ -153,6 +255,7 @@ render_line(struct line *l) {
XftDrawRect(l->draw, &state.bg, 0, 0, l->w, l->h);
pango_xft_render_layout(l->draw, &state.fg, l->layout, 0, 0);
l->dirty = 0;
+ */
}
static void
@@ -171,7 +274,7 @@ append_line(int text_index, int line_index) {
if (text_index != -1) {
struct line *l = &lines[line_index];
int len = l->len - text_index;
- new_l->pix = None;
+ //new_l->pix = None;
new_l->len = len;
new_l->cap = len + 10;
new_l->text = malloc(new_l->cap);
@@ -182,6 +285,7 @@ append_line(int text_index, int line_index) {
pango_layout_set_text(l->layout, l->text, l->len);
/* FIXME: set height here */
}
+ /* FIXME: update line heights, etc. */
}
static void change_keyboard(char *lang);
@@ -207,6 +311,31 @@ static int scroll_dragging = 0;
static int scroll_grab_handle = 0;
static void
+set_line_cursor_attrs(int line, int index) {
+ if (cur_mode == NORMAL) {
+ PangoAttribute *attr0 = pango_attr_background_new(0, 0, 0);
+ PangoAttribute *attr1 = pango_attr_foreground_new(65535, 65535, 65535);
+ attr0->start_index = index;
+ attr0->end_index = index + 1;
+ attr1->start_index = index;
+ attr1->end_index = index + 1;
+ PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE);
+ PangoAttrList *list = pango_attr_list_new();
+ pango_attr_list_insert(list, attr0);
+ pango_attr_list_insert(list, attr1);
+ pango_attr_list_insert(list, attr2);
+ pango_layout_set_attributes(lines[line].layout, list);
+ } else {
+ pango_layout_set_attributes(lines[line].layout, basic_attrs);
+ }
+}
+
+static void
+wipe_line_cursor_attrs(int line) {
+ pango_layout_set_attributes(lines[line].layout, basic_attrs);
+}
+
+static void
mainloop(void) {
XEvent event;
int xkb_event_type;
@@ -348,7 +477,10 @@ mainloop(void) {
XSetForeground(state.dpy, state.gc, state.bg.pixel);
XFillRectangle(state.dpy, state.back_buf, state.gc, 0, 0, state.w, state.h);
int h = 0;
+ if (cache.dirty)
+ redraw_cache_complete(CACHE_BOTH);
/*int cur_line_height = 0;*/
+ /*
int tmp_w, tmp_h;
int cur_line_y = 0;
int cursor_displayed = 0;
@@ -394,24 +526,32 @@ mainloop(void) {
break;
h += lines[i].h;
}
+ */
+ double offset = cur_display_offset - cache.start_offset;
+ if (offset < 0) {
+ printf("FIX THIS CODE 3!\n");
+ offset = 0;
+ }
+ XCopyArea(state.dpy, cache.pix, state.back_buf, state.gc, 0, (int)offset, state.w - 10, state.h, 0, 0);
need_redraw = 0;
XSetForeground(state.dpy, state.gc, state.fg.pixel);
PangoRectangle strong, weak;
pango_layout_get_cursor_pos(lines[cur_line].layout, cur_index, &strong, &weak);
- int cursor_y = strong.y / PANGO_SCALE + cur_line_y;
- if (cursor_displayed && cursor_y >= 0) {
+ /* FIXME: long, int, etc. */
+ long cursor_y = strong.y / PANGO_SCALE + lines[cur_line].y_offset;
+ if (cursor_y >= cur_display_offset && cursor_y < cur_display_offset + state.h) {
if (cur_mode == NORMAL && cur_index == lines[cur_line].len) {
XFillRectangle(
state.dpy, state.back_buf, state.gc,
- strong.x / PANGO_SCALE, cursor_y,
+ strong.x / PANGO_SCALE, cursor_y - (long)cur_display_offset,
10, strong.height / PANGO_SCALE
);
} else if (cur_mode == INSERT) {
XDrawLine(
state.dpy, state.back_buf, state.gc,
- strong.x / PANGO_SCALE, cursor_y,
- strong.x / PANGO_SCALE, (strong.y + strong.height) / PANGO_SCALE + cur_line_y
+ strong.x / PANGO_SCALE, cursor_y - (long)cur_display_offset,
+ strong.x / PANGO_SCALE, (strong.y + strong.height) / PANGO_SCALE + lines[cur_line].y_offset - (long)cur_display_offset
);
}
}
@@ -480,13 +620,20 @@ setup(int argc, char *argv[]) {
state.cm = DefaultColormap(state.dpy, state.screen);
memset(&attrs, 0, sizeof(attrs));
- attrs.background_pixmap = None;
+ attrs.background_pixel = BlackPixel(state.dpy, state.screen);
attrs.colormap = state.cm;
+ /* this causes the window contents to be kept
+ * when it is resized, leading to less flicker */
+ attrs.bit_gravity = NorthWestGravity;
state.win = XCreateWindow(state.dpy, DefaultRootWindow(state.dpy), 0, 0,
state.w, state.h, 0, state.depth,
- InputOutput, state.vis, CWBackPixmap | CWColormap, &attrs);
+ InputOutput, state.vis, CWBackPixel | CWColormap | CWBitGravity, &attrs);
state.back_buf = XdbeAllocateBackBufferName(state.dpy, state.win, XdbeBackground);
+ cache.pix = XCreatePixmap(state.dpy, state.back_buf, 500, 500, state.depth);
+ cache.pix_w = cache.pix_h = 500;
+ cache.draw = XftDrawCreate(state.dpy, cache.pix, state.vis, state.cm);
+ cache.dirty = 1;
memset(&gcv, 0, sizeof(gcv));
gcv.line_width = 1;
@@ -543,59 +690,56 @@ button_release(void) {
static void
ensure_cursor_shown(void) {
- int cur_line_y = -1;
- int h = 0;
- /* FIXME: cache this to avoid useless recalculation */
- for (int i = 0; i < lines_num; i++) {
- if (cur_line == i) {
- cur_line_y = h;
- break;
- }
- h += lines[i].h;
- }
- if (cur_line_y < 0)
- return;
PangoRectangle strong, weak;
pango_layout_get_cursor_pos(lines[cur_line].layout, cur_index, &strong, &weak);
- int cursor_y = strong.y / PANGO_SCALE + cur_line_y;
+ long cursor_y = strong.y / PANGO_SCALE + lines[cur_line].y_offset;
if (cursor_y < cur_display_offset) {
cur_display_offset = cursor_y;
+ cache.dirty = 1;
} else if (cursor_y + strong.height / PANGO_SCALE > cur_display_offset + state.h) {
cur_display_offset = cursor_y - state.h + strong.height / PANGO_SCALE;
+ cache.dirty = 1;
}
}
static void
-recalc_height(void) {
- /*
- int w, h;
- pango_layout_get_pixel_size(lines[cur_line].layout, &w, &h);
- total_height += (h - cur_line_height);
- */
- if (total_height < 0)
- total_height = 0; /* should never actually happen */
- /*cur_line_height = h;*/
-}
-
-static void
-set_cur_line_height(void) {
+recalc_cur_line_size(void) {
int w, h;
pango_layout_get_pixel_size(lines[cur_line].layout, &w, &h);
- lines[cur_line].h = h;
- /*cur_line_height = h;*/
+ lines[cur_line].w = w;
+ /* if height changed, set height of current line
+ * and adjust offsets of all lines following it */
+ if (lines[cur_line].h != h) {
+ int delta = h - lines[cur_line].h;
+ lines[cur_line].h = h;
+ /* protect against underflow even though
+ * it should never happen anyways... */
+ if (delta < 0 && total_height < -delta)
+ total_height = 0;
+ else
+ total_height += delta;
+ for (int i = cur_line + 1; i < lines_num; i++) {
+ /* yeah, maybe I should just use a signed type... */
+ if (delta < 0 && lines[i].y_offset < -delta)
+ lines[i].y_offset = 0;
+ else
+ lines[i].y_offset += delta;
+ }
+ }
}
static void
-recalc_height_absolute(void) {
+recalc_line_size_absolute(void) {
int w, h;
total_height = 0;
+ /* completely recalculate line sizes and offsets from scratch */
for (int i = 0; i < lines_num; i++) {
+ lines[i].y_offset = total_height;
pango_layout_get_pixel_size(lines[i].layout, &w, &h);
total_height += h;
lines[i].w = w;
lines[i].h = h;
}
- set_cur_line_height();
}
static void
@@ -608,11 +752,19 @@ resize_window(int w, int h) {
/* 10 pixels for scrollbar */
pango_layout_set_width(lines[i].layout, (w - 10) * PANGO_SCALE);
pango_layout_get_pixel_size(lines[i].layout, &tmp_w, &tmp_h);
- total_height += tmp_h;
lines[i].h = tmp_h;
- lines[i].dirty = 1;
+ lines[i].w = tmp_w;
+ lines[i].y_offset = total_height;
+ total_height += tmp_h;
+ }
+ if (cache.pix_w < state.w - 10 || cache.pix_h < state.h + 2 * MAX_CACHE_PIXELS) {
+ XFreePixmap(state.dpy, cache.pix);
+ cache.pix = XCreatePixmap(state.dpy, state.back_buf, state.w, state.h + 2 * MAX_CACHE_PIXELS + 50, state.depth);
+ cache.pix_w = state.w;
+ cache.pix_h = state.h + 2 * MAX_CACHE_PIXELS + 50;
+ XftDrawChange(cache.draw, cache.pix);
}
- //set_cur_line_height();
+ cache.dirty = 1;
}
static void
@@ -651,8 +803,9 @@ backspace(void) {
cur_index = i;
pango_layout_set_text(l->layout, l->text, l->len);
}
- lines[cur_line].dirty = 1;
- recalc_height_absolute();
+ set_line_cursor_attrs(cur_line, cur_index);
+ cache.dirty = 1;
+ recalc_cur_line_size();
}
static void
@@ -677,8 +830,9 @@ delete_key(void) {
l->len -= i - cur_index;
pango_layout_set_text(l->layout, l->text, l->len);
}
- lines[cur_line].dirty = 1;
- recalc_height_absolute();
+ set_line_cursor_attrs(cur_line, cur_index);
+ cache.dirty = 1;
+ recalc_cur_line_size();
}
static void
@@ -703,7 +857,8 @@ move_cursor(int dir) {
else
cur_index = lines[cur_line].len;
}
- lines[cur_line].dirty = 1;
+ set_line_cursor_attrs(cur_line, cur_index);
+ cache.dirty = 1;
}
static void
@@ -719,11 +874,14 @@ cursor_right(void) {
static void
return_key(void) {
append_line(cur_index, cur_line);
- lines[cur_line].dirty = 1;
+ /* FIXME: these aren't needed, right? This only works in insert mode
+ * anyways, so there's nothing to wipe */
+ wipe_line_cursor_attrs(cur_line);
cur_line++;
- lines[cur_line].dirty = 1;
+ set_line_cursor_attrs(cur_line, cur_index);
+ cache.dirty = 1;
cur_index = 0;
- recalc_height_absolute();
+ recalc_line_size_absolute();
}
static void
@@ -740,7 +898,8 @@ escape_key(void) {
} else {
cursor_left();
}
- lines[cur_line].dirty = 1;
+ set_line_cursor_attrs(cur_line, cur_index);
+ cache.dirty = 1;
/*
if (cur_index > 0)
cursor_left();
@@ -755,7 +914,8 @@ i_key(void) {
pango_layout_set_attributes(lines[i].layout, NULL);
}
*/
- lines[cur_line].dirty = 1;
+ wipe_line_cursor_attrs(cur_line);
+ cache.dirty = 1;
}
static void
@@ -765,7 +925,7 @@ line_down(void) {
int maxlines = pango_layout_get_line_count(lines[cur_line].layout);
PangoLayoutLine *line = pango_layout_get_line_readonly(lines[cur_line].layout, lineno);
if (lineno == maxlines - 1) {
- lines[cur_line].dirty = 1;
+ wipe_line_cursor_attrs(cur_line);
/* move to the next hard line */
if (cur_line < lines_num - 1) {
cur_line++;
@@ -785,7 +945,8 @@ line_down(void) {
}
if (cur_index > 0 && cur_mode == NORMAL && cur_index >= lines[cur_line].len)
cursor_left();
- lines[cur_line].dirty = 1;
+ set_line_cursor_attrs(cur_line, cur_index);
+ cache.dirty = 1;
}
static void
@@ -794,7 +955,7 @@ line_up(void) {
pango_layout_index_to_line_x(lines[cur_line].layout, cur_index, 0, &lineno, &x);
PangoLayoutLine *line = pango_layout_get_line_readonly(lines[cur_line].layout, lineno);
if (lineno == 0) {
- lines[cur_line].dirty = 1;
+ wipe_line_cursor_attrs(cur_line);
/* move to the previous hard line */
if (cur_line > 0) {
cur_line--;
@@ -815,13 +976,15 @@ line_up(void) {
}
if (cur_index > 0 && cur_mode == NORMAL && cur_index >= lines[cur_line].len)
cursor_left();
- lines[cur_line].dirty = 1;
+ set_line_cursor_attrs(cur_line, cur_index);
+ cache.dirty = 1;
}
static void
zero_key(void) {
cur_index = 0;
- lines[cur_line].dirty = 1;
+ set_line_cursor_attrs(cur_line, cur_index);
+ cache.dirty = 1;
}
static struct key keys_en[] = {
@@ -924,6 +1087,7 @@ key_press(XEvent event) {
if (cur_mode == INSERT && !found && n > 0) {
insert_text(&lines[cur_line], cur_index, buf, n);
cur_index += n;
+ recalc_cur_line_size();
}
ensure_cursor_shown();
}