#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); }