Files
tmux/screen-redraw.c
Nicholas Marriott cd755fa4b2 Add scrollbar draw.
2026-06-16 17:02:44 +01:00

1174 lines
28 KiB
C

/* $OpenBSD$ */
/*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "tmux.h"
/* Type of span in the scene. */
enum redraw_span_type {
REDRAW_SPAN_PANE, /* inside a pane */
REDRAW_SPAN_OUTSIDE, /* outside the window */
REDRAW_SPAN_EMPTY, /* inside the window but nothing visible */
REDRAW_SPAN_STATUS, /* pane status line */
REDRAW_SPAN_BORDER, /* pane border */
REDRAW_SPAN_SCROLLBAR, /* pane scrollbar */
};
#define REDRAW_SPAN_TYPES 6
/* Border connections to adjacent cells. */
#define REDRAW_BORDER_L 0x1
#define REDRAW_BORDER_R 0x2
#define REDRAW_BORDER_U 0x4
#define REDRAW_BORDER_D 0x8
/* Span flags. */
#define REDRAW_BORDER_IS_ARROW 0x1
#define REDRAW_SCROLLBAR_LEFT 0x2
#define REDRAW_SCROLLBAR_RIGHT 0x4
/* Draw operations. */
#define REDRAW_DRAW_PANE 0x1
#define REDRAW_DRAW_BORDER 0x2
#define REDRAW_DRAW_STATUS 0x4
#define REDRAW_DRAW_SCROLLBAR 0x8
#define REDRAW_DRAW_ALL 0xff
/* Data for a span. */
struct redraw_span_data {
enum redraw_span_type type;
union {
struct {
/* Pane this span belongs to. */
struct window_pane *wp;
/* Position of span inside the pane. */
u_int px;
u_int py;
} p;
struct {
/* Adjacent panes on each side. */
struct window_pane *top_wp;
struct window_pane *bottom_wp;
struct window_pane *left_wp;
struct window_pane *right_wp;
/*
* The pane this span belongs if that is known when the
* scene is built. This is used for the half coloured
* active pane indicator.
*/
struct window_pane *style_wp;
/* Cell type for this span. */
int cell_type;
int cell_mask;
/* Line styles for this span. */
enum pane_lines top_lines;
enum pane_lines bottom_lines;
enum pane_lines left_lines;
enum pane_lines right_lines;
/* Flags for this span. */
int flags;
} b;
struct {
/* The pane and the offset into the status line. */
struct window_pane *wp;
u_int offset;
} st;
struct {
/* Pane this span belongs to. */
struct window_pane *wp;
/* Line within the scrollbar. */
u_int y;
/* Full height of scrollbar. */
u_int height;
/* Flags for this span. */
int flags;
} sb;
};
};
/* A span of cells of the same type inside a line. */
struct redraw_span {
u_int x;
u_int width;
struct redraw_span_data data;
TAILQ_ENTRY(redraw_span) entry;
};
TAILQ_HEAD(redraw_spans, redraw_span);
/* A visible line on the client. */
struct redraw_line {
struct redraw_spans spans[REDRAW_SPAN_TYPES];
};
/* A scene representing all the spans on the client. */
struct redraw_scene {
struct client *c;
struct window *w;
u_int sx;
u_int sy;
u_int ox;
u_int oy;
struct redraw_line *lines;
};
/* Cell for building the scene. */
struct redraw_build_cell {
struct redraw_span_data data;
};
/* Context for building the scene. */
struct redraw_build_ctx {
struct client *c;
struct window *w;
u_int ox;
u_int oy;
u_int sx;
u_int sy;
int sb;
int sbp;
int ind;
struct redraw_build_cell *cells;
};
/* Initialize the context for building scene. */
static void
screen_redraw_set_context(struct client *c, struct redraw_build_ctx *bctx)
{
struct session *s = c->session;
struct window *w = s->curw->window;
struct options *oo = w->options;
memset(bctx, 0, sizeof *bctx);
bctx->c = c;
bctx->w = w;
tty_window_offset(&c->tty, &bctx->ox, &bctx->oy, &bctx->sx, &bctx->sy);
bctx->sb = options_get_number(oo, "pane-scrollbars");
bctx->sbp = options_get_number(oo, "pane-scrollbars-position");
bctx->ind = options_get_number(oo, "pane-border-indicators");
}
/* Return a cell. */
static struct redraw_build_cell *
screen_redraw_cell(struct redraw_build_ctx *bctx, u_int x, u_int y)
{
return (&bctx->cells[(y * bctx->sx) + x]);
}
/* Reset cell to either empty or outside the window. */
static void
screen_redraw_reset_cell(struct redraw_build_ctx *bctx, u_int x, u_int y)
{
struct redraw_build_cell *bc = screen_redraw_cell(bctx, x, y);
struct window *w = bctx->w;
memset(bc, 0, sizeof *bc);
if (bctx->ox + x <= w->sx && bctx->oy + y <= w->sy)
bc->data.type = REDRAW_SPAN_EMPTY;
else
bc->data.type = REDRAW_SPAN_OUTSIDE;
}
/* Convert window position to scene position. */
static int
screen_redraw_window_scene(struct redraw_build_ctx *bctx, int wx, int wy,
u_int *x, u_int *y)
{
if (wx < 0 || wy < 0)
return (0);
if ((u_int)wx > bctx->w->sx || (u_int)wy > bctx->w->sy)
return (0);
if (wx < (int)bctx->ox || wy < (int)bctx->oy)
return (0);
wx -= bctx->ox;
wy -= bctx->oy;
if (wx < 0 || wy < 0)
return (0);
if ((u_int)wx >= bctx->sx || (u_int)wy >= bctx->sy)
return (0);
*x = wx;
*y = wy;
return (1);
}
/* Convert pane position to scene. */
static int
screen_redraw_pane_scene(struct redraw_build_ctx *bctx,
struct window_pane *wp, int px, int py, u_int *x, u_int *y)
{
int wx = wp->xoff + px, wy = wp->yoff + py;
return (screen_redraw_window_scene(bctx, wx, wy, x, y));
}
/* Convert redraw border mask to a cell type. */
static int
screen_redraw_get_cell_type(int mask)
{
switch (mask) {
case REDRAW_BORDER_L|REDRAW_BORDER_R|REDRAW_BORDER_U|REDRAW_BORDER_D:
return (CELL_JOIN);
case REDRAW_BORDER_L|REDRAW_BORDER_R|REDRAW_BORDER_U:
return (CELL_BOTTOMJOIN);
case REDRAW_BORDER_L|REDRAW_BORDER_R|REDRAW_BORDER_D:
return (CELL_TOPJOIN);
case REDRAW_BORDER_L|REDRAW_BORDER_R:
case REDRAW_BORDER_L:
case REDRAW_BORDER_R:
return (CELL_LEFTRIGHT);
case REDRAW_BORDER_L|REDRAW_BORDER_U|REDRAW_BORDER_D:
return (CELL_RIGHTJOIN);
case REDRAW_BORDER_L|REDRAW_BORDER_U:
return (CELL_BOTTOMRIGHT);
case REDRAW_BORDER_L|REDRAW_BORDER_D:
return (CELL_TOPRIGHT);
case REDRAW_BORDER_R|REDRAW_BORDER_U|REDRAW_BORDER_D:
return (CELL_LEFTJOIN);
case REDRAW_BORDER_R|REDRAW_BORDER_U:
return (CELL_BOTTOMLEFT);
case REDRAW_BORDER_R|REDRAW_BORDER_D:
return (CELL_TOPLEFT);
case REDRAW_BORDER_U|REDRAW_BORDER_D:
case REDRAW_BORDER_U:
case REDRAW_BORDER_D:
return (CELL_TOPBOTTOM);
}
return (CELL_OUTSIDE);
}
/* Return if this cell has exactly two panes with a shared border. */
static int
screen_redraw_two_panes(struct window *w, enum layout_type *type)
{
struct window_pane *wp;
u_int count = 0;
TAILQ_FOREACH(wp, &w->panes, entry) {
if (window_pane_is_floating(wp) || wp->layout_cell == NULL)
continue;
count++;
if (count > 2 || wp->layout_cell->parent == NULL)
return (0);
*type = wp->layout_cell->parent->type;
}
return (count == 2);
}
/* Clear the cells covered by a floating pane. */
static void
screen_redraw_clear_floating_pane(struct redraw_build_ctx *bctx,
struct window_pane *wp, int sb_w, int sb_left)
{
enum pane_lines pane_lines;
u_int x, y;
int left, right, top, bottom, wx, wy;
if (!window_pane_is_floating(wp))
return;
pane_lines = window_pane_get_pane_lines(wp);
if (pane_lines == PANE_LINES_NONE) {
left = wp->xoff;
right = wp->xoff + (int)wp->sx - 1;
top = wp->yoff;
bottom = wp->yoff + (int)wp->sy - 1;
} else {
left = wp->xoff - 1;
right = wp->xoff + (int)wp->sx;
top = wp->yoff - 1;
bottom = wp->yoff + (int)wp->sy;
}
if (sb_left)
left -= sb_w;
else
right += sb_w;
for (wy = top; wy <= bottom; wy++) {
for (wx = left; wx <= right; wx++) {
if (screen_redraw_window_scene(bctx, wx, wy, &x, &y))
screen_redraw_reset_cell(bctx, x, y);
}
}
}
/* Mark pane inside data. */
static void
screen_redraw_mark_pane_inside(struct redraw_build_ctx *bctx,
struct window_pane *wp)
{
struct redraw_build_cell *bc;
u_int px, py, x, y;
for (py = 0; py < wp->sy; py++) {
for (px = 0; px < wp->sx; px++) {
if (!screen_redraw_pane_scene(bctx, wp, px, py, &x, &y))
continue;
bc = screen_redraw_cell(bctx, x, y);
memset(bc, 0, sizeof *bc);
bc->data.type = REDRAW_SPAN_PANE;
bc->data.p.wp = wp;
bc->data.p.px = px;
bc->data.p.py = py;
}
}
}
/* Mark scrollbar data. */
static void
screen_redraw_mark_pane_scrollbar(struct redraw_build_ctx *bctx,
struct window_pane *wp, int sb_w, int sb_left)
{
struct redraw_build_cell *bc;
u_int x, y;
int wx, wy, sx, ex;
u_int sy;
if (sb_w == 0)
return;
if (sb_left) {
sx = wp->xoff - sb_w;
ex = wp->xoff - 1;
} else {
sx = wp->xoff + (int)wp->sx;
ex = sx + sb_w - 1;
}
for (sy = 0; sy < wp->sy; sy++) {
wy = wp->yoff + (int)sy;
for (wx = sx; wx <= ex; wx++) {
if (!screen_redraw_window_scene(bctx, wx, wy, &x, &y))
continue;
bc = screen_redraw_cell(bctx, x, y);
memset(bc, 0, sizeof *bc);
bc->data.type = REDRAW_SPAN_SCROLLBAR;
bc->data.sb.wp = wp;
bc->data.sb.y = sy;
bc->data.sb.height = wp->sy;
if (sb_left)
bc->data.sb.flags |= REDRAW_SCROLLBAR_LEFT;
else
bc->data.sb.flags |= REDRAW_SCROLLBAR_RIGHT;
}
}
}
/*
* Mark one border cell. If a non-border cell is being marked as a border,
* replace it. If it is already a border, merge it.
*/
static void
screen_redraw_mark_border_cell(struct redraw_build_ctx *bctx, int wx, int wy,
struct window_pane *wp, int top_owner, int bottom_owner, int mask)
{
struct redraw_build_cell *bc;
enum pane_lines pane_lines;
u_int x, y;
if (!screen_redraw_window_scene(bctx, wx, wy, &x, &y))
return;
bc = screen_redraw_cell(bctx, x, y);
if (bc->data.type != REDRAW_SPAN_BORDER) {
if (bc->data.type != REDRAW_SPAN_EMPTY)
return;
memset(bc, 0, sizeof *bc);
bc->data.type = REDRAW_SPAN_BORDER;
}
pane_lines = window_pane_get_pane_lines(wp);
if (top_owner) {
bc->data.b.top_wp = wp;
bc->data.b.top_lines = pane_lines;
}
if (bottom_owner) {
bc->data.b.bottom_wp = wp;
bc->data.b.bottom_lines = pane_lines;
}
if (mask & (REDRAW_BORDER_U|REDRAW_BORDER_D)) {
if (wx < wp->xoff) {
bc->data.b.right_wp = wp;
bc->data.b.right_lines = pane_lines;
} else if (wx >= wp->xoff + (int)wp->sx) {
bc->data.b.left_wp = wp;
bc->data.b.left_lines = pane_lines;
}
}
mask |= bc->data.b.cell_mask;
bc->data.b.cell_mask = mask;
bc->data.b.cell_type = screen_redraw_get_cell_type(mask);
}
/* Mark area available for pane status line. */
static void
screen_redraw_mark_border_status(struct redraw_build_ctx *bctx,
struct window_pane *wp, int left, int right, int top, int bottom)
{
struct redraw_build_cell *bc;
u_int x, y, offset = 0;
int pane_status, wy, sx, ex, wx;
pane_status = window_pane_get_pane_status(wp);
if (pane_status == PANE_STATUS_OFF)
return;
if (pane_status == PANE_STATUS_TOP)
wy = top;
else
wy = bottom;
sx = left + 1;
ex = right - 1;
if (bctx->ind == PANE_BORDER_ARROWS || bctx->ind == PANE_BORDER_BOTH) {
wx = wp->xoff + 1;
if (wx >= sx && wx <= ex)
sx = wx + 1;
}
if (sx > ex)
return;
for (wx = sx; wx <= ex; wx++, offset++) {
if (!screen_redraw_window_scene(bctx, wx, wy, &x, &y))
continue;
bc = screen_redraw_cell(bctx, x, y);
if (bc->data.type != REDRAW_SPAN_BORDER)
continue;
bc->data.type = REDRAW_SPAN_STATUS;
bc->data.st.wp = wp;
bc->data.st.offset = offset;
}
}
/* Mark where indicator arrows will go, if enabled. */
static void
screen_redraw_mark_border_arrows(struct redraw_build_ctx *bctx,
struct window_pane *wp, int left, int right, int top, int bottom)
{
struct redraw_build_cell *bc;
u_int x, y;
int wx, wy;
if (bctx->ind != PANE_BORDER_ARROWS && bctx->ind != PANE_BORDER_BOTH)
return;
wx = wp->xoff + 1;
if (wx >= left && wx <= right) {
wy = top;
if (screen_redraw_window_scene(bctx, wx, wy, &x, &y)) {
bc = screen_redraw_cell(bctx, x, y);
if (bc->data.type == REDRAW_SPAN_BORDER)
bc->data.b.flags |= REDRAW_BORDER_IS_ARROW;
}
wy = bottom;
if (screen_redraw_window_scene(bctx, wx, wy, &x, &y)) {
bc = screen_redraw_cell(bctx, x, y);
if (bc->data.type == REDRAW_SPAN_BORDER)
bc->data.b.flags |= REDRAW_BORDER_IS_ARROW;
}
}
wy = wp->yoff + 1;
if (wy >= top && wy <= bottom) {
wx = left;
if (screen_redraw_window_scene(bctx, wx, wy, &x, &y)) {
bc = screen_redraw_cell(bctx, x, y);
if (bc->data.type == REDRAW_SPAN_BORDER)
bc->data.b.flags |= REDRAW_BORDER_IS_ARROW;
}
wx = right;
if (screen_redraw_window_scene(bctx, wx, wy, &x, &y)) {
bc = screen_redraw_cell(bctx, x, y);
if (bc->data.type == REDRAW_SPAN_BORDER)
bc->data.b.flags |= REDRAW_BORDER_IS_ARROW;
}
}
}
/* Mark pane borders. */
static void
screen_redraw_mark_pane_borders(struct redraw_build_ctx *bctx,
struct window_pane *wp, int sb_w, int sb_left)
{
enum pane_lines pane_lines = window_pane_get_pane_lines(wp);
int pane_status, left, right, top, bottom, wx, wy;
int draw_top, draw_bottom, draw_left, draw_right, mask = 0;
if (window_pane_is_floating(wp) && pane_lines == PANE_LINES_NONE)
return;
pane_status = window_pane_get_pane_status(wp);
left = wp->xoff - 1;
right = wp->xoff + (int)wp->sx;
if (sb_w != 0) {
if (sb_left)
left -= sb_w;
else
right += sb_w;
}
top = wp->yoff - 1;
bottom = wp->yoff + (int)wp->sy;
draw_left = (left >= 0);
draw_right = (right <= (int)bctx->w->sx);
draw_top = (top >= 0);
draw_bottom = (bottom <= (int)bctx->w->sy);
if (pane_status == PANE_STATUS_TOP)
draw_bottom = 0;
else if (pane_status == PANE_STATUS_BOTTOM)
draw_top = 0;
if (draw_top) {
for (wx = left; wx <= right; wx++) {
mask = 0;
if (wx > left)
mask |= REDRAW_BORDER_L;
if (wx < right)
mask |= REDRAW_BORDER_R;
screen_redraw_mark_border_cell(bctx, wx, top, wp,
0, 1, mask);
}
}
if (draw_bottom) {
for (wx = left; wx <= right; wx++) {
mask = 0;
if (wx > left)
mask |= REDRAW_BORDER_L;
if (wx < right)
mask |= REDRAW_BORDER_R;
screen_redraw_mark_border_cell(bctx, wx, bottom, wp,
1, 0, ((wx > left) ? REDRAW_BORDER_L : 0) |
((wx < right) ? REDRAW_BORDER_R : 0));
}
}
if (draw_left) {
for (wy = top; wy <= bottom; wy++) {
mask = 0;
if (wy > top)
mask |= REDRAW_BORDER_U;
if (wy < bottom)
mask |= REDRAW_BORDER_D;
screen_redraw_mark_border_cell(bctx, left, wy, wp,
0, 0, mask);
}
}
if (draw_right) {
for (wy = top; wy <= bottom; wy++) {
mask = 0;
if (wy > top)
mask |= REDRAW_BORDER_U;
if (wy < bottom)
mask |= REDRAW_BORDER_D;
screen_redraw_mark_border_cell(bctx, right, wy, wp,
0, 0, mask);
}
}
screen_redraw_mark_border_status(bctx, wp, left, right, top, bottom);
screen_redraw_mark_border_arrows(bctx, wp, left, right, top, bottom);
}
/*
* Mark an entire pane in the build grid. Floating panes overwrite anything
* already below them.
*/
static void
screen_redraw_mark_pane(struct redraw_build_ctx *bctx, struct window_pane *wp)
{
int sb_w = 0, sb_left = 0;
if (!window_pane_is_visible(wp))
return;
if (window_pane_show_scrollbar(wp, bctx->sb))
sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
if (sb_w != 0 && bctx->sbp == PANE_SCROLLBARS_LEFT)
sb_left = 1;
screen_redraw_clear_floating_pane(bctx, wp, sb_w, sb_left);
screen_redraw_mark_pane_inside(bctx, wp);
screen_redraw_mark_pane_borders(bctx, wp, sb_w, sb_left);
screen_redraw_mark_pane_scrollbar(bctx, wp, sb_w, sb_left);
}
/* Mark the indicator. */
static void
screen_redraw_mark_two_pane_colours(struct redraw_build_ctx *bctx)
{
struct redraw_build_cell *bc;
struct redraw_span_data *sd;
enum layout_type type;
u_int x, y, wx, wy;
if (bctx->ind != PANE_BORDER_COLOUR && bctx->ind != PANE_BORDER_BOTH)
return;
if (!screen_redraw_two_panes(bctx->w, &type))
return;
for (y = 0; y < bctx->sy; y++) {
for (x = 0; x < bctx->sx; x++) {
bc = screen_redraw_cell(bctx, x, y);
if (bc->data.type != REDRAW_SPAN_BORDER)
continue;
sd = &bc->data;
wx = bctx->ox + x;
wy = bctx->oy + y;
if (type == LAYOUT_LEFTRIGHT &&
sd->b.left_wp != NULL &&
sd->b.right_wp != NULL) {
if (wy <= bctx->w->sy / 2)
sd->b.style_wp = sd->b.left_wp;
else
sd->b.style_wp = sd->b.right_wp;
} else if (type == LAYOUT_TOPBOTTOM &&
sd->b.top_wp != NULL &&
sd->b.bottom_wp != NULL) {
if (wx <= bctx->w->sx / 2)
sd->b.style_wp = sd->b.top_wp;
else
sd->b.style_wp = sd->b.bottom_wp;
}
}
}
}
/* Return true if two adjacent build data can be coalesced into one span. */
static int
screen_redraw_data_cmp(struct redraw_build_cell *a, struct redraw_build_cell *b)
{
struct redraw_span_data *ad = &a->data, *bd = &b->data;
if (ad->type != bd->type)
return (0);
switch (ad->type) {
case REDRAW_SPAN_PANE:
if (ad->p.wp != bd->p.wp ||
ad->p.py != bd->p.py ||
ad->p.px + 1 != bd->p.px)
return (0);
return (1);
case REDRAW_SPAN_BORDER:
if (ad->b.top_wp != bd->b.top_wp ||
ad->b.bottom_wp != bd->b.bottom_wp ||
ad->b.left_wp != bd->b.left_wp ||
ad->b.right_wp != bd->b.right_wp ||
ad->b.style_wp != bd->b.style_wp ||
ad->b.top_lines != bd->b.top_lines ||
ad->b.bottom_lines != bd->b.bottom_lines ||
ad->b.left_lines != bd->b.left_lines ||
ad->b.right_lines != bd->b.right_lines ||
ad->b.cell_type != bd->b.cell_type ||
ad->b.cell_mask != bd->b.cell_mask ||
ad->b.flags != bd->b.flags)
return (0);
if (ad->b.flags & REDRAW_BORDER_IS_ARROW)
return (0);
return (1);
case REDRAW_SPAN_STATUS:
if (ad->st.wp != bd->st.wp ||
ad->st.offset + 1 != bd->st.offset)
return (0);
return (1);
case REDRAW_SPAN_SCROLLBAR:
if (ad->sb.wp != bd->sb.wp ||
ad->sb.y != bd->sb.y ||
ad->sb.height != bd->sb.height ||
ad->sb.flags != bd->sb.flags)
return (0);
return (1);
case REDRAW_SPAN_OUTSIDE:
case REDRAW_SPAN_EMPTY:
return (1);
}
return (0);
}
/* Convert the cells into spans. */
static void
screen_redraw_finish_scene(struct redraw_build_ctx *bctx,
struct redraw_scene *scene)
{
struct redraw_build_cell *bc, *last;
struct redraw_line *line;
struct redraw_span *span;
enum redraw_span_type type;
u_int x, y, x0;
for (y = 0; y < bctx->sy; y++) {
line = &scene->lines[y];
x = 0;
while (x < bctx->sx) {
x0 = x;
last = screen_redraw_cell(bctx, x, y);
x++;
while (x < bctx->sx) {
bc = screen_redraw_cell(bctx, x, y);
if (!screen_redraw_data_cmp(last, bc))
break;
last = bc;
x++;
}
bc = screen_redraw_cell(bctx, x0, y);
type = bc->data.type;
span = xcalloc(1, sizeof *span);
span->x = x0;
span->width = x - x0;
span->data = bc->data;
TAILQ_INSERT_TAIL(&line->spans[type], span, entry);
}
}
}
/*
* Build and return a redraw scene for c. The caller owns the scene and must
* free it with screen_redraw_free_scene().
*/
static struct redraw_scene *
screen_redraw_make_scene(struct client *c)
{
struct redraw_build_ctx bctx;
struct redraw_scene *scene;
struct window_pane *wp;
u_int x, y, type;
size_t ncells;
if (c->flags & CLIENT_SUSPENDED)
return (NULL);
screen_redraw_set_context(c, &bctx);
scene = xcalloc(1, sizeof *scene);
scene->c = c;
scene->w = bctx.w;
scene->sx = bctx.sx;
scene->sy = bctx.sy;
scene->ox = bctx.ox;
scene->oy = bctx.oy;
scene->lines = xcalloc(scene->sy, sizeof *scene->lines);
for (y = 0; y < scene->sy; y++) {
for (type = 0; type < REDRAW_SPAN_TYPES; type++)
TAILQ_INIT(&scene->lines[y].spans[type]);
}
if (bctx.sx != 0 && bctx.sy > SIZE_MAX / bctx.sx)
fatalx("%s: too many cells", __func__);
ncells = (size_t)bctx.sx * bctx.sy;
if (ncells > SIZE_MAX / sizeof *bctx.cells)
fatalx("%s: too many cells", __func__);
bctx.cells = xmalloc(ncells * sizeof *bctx.cells);
for (y = 0; y < bctx.sy; y++) {
for (x = 0; x < bctx.sx; x++)
screen_redraw_reset_cell(&bctx, x, y);
}
TAILQ_FOREACH_REVERSE(wp, &bctx.w->z_index, window_panes_zindex, zentry)
screen_redraw_mark_pane(&bctx, wp);
screen_redraw_mark_two_pane_colours(&bctx);
screen_redraw_finish_scene(&bctx, scene);
free(bctx.cells);
return (scene);
}
/* Free a scene. */
static void
screen_redraw_free_scene(struct redraw_scene *scene)
{
struct redraw_spans *spans;
struct redraw_span *span, *span1;
u_int y, type;
for (y = 0; y < scene->sy; y++) {
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
spans = &scene->lines[y].spans[type];
TAILQ_FOREACH_SAFE(span, spans, entry, span1) {
TAILQ_REMOVE(spans, span, entry);
free(span);
}
}
}
free(scene->lines);
free(scene);
}
/* Is this span adjacent to this pane? */
static int
screen_redraw_span_has_pane(struct redraw_span *span, struct window_pane *wp)
{
if (span->data.b.top_wp == wp)
return (1);
if (span->data.b.bottom_wp == wp)
return (1);
if (span->data.b.left_wp == wp)
return (1);
if (span->data.b.right_wp == wp)
return (1);
return (0);
}
/* Draw a pane span. */
static void
screen_redraw_draw_pane_span(struct redraw_scene *scene,
struct redraw_span *span, u_int x, u_int y, u_int n)
{
struct client *c = scene->c;
struct tty *tty = &c->tty;
struct window_pane *wp = span->data.p.wp;
struct screen *s = wp->screen;
struct grid_cell defaults;
struct tty_style_ctx style_ctx;
u_int px, py;
//XXX sync?
tty_default_colours(&defaults, wp);
style_ctx.defaults = &defaults;
style_ctx.palette = &wp->palette;
style_ctx.hyperlinks = s->hyperlinks;
px = span->data.p.px + (x - span->x);
py = span->data.p.py;
tty_draw_line(tty, s, px, py, n, x, y, &style_ctx);
}
/* Draw a border span. */
static void
screen_redraw_draw_border_span(struct redraw_scene *scene,
struct redraw_span *span, u_int x, u_int y, u_int n)
{
struct client *c = scene->c;
struct tty *tty = &c->tty;
tty_cursor(tty, x, y);
for (x = 0; x < n; x++)
tty_putc(tty, '-');
}
/* Draw a status span. */
static void
screen_redraw_draw_status_span(struct redraw_scene *scene,
struct redraw_span *span, u_int x, u_int y, u_int n)
{
struct client *c = scene->c;
struct tty *tty = &c->tty;
tty_cursor(tty, x, y);
for (x = 0; x < n; x++)
tty_putc(tty, 's');
}
/* Draw a scrollbar span. */
static void
screen_redraw_draw_scrollbar_span(struct redraw_scene *scene,
struct redraw_span *span, u_int x, u_int y, u_int n)
{
struct window_pane *wp = span->data.sb.wp;
struct screen *s = wp->screen;
struct tty *tty = &scene->c->tty;
struct style *sb_style = &wp->scrollbar_style;
struct grid_cell gc, slgc, *gcp;
double pct_view;
u_int total_height, slider_h, slider_y;
u_int sb_h = span->data.sb.height;
u_int sb_y = span->data.sb.y;
u_int i, off, sb_w, sb_pad;
int cm_y, cm_size;
if (window_pane_mode(wp) == WINDOW_PANE_NO_MODE) {
total_height = screen_size_y(s) + screen_hsize(s);
if (total_height == 0)
return;
pct_view = (double)sb_h / total_height;
slider_h = (double)sb_h * pct_view;
slider_y = sb_h - slider_h;
} else {
if (TAILQ_FIRST(&wp->modes) == NULL)
return;
if (window_copy_get_current_offset(wp, &cm_y, &cm_size) == 0)
return;
total_height = cm_size + sb_h;
if (total_height == 0)
return;
pct_view = (double)sb_h / total_height;
slider_h = (double)sb_h * pct_view;
slider_y = (sb_h + 1) * ((double)cm_y / total_height);
}
if (slider_h < 1)
slider_h = 1;
if (slider_y >= sb_h)
slider_y = sb_h - 1;
wp->sb_slider_y = slider_y;
wp->sb_slider_h = slider_h;
gc = sb_style->gc;
memcpy(&slgc, &gc, sizeof slgc);
slgc.fg = gc.bg;
slgc.bg = gc.fg;
sb_w = sb_style->width;
sb_pad = sb_style->pad;
off = x - span->x;
tty_cursor(tty, x, y);
for (i = 0; i < n; i++) {
if (span->data.sb.flags & REDRAW_SCROLLBAR_LEFT) {
if (off + i >= sb_w && off + i < sb_w + sb_pad) {
tty_cell(tty, &grid_default_cell, NULL);
continue;
}
} else {
if (off + i < sb_pad) {
tty_cell(tty, &grid_default_cell, NULL);
continue;
}
}
if (sb_y >= slider_y && sb_y < slider_y + slider_h)
gcp = &slgc;
else
gcp = &gc;
tty_cell(tty, gcp, NULL);
}
}
/* Draw a span. */
static void
screen_redraw_draw_span(struct redraw_scene *scene, struct redraw_span *span,
u_int y)
{
struct client *c = scene->c;
struct tty *tty = &c->tty;
struct visible_ranges *r;
struct visible_range *rr;
u_int i, x, n;
r = tty_check_overlay_range(tty, span->x, y, span->width);
for (i = 0; i < r->used; i++) {
rr = &r->ranges[i];
if (rr->nx == 0)
continue;
x = rr->px;
n = rr->nx;
switch (span->data.type) {
case REDRAW_SPAN_PANE:
screen_redraw_draw_pane_span(scene, span, x, y, n);
break;
case REDRAW_SPAN_BORDER:
case REDRAW_SPAN_EMPTY:
case REDRAW_SPAN_OUTSIDE:
screen_redraw_draw_border_span(scene, span, x, y, n);
break;
case REDRAW_SPAN_STATUS:
screen_redraw_draw_status_span(scene, span, x, y, n);
break;
case REDRAW_SPAN_SCROLLBAR:
screen_redraw_draw_scrollbar_span(scene, span, x, y, n);
break;
}
}
}
/* Draw pane lines. */
static void
screen_redraw_draw_pane_lines(struct redraw_scene *scene, u_int status_lines,
struct window_pane *wp, int flags)
{
struct redraw_line *line;
struct redraw_spans *spans;
struct redraw_span *span;
u_int y, cy, top, bottom;
if (wp->yoff < (int)scene->oy)
top = 0;
else
top = wp->yoff - scene->oy;
if (wp->yoff + wp->sy <= scene->oy)
bottom = 0;
else
bottom = wp->yoff + wp->sy - scene->oy;
if (bottom > scene->sy)
bottom = scene->sy;
for (y = top; y < bottom; y++) {
line = &scene->lines[y];
cy = status_lines + y;
if (flags & REDRAW_DRAW_PANE) {
spans = &line->spans[REDRAW_SPAN_PANE];
TAILQ_FOREACH(span, spans, entry) {
if (span->data.p.wp != wp)
continue;
screen_redraw_draw_span(scene, span, cy);
}
}
if (flags & REDRAW_DRAW_SCROLLBAR) {
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
TAILQ_FOREACH(span, spans, entry) {
if (span->data.sb.wp != wp)
continue;
cy = status_lines + y;
screen_redraw_draw_span(scene, span, cy);
}
}
}
}
/* Draw lines. */
static void
screen_redraw_draw_lines(struct redraw_scene *scene, u_int status_lines,
int flags)
{
struct redraw_line *line;
struct redraw_spans *spans;
struct redraw_span *span;
u_int y, cy, type;
for (y = 0; y < scene->sy; y++) {
line = &scene->lines[y];
cy = status_lines + y;
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
if (flags != REDRAW_DRAW_ALL) {
switch (type) {
case REDRAW_SPAN_PANE:
if (~flags & REDRAW_DRAW_PANE)
continue;
break;
case REDRAW_SPAN_BORDER:
if (~flags & REDRAW_DRAW_BORDER)
continue;
break;
case REDRAW_SPAN_STATUS:
if (~flags & REDRAW_DRAW_STATUS)
continue;
break;
case REDRAW_SPAN_SCROLLBAR:
if (~flags & REDRAW_DRAW_SCROLLBAR)
continue;
break;
default:
continue;
}
}
spans = &line->spans[type];
TAILQ_FOREACH(span, spans, entry)
screen_redraw_draw_span(scene, span, cy);
}
}
}
/* Draw scene to client. */
static void
screen_redraw_draw(struct client *c, struct window_pane *wp, int flags)
{
struct session *s = c->session;
struct tty *tty = &c->tty;
struct redraw_scene *scene;
u_int status_lines = 0;
if (c->flags & CLIENT_SUSPENDED)
return;
scene = screen_redraw_make_scene(c); //XXX
if (scene == NULL)
return;
if (options_get_number(s->options, "status-position") == 0)
status_lines = status_line_size(c);
tty_sync_start(tty);
tty_update_mode(tty, 0, NULL);
if (wp != NULL)
screen_redraw_draw_pane_lines(scene, status_lines, wp, flags);
else
screen_redraw_draw_lines(scene, status_lines, flags);
tty_reset(tty);
tty_sync_end(tty);
screen_redraw_free_scene(scene); //XXX
}
/* Draw screen. */
void
screen_redraw_screen(struct client *c)
{
int flags = 0;
if (c->flags & CLIENT_REDRAWWINDOW)
screen_redraw_draw(c, NULL, REDRAW_DRAW_ALL);
else {
if (c->flags & CLIENT_REDRAWBORDERS)
flags |= (REDRAW_DRAW_BORDER|REDRAW_DRAW_STATUS);
if (c->flags & CLIENT_REDRAWSTATUS)
flags |= REDRAW_DRAW_STATUS;
screen_redraw_draw(c, NULL, flags);
}
//XXX client status line
//XXX overlays
}
/* Draw a single pane. */
void
screen_redraw_pane(struct client *c, struct window_pane *wp)
{
screen_redraw_draw(c, wp, REDRAW_DRAW_PANE|REDRAW_DRAW_SCROLLBAR);
}
/* Draw a pane's scrollbar. */
void
screen_redraw_pane_scrollbar(struct client *c, struct window_pane *wp)
{
screen_redraw_draw(c, wp, REDRAW_DRAW_SCROLLBAR);
}