aboutsummaryrefslogtreecommitdiff
path: root/src/libterm/window.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libterm/window.c')
-rw-r--r--src/libterm/window.c408
1 files changed, 408 insertions, 0 deletions
diff --git a/src/libterm/window.c b/src/libterm/window.c
new file mode 100644
index 0000000..5d36c8b
--- /dev/null
+++ b/src/libterm/window.c
@@ -0,0 +1,408 @@
+#include "term.h"
+
+// -----------------------------------------------------------------------
+// buffers
+
+static
+void
+zero(Row *row, int start, int len)
+{
+ int i;
+ Cell cell = {
+ .txt = L' ',
+ .pen = {
+ .state = PenNormal,
+ .col.fg = -1,
+ .col.bg = -1,
+ },
+ };
+
+ for (i = start; i < len + start; i++)
+ row->cells[i] = cell;
+ row->dirty = 1;
+}
+
+static
+void
+roll(Row *start, Row *end, int count)
+{
+ int n = end - start;
+
+ /* enforce circularity */
+ count %= n;
+ if (count < 0)
+ count += n;
+
+ if (count) {
+ char buf[count * sizeof(Row)]; /* XXX: remove VLA */
+ memcpy(buf, start, count * sizeof(Row));
+ memmove(start, start + count, (n - count) * sizeof(Row));
+ memcpy(end - count, buf, count * sizeof(Row));
+
+ for (Row *row = start; row < end; row++)
+ row->dirty = 1;
+ }
+}
+
+/* buffer operations */
+static
+void
+bclear(Buffer *b)
+{
+ int i;
+ Cell cell = {
+ .txt = L' ',
+ .pen = {
+ .state = PenNormal,
+ .col.fg = -1,
+ .col.bg = -1,
+ },
+ };
+
+ for (i = 0; i < b->h; i++) {
+ Row *row = b->row + i;
+ for (int j = 0; j < b->w; j++) {
+ row->cells[j] = cell;
+ row->dirty = 1;
+ }
+ }
+}
+
+static
+void
+bfini(Buffer *b)
+{
+ int i;
+
+ for (i = 0; i < b->h; i++)
+ free(b->row[i].cells);
+
+ free(b->row);
+
+ if (b->scroll.size) {
+ for (i = 0; i < b->scroll.size; i++)
+ free(b->scroll.buf[i].cells);
+
+ free(b->scroll.buf);
+ }
+}
+
+static
+void
+bscroll(Buffer *b, int s)
+{
+ Row tmp;
+ int i, ssz = b->scroll.bot - b->scroll.top;
+
+ /* work in quanta of screen size */
+ if (s > ssz) {
+ bscroll(b, ssz);
+ bscroll(b, s - ssz);
+ return;
+ }
+ if (s < -ssz) {
+ bscroll(b, -ssz);
+ bscroll(b, s + ssz);
+ return;
+ }
+
+ b->scroll.above += s;
+ b->scroll.above = CLAMP(b->scroll.above, 0, b->scroll.size);
+
+ if (s > 0) {
+ if (b->scroll.size) {
+ for (i = 0; i < s; i++) {
+ tmp = b->scroll.top[i];
+ b->scroll.top[i] = b->scroll.buf[b->scroll.index];
+ b->scroll.buf[b->scroll.index] = tmp;
+
+ b->scroll.index++;
+ if (b->scroll.index == b->scroll.size)
+ b->scroll.index = 0;
+ }
+ } else
+ for (i = 0; i < s; i++)
+ zero(b->scroll.top+i, 0, b->maxw);
+ }
+
+ roll(b->scroll.top, b->scroll.bot, s);
+
+ if (s < 0) {
+ if (b->scroll.size) {
+ for (i = (-s) - 1; i >= 0; i--) {
+ b->scroll.index--;
+ if (b->scroll.index == -1)
+ b->scroll.index = b->scroll.size - 1;
+
+ tmp = b->scroll.top[i];
+
+ b->scroll.top[i] = b->scroll.buf[b->scroll.index];
+ b->scroll.buf[b->scroll.index] = tmp;
+ b->scroll.top[i].dirty = 1;
+ }
+ } else
+ for (i = (-s) - 1; i >= 0; i--)
+ zero(b->scroll.top+i, 0, b->maxw);
+ }
+}
+
+static
+void
+bresize(Buffer *b, int nrow, int ncol)
+{
+ int r, d;
+ Row *row = b->row;
+ Row *cur = row + b->cur.row;
+
+ if (b->h != nrow) {
+ /* scroll if we can */
+ if (cur >= row + nrow)
+ bscroll(b, b->cur.row - nrow + 1);
+ while (b->h > nrow) {
+ free(row[b->h - 1].cells);
+ b->h--;
+ }
+
+ row = realloc(row, sizeof(Row) * nrow);
+ }
+
+ if (b->maxw < ncol) {
+ /* expand each row */
+ for (r = 0; r < b->h; r++) {
+ row[r].cells = realloc(row[r].cells, sizeof(Cell) * ncol);
+ if (b->h < ncol)
+ zero(row + r, b->w, ncol - b->w);
+ row[r].dirty = 1;
+ }
+ /* expand the scroll buffer */
+ Row *sbuf = b->scroll.buf;
+ for (r = 0; r < b->scroll.size; r++) {
+ sbuf[r].cells = realloc(sbuf[r].cells, sizeof(Cell) * ncol);
+ if (b->w < ncol)
+ zero(sbuf + r, b->w, ncol - b->w);
+ }
+ b->maxw = b->w = ncol;
+ } else if (b->w != ncol) {
+ for (r = 0; r < b->h; r++)
+ row[r].dirty = 1;
+ b->w = ncol;
+ }
+
+ d = 0;
+ if (b->h < nrow) {
+ while (b->h < nrow) {
+ row[b->h].cells = calloc(b->maxw, sizeof(Cell));
+ zero(row + b->h, 0, b->maxw);
+ b->h++;
+ }
+
+ /* prepare for backfill */
+ if (cur >= b->scroll.bot - 1) {
+ d = b->row + nrow - cur - 1;
+ if (d > b->scroll.above)
+ d = b->scroll.above;
+ }
+ }
+
+ b->cur.row += row - b->row;
+ b->scroll.top = row;
+ b->scroll.bot = row + nrow;
+ b->row = row;
+
+ /* perform backfill */
+ if (d > 0) {
+ bscroll(b, -d);
+ b->cur.row += d;
+ }
+}
+
+static
+bool
+binit(Buffer *b, int cols, int rows, int scroll)
+{
+ int size;
+
+ b->pen.state = PenNormal;
+ b->pen.col.fg = b->pen.col.bg = -1;
+
+ size = MAX(scroll, 0);
+ if (size && !(b->scroll.buf = calloc(size, sizeof(Row))))
+ return false;
+
+ b->scroll.size = size;
+ bresize(b, rows, cols);
+
+ b->cur = (Dot){0};
+ b->save = b->cur;
+
+ return true;
+}
+
+static
+void
+bboundary(Buffer *b, Row **bs, Row **be, Row **as, Row **ae)
+{
+ if (bs)
+ *bs = nil;
+ if (be)
+ *be = nil;
+ if (as)
+ *as = nil;
+ if (ae)
+ *ae = nil;
+ if (!b->scroll.size)
+ return;
+
+ if (b->scroll.above) {
+ if (bs)
+ *bs = &b->scroll.buf[(b->scroll.index - b->scroll.above + b->scroll.size) % b->scroll.size];
+ if (be)
+ *be = &b->scroll.buf[(b->scroll.index-1 + b->scroll.size) % b->scroll.size];
+ }
+ if (b->scroll.below) {
+ if (as)
+ *as = &b->scroll.buf[b->scroll.index];
+ if (ae)
+ *ae = &b->scroll.buf[(b->scroll.index + b->scroll.below-1) % b->scroll.size];
+ }
+}
+
+static
+Row *
+browfirst(Buffer *b)
+{
+ Row *bstart;
+ if (!b->scroll.size || !b->scroll.above)
+ return b->row;
+ bboundary(b, &bstart, nil, nil, nil);
+ return bstart;
+}
+
+static
+Row *
+browlast(Buffer *b)
+{
+ Row *aend;
+ if (!b->scroll.size || !b->scroll.below)
+ return b->row + b->h - 1;
+ bboundary(b, nil, nil, nil, &aend);
+ return aend;
+}
+
+static
+Row *
+brownext(Buffer *b, Row *row)
+{
+ Row *before_start, *before_end, *after_start, *after_end;
+ Row *first = b->row, *last = b->row + b->h - 1;
+
+ if (!row)
+ return nil;
+
+ bboundary(b, &before_start, &before_end, &after_start, &after_end);
+
+ if (row >= first && row < last)
+ return ++row;
+ if (row == last)
+ return after_start;
+ if (row == before_end)
+ return first;
+ if (row == after_end)
+ return nil;
+ if (row == &b->scroll.buf[b->scroll.size - 1])
+ return b->scroll.buf;
+ return ++row;
+}
+
+static
+Row *
+bprevrow(Buffer *b, Row *row)
+{
+ Row *before_start, *before_end, *after_start, *after_end;
+ Row *first = b->row, *last = b->row + b->h - 1;
+
+ if (!row)
+ return nil;
+
+ bboundary(b, &before_start, &before_end, &after_start, &after_end);
+
+ if (row > first && row <= last)
+ return --row;
+ if (row == first)
+ return before_end;
+ if (row == before_start)
+ return nil;
+ if (row == after_start)
+ return last;
+ if (row == b->scroll.buf)
+ return &b->scroll.buf[b->scroll.size - 1];
+ return --row;
+}
+
+// -----------------------------------------------------------------------
+// windows
+
+Window *
+wmake(Window *root, int top, int left, int w, int h, int scroll)
+{
+ Window *child, *it;
+
+ child = calloc(1, sizeof(*child));
+ child->top = top;
+ child->left = left;
+ child->parent = root;
+ if (root) {
+ if (root->child) {
+ for (it = root->child; it->link != nil; it = it->link)
+ ;
+ it->link = child;
+ } else
+ root->child = child;
+
+ child->curvis = root->curvis;
+ child->blink = root->blink;
+ }
+
+ if (!binit((Buffer*)child, w, h, scroll)) {
+ free(child);
+ return nil;
+ }
+
+ return child;
+}
+
+void
+wfree(Window *win)
+{
+ free(win);
+}
+
+void
+wresize(Window *win, int w, int h)
+{
+ bresize((Buffer*)win, w, h);
+}
+
+/* TODO: more sophisticated damage tracking */
+void
+wputrune(Window *win, rune r)
+{
+ Row *row = win->row + win->cur.row;
+ Cell *cell = row->cells + win->cur.col;
+
+ cell->pen = win->pen;
+ cell->txt = r;
+
+ if (win->cur.col++ >= win->w) {
+ win->cur.col = 0;
+ if (win->cur.row++ >= win->h)
+ win->cur.row = win->h-1;
+ }
+ row->dirty = 1;
+}
+
+void
+wscroll(Window *win, int s)
+{
+ bscroll((Buffer*)win, s);
+}