screen-redraw.c revision 1.1.1.16 1 /* $OpenBSD$ */
2
3 /*
4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott (at) gmail.com>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/types.h>
20
21 #include <stdlib.h>
22 #include <string.h>
23
24 #include "tmux.h"
25
26 static void screen_redraw_draw_borders(struct screen_redraw_ctx *);
27 static void screen_redraw_draw_panes(struct screen_redraw_ctx *);
28 static void screen_redraw_draw_status(struct screen_redraw_ctx *);
29 static void screen_redraw_draw_pane(struct screen_redraw_ctx *,
30 struct window_pane *);
31 static void screen_redraw_set_context(struct client *,
32 struct screen_redraw_ctx *);
33 static void screen_redraw_draw_pane_scrollbars(struct screen_redraw_ctx *);
34 static void screen_redraw_draw_scrollbar(struct screen_redraw_ctx *,
35 struct window_pane *, int, int, int, u_int, u_int, u_int);
36 static void screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *,
37 struct window_pane *);
38
39 #define START_ISOLATE "\342\201\246"
40 #define END_ISOLATE "\342\201\251"
41
42 /* Border in relation to a pane. */
43 enum screen_redraw_border_type {
44 SCREEN_REDRAW_OUTSIDE,
45 SCREEN_REDRAW_INSIDE,
46 SCREEN_REDRAW_BORDER_LEFT,
47 SCREEN_REDRAW_BORDER_RIGHT,
48 SCREEN_REDRAW_BORDER_TOP,
49 SCREEN_REDRAW_BORDER_BOTTOM
50 };
51 #define BORDER_MARKERS " +,.-"
52
53 /* Get cell border character. */
54 static void
55 screen_redraw_border_set(struct window *w, struct window_pane *wp,
56 enum pane_lines pane_lines, int cell_type, struct grid_cell *gc)
57 {
58 u_int idx;
59
60 if (cell_type == CELL_OUTSIDE && w->fill_character != NULL) {
61 utf8_copy(&gc->data, &w->fill_character[0]);
62 return;
63 }
64
65 switch (pane_lines) {
66 case PANE_LINES_NUMBER:
67 if (cell_type == CELL_OUTSIDE) {
68 gc->attr |= GRID_ATTR_CHARSET;
69 utf8_set(&gc->data, CELL_BORDERS[CELL_OUTSIDE]);
70 break;
71 }
72 gc->attr &= ~GRID_ATTR_CHARSET;
73 if (wp != NULL && window_pane_index(wp, &idx) == 0)
74 utf8_set(&gc->data, '0' + (idx % 10));
75 else
76 utf8_set(&gc->data, '*');
77 break;
78 case PANE_LINES_DOUBLE:
79 gc->attr &= ~GRID_ATTR_CHARSET;
80 utf8_copy(&gc->data, tty_acs_double_borders(cell_type));
81 break;
82 case PANE_LINES_HEAVY:
83 gc->attr &= ~GRID_ATTR_CHARSET;
84 utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type));
85 break;
86 case PANE_LINES_SIMPLE:
87 gc->attr &= ~GRID_ATTR_CHARSET;
88 utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]);
89 break;
90 case PANE_LINES_SPACES:
91 gc->attr &= ~GRID_ATTR_CHARSET;
92 utf8_set(&gc->data, ' ');
93 break;
94 default:
95 gc->attr |= GRID_ATTR_CHARSET;
96 utf8_set(&gc->data, CELL_BORDERS[cell_type]);
97 break;
98 }
99 }
100
101 /* Return if window has only two panes. */
102 static int
103 screen_redraw_two_panes(struct window *w, int direction)
104 {
105 struct window_pane *wp;
106
107 wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry);
108 if (wp == NULL)
109 return (0); /* one pane */
110 if (TAILQ_NEXT(wp, entry) != NULL)
111 return (0); /* more than two panes */
112 if (direction == 0 && wp->xoff == 0)
113 return (0);
114 if (direction == 1 && wp->yoff == 0)
115 return (0);
116 return (1);
117 }
118
119 /* Check if cell is on the border of a pane. */
120 static enum screen_redraw_border_type
121 screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp,
122 u_int px, u_int py)
123 {
124 struct options *oo = wp->window->options;
125 u_int ex = wp->xoff + wp->sx, ey = wp->yoff + wp->sy;
126 int hsplit = 0, vsplit = 0, pane_status = ctx->pane_status;
127 int pane_scrollbars = ctx->pane_scrollbars, sb_w = 0;
128 int sb_pos;
129
130 if (pane_scrollbars != 0)
131 sb_pos = ctx->pane_scrollbars_pos;
132 else
133 sb_pos = 0;
134
135 /* Inside pane. */
136 if (px >= wp->xoff && px < ex && py >= wp->yoff && py < ey)
137 return (SCREEN_REDRAW_INSIDE);
138
139 /* Get pane indicator. */
140 switch (options_get_number(oo, "pane-border-indicators")) {
141 case PANE_BORDER_COLOUR:
142 case PANE_BORDER_BOTH:
143 hsplit = screen_redraw_two_panes(wp->window, 0);
144 vsplit = screen_redraw_two_panes(wp->window, 1);
145 break;
146 }
147
148 /* Are scrollbars enabled? */
149 if (window_pane_show_scrollbar(wp, pane_scrollbars))
150 sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
151
152 /*
153 * Left/right borders. The wp->sy / 2 test is to colour only half the
154 * active window's border when there are two panes.
155 */
156 if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= ey) {
157 if (sb_pos == PANE_SCROLLBARS_LEFT) {
158 if (wp->xoff - sb_w == 0 && px == wp->sx + sb_w)
159 if (!hsplit || (hsplit && py <= wp->sy / 2))
160 return (SCREEN_REDRAW_BORDER_RIGHT);
161 if (wp->xoff - sb_w != 0) {
162 if (px == wp->xoff - sb_w - 1 &&
163 (!hsplit || (hsplit && py > wp->sy / 2)))
164 return (SCREEN_REDRAW_BORDER_LEFT);
165 if (px == wp->xoff + wp->sx + sb_w - 1)
166 return (SCREEN_REDRAW_BORDER_RIGHT);
167 }
168 } else { /* sb_pos == PANE_SCROLLBARS_RIGHT or disabled*/
169 if (wp->xoff == 0 && px == wp->sx + sb_w)
170 if (!hsplit || (hsplit && py <= wp->sy / 2))
171 return (SCREEN_REDRAW_BORDER_RIGHT);
172 if (wp->xoff != 0) {
173 if (px == wp->xoff - 1 &&
174 (!hsplit || (hsplit && py > wp->sy / 2)))
175 return (SCREEN_REDRAW_BORDER_LEFT);
176 if (px == wp->xoff + wp->sx + sb_w)
177 return (SCREEN_REDRAW_BORDER_RIGHT);
178 }
179 }
180 }
181
182 /* Top/bottom borders. */
183 if (vsplit && pane_status == PANE_STATUS_OFF && sb_w == 0) {
184 if (wp->yoff == 0 && py == wp->sy && px <= wp->sx / 2)
185 return (SCREEN_REDRAW_BORDER_BOTTOM);
186 if (wp->yoff != 0 && py == wp->yoff - 1 && px > wp->sx / 2)
187 return (SCREEN_REDRAW_BORDER_TOP);
188 } else {
189 if (sb_pos == PANE_SCROLLBARS_LEFT) {
190 if ((wp->xoff - sb_w == 0 || px >= wp->xoff - sb_w) &&
191 (px <= ex || (sb_w != 0 && px < ex + sb_w))) {
192 if (wp->yoff != 0 && py == wp->yoff - 1)
193 return (SCREEN_REDRAW_BORDER_TOP);
194 if (py == ey)
195 return (SCREEN_REDRAW_BORDER_BOTTOM);
196 }
197 } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */
198 if ((wp->xoff == 0 || px >= wp->xoff) &&
199 (px <= ex || (sb_w != 0 && px < ex + sb_w))) {
200 if (wp->yoff != 0 && py == wp->yoff - 1)
201 return (SCREEN_REDRAW_BORDER_TOP);
202 if (py == ey)
203 return (SCREEN_REDRAW_BORDER_BOTTOM);
204 }
205 }
206 }
207
208 /* Outside pane. */
209 return (SCREEN_REDRAW_OUTSIDE);
210 }
211
212 /* Check if a cell is on a border. */
213 static int
214 screen_redraw_cell_border(struct screen_redraw_ctx *ctx, u_int px, u_int py)
215 {
216 struct client *c = ctx->c;
217 struct window *w = c->session->curw->window;
218 struct window_pane *wp;
219 u_int sy = w->sy;
220
221 if (ctx->pane_status == PANE_STATUS_BOTTOM)
222 sy--;
223
224 /* Outside the window? */
225 if (px > w->sx || py > sy)
226 return (0);
227
228 /* On the window border? */
229 if (px == w->sx || py == sy)
230 return (1);
231
232 /* Check all the panes. */
233 TAILQ_FOREACH(wp, &w->panes, entry) {
234 if (!window_pane_visible(wp))
235 continue;
236 switch (screen_redraw_pane_border(ctx, wp, px, py)) {
237 case SCREEN_REDRAW_INSIDE:
238 return (0);
239 case SCREEN_REDRAW_OUTSIDE:
240 break;
241 default:
242 return (1);
243 }
244 }
245
246 return (0);
247 }
248
249 /* Work out type of border cell from surrounding cells. */
250 static int
251 screen_redraw_type_of_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py)
252 {
253 struct client *c = ctx->c;
254 int pane_status = ctx->pane_status;
255 struct window *w = c->session->curw->window;
256 u_int sx = w->sx, sy = w->sy;
257 int borders = 0;
258
259 if (pane_status == PANE_STATUS_BOTTOM)
260 sy--;
261
262 /* Is this outside the window? */
263 if (px > sx || py > sy)
264 return (CELL_OUTSIDE);
265
266 /*
267 * Construct a bitmask of whether the cells to the left (bit 8), right,
268 * top, and bottom (bit 1) of this cell are borders.
269 *
270 * bits 8 4 2 1: 2
271 * 8 + 4
272 * 1
273 */
274 if (px == 0 || screen_redraw_cell_border(ctx, px - 1, py))
275 borders |= 8;
276 if (px <= sx && screen_redraw_cell_border(ctx, px + 1, py))
277 borders |= 4;
278 if (pane_status == PANE_STATUS_TOP) {
279 if (py != 0 &&
280 screen_redraw_cell_border(ctx, px, py - 1))
281 borders |= 2;
282 if (screen_redraw_cell_border(ctx, px, py + 1))
283 borders |= 1;
284 } else if (pane_status == PANE_STATUS_BOTTOM) {
285 if (py == 0 ||
286 screen_redraw_cell_border(ctx, px, py - 1))
287 borders |= 2;
288 if (py != sy &&
289 screen_redraw_cell_border(ctx, px, py + 1))
290 borders |= 1;
291 } else {
292 if (py == 0 ||
293 screen_redraw_cell_border(ctx, px, py - 1))
294 borders |= 2;
295 if (screen_redraw_cell_border(ctx, px, py + 1))
296 borders |= 1;
297 }
298
299 /*
300 * Figure out what kind of border this cell is. Only one bit set
301 * doesn't make sense (can't have a border cell with no others
302 * connected).
303 */
304 switch (borders) {
305 case 15: /* 1111, left right top bottom */
306 return (CELL_JOIN);
307 case 14: /* 1110, left right top */
308 return (CELL_BOTTOMJOIN);
309 case 13: /* 1101, left right bottom */
310 return (CELL_TOPJOIN);
311 case 12: /* 1100, left right */
312 return (CELL_LEFTRIGHT);
313 case 11: /* 1011, left top bottom */
314 return (CELL_RIGHTJOIN);
315 case 10: /* 1010, left top */
316 return (CELL_BOTTOMRIGHT);
317 case 9: /* 1001, left bottom */
318 return (CELL_TOPRIGHT);
319 case 7: /* 0111, right top bottom */
320 return (CELL_LEFTJOIN);
321 case 6: /* 0110, right top */
322 return (CELL_BOTTOMLEFT);
323 case 5: /* 0101, right bottom */
324 return (CELL_TOPLEFT);
325 case 3: /* 0011, top bottom */
326 return (CELL_TOPBOTTOM);
327 }
328 return (CELL_OUTSIDE);
329 }
330
331 /* Check if cell inside a pane. */
332 static int
333 screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py,
334 struct window_pane **wpp)
335 {
336 struct client *c = ctx->c;
337 struct window *w = c->session->curw->window;
338 struct window_pane *wp, *active;
339 int pane_status = ctx->pane_status;
340 u_int sx = w->sx, sy = w->sy;
341 int border, pane_scrollbars = ctx->pane_scrollbars;
342 u_int right, line;
343 int sb_pos = ctx->pane_scrollbars_pos;
344 int sb_w;
345
346 *wpp = NULL;
347
348 if (px > sx || py > sy)
349 return (CELL_OUTSIDE);
350 if (px == sx || py == sy) /* window border */
351 return (screen_redraw_type_of_cell(ctx, px, py));
352
353 if (pane_status != PANE_STATUS_OFF) {
354 active = wp = server_client_get_pane(c);
355 do {
356 if (!window_pane_visible(wp))
357 goto next1;
358
359 if (pane_status == PANE_STATUS_TOP)
360 line = wp->yoff - 1;
361 else
362 line = wp->yoff + sy;
363 right = wp->xoff + 2 + wp->status_size - 1;
364
365 if (py == line && px >= wp->xoff + 2 && px <= right)
366 return (CELL_INSIDE);
367
368 next1:
369 wp = TAILQ_NEXT(wp, entry);
370 if (wp == NULL)
371 wp = TAILQ_FIRST(&w->panes);
372 } while (wp != active);
373 }
374
375 active = wp = server_client_get_pane(c);
376 do {
377 if (!window_pane_visible(wp))
378 goto next2;
379 *wpp = wp;
380
381 /* Check if CELL_SCROLLBAR */
382 if (window_pane_show_scrollbar(wp, pane_scrollbars)) {
383
384 if (pane_status == PANE_STATUS_TOP)
385 line = wp->yoff - 1;
386 else
387 line = wp->yoff + wp->sy;
388
389 /*
390 * Check if py could lie within a scrollbar. If the
391 * pane is at the top then py == 0 to sy; if the pane
392 * is not at the top, then yoff to yoff + sy.
393 */
394 sb_w = wp->scrollbar_style.width +
395 wp->scrollbar_style.pad;
396 if ((pane_status && py != line) ||
397 (wp->yoff == 0 && py < wp->sy) ||
398 (py >= wp->yoff && py < wp->yoff + wp->sy)) {
399 /* Check if px lies within a scrollbar. */
400 if ((sb_pos == PANE_SCROLLBARS_RIGHT &&
401 (px >= wp->xoff + wp->sx &&
402 px < wp->xoff + wp->sx + sb_w)) ||
403 (sb_pos == PANE_SCROLLBARS_LEFT &&
404 (px >= wp->xoff - sb_w &&
405 px < wp->xoff)))
406 return (CELL_SCROLLBAR);
407 }
408 }
409
410 /*
411 * If definitely inside, return. If not on border, skip.
412 * Otherwise work out the cell.
413 */
414 border = screen_redraw_pane_border(ctx, wp, px, py);
415 if (border == SCREEN_REDRAW_INSIDE)
416 return (CELL_INSIDE);
417 if (border == SCREEN_REDRAW_OUTSIDE)
418 goto next2;
419 return (screen_redraw_type_of_cell(ctx, px, py));
420
421 next2:
422 wp = TAILQ_NEXT(wp, entry);
423 if (wp == NULL)
424 wp = TAILQ_FIRST(&w->panes);
425 } while (wp != active);
426
427 return (CELL_OUTSIDE);
428 }
429
430 /* Check if the border of a particular pane. */
431 static int
432 screen_redraw_check_is(struct screen_redraw_ctx *ctx, u_int px, u_int py,
433 struct window_pane *wp)
434 {
435 enum screen_redraw_border_type border;
436
437 border = screen_redraw_pane_border(ctx, wp, px, py);
438 if (border != SCREEN_REDRAW_INSIDE && border != SCREEN_REDRAW_OUTSIDE)
439 return (1);
440 return (0);
441 }
442
443 /* Update pane status. */
444 static int
445 screen_redraw_make_pane_status(struct client *c, struct window_pane *wp,
446 struct screen_redraw_ctx *rctx, enum pane_lines pane_lines)
447 {
448 struct window *w = wp->window;
449 struct grid_cell gc;
450 const char *fmt;
451 struct format_tree *ft;
452 char *expanded;
453 int pane_status = rctx->pane_status, sb_w = 0;
454 int pane_scrollbars = rctx->pane_scrollbars;
455 u_int width, i, cell_type, px, py;
456 struct screen_write_ctx ctx;
457 struct screen old;
458
459 if (window_pane_show_scrollbar(wp, pane_scrollbars))
460 sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
461
462 ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS);
463 format_defaults(ft, c, c->session, c->session->curw, wp);
464
465 if (wp == server_client_get_pane(c))
466 style_apply(&gc, w->options, "pane-active-border-style", ft);
467 else
468 style_apply(&gc, w->options, "pane-border-style", ft);
469 fmt = options_get_string(wp->options, "pane-border-format");
470
471 expanded = format_expand_time(ft, fmt);
472 if (wp->sx < 4)
473 wp->status_size = width = 0;
474 else
475 wp->status_size = width = wp->sx + sb_w - 2;
476
477 memcpy(&old, &wp->status_screen, sizeof old);
478 screen_init(&wp->status_screen, width, 1, 0);
479 wp->status_screen.mode = 0;
480
481 screen_write_start(&ctx, &wp->status_screen);
482
483 for (i = 0; i < width; i++) {
484 px = wp->xoff + 2 + i;
485 if (pane_status == PANE_STATUS_TOP)
486 py = wp->yoff - 1;
487 else
488 py = wp->yoff + wp->sy;
489 cell_type = screen_redraw_type_of_cell(rctx, px, py);
490 screen_redraw_border_set(w, wp, pane_lines, cell_type, &gc);
491 screen_write_cell(&ctx, &gc);
492 }
493 gc.attr &= ~GRID_ATTR_CHARSET;
494
495 screen_write_cursormove(&ctx, 0, 0, 0);
496 format_draw(&ctx, &gc, width, expanded, NULL, 0);
497 screen_write_stop(&ctx);
498
499 free(expanded);
500 format_free(ft);
501
502 if (grid_compare(wp->status_screen.grid, old.grid) == 0) {
503 screen_free(&old);
504 return (0);
505 }
506 screen_free(&old);
507 return (1);
508 }
509
510 /* Draw pane status. */
511 static void
512 screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx)
513 {
514 struct client *c = ctx->c;
515 struct window *w = c->session->curw->window;
516 struct tty *tty = &c->tty;
517 struct window_pane *wp;
518 struct screen *s;
519 u_int i, x, width, xoff, yoff, size;
520
521 log_debug("%s: %s @%u", __func__, c->name, w->id);
522
523 TAILQ_FOREACH(wp, &w->panes, entry) {
524 if (!window_pane_visible(wp))
525 continue;
526 s = &wp->status_screen;
527
528 size = wp->status_size;
529 if (ctx->pane_status == PANE_STATUS_TOP)
530 yoff = wp->yoff - 1;
531 else
532 yoff = wp->yoff + wp->sy;
533 xoff = wp->xoff + 2;
534
535 if (xoff + size <= ctx->ox ||
536 xoff >= ctx->ox + ctx->sx ||
537 yoff < ctx->oy ||
538 yoff >= ctx->oy + ctx->sy)
539 continue;
540
541 if (xoff >= ctx->ox && xoff + size <= ctx->ox + ctx->sx) {
542 /* All visible. */
543 i = 0;
544 x = xoff - ctx->ox;
545 width = size;
546 } else if (xoff < ctx->ox && xoff + size > ctx->ox + ctx->sx) {
547 /* Both left and right not visible. */
548 i = ctx->ox;
549 x = 0;
550 width = ctx->sx;
551 } else if (xoff < ctx->ox) {
552 /* Left not visible. */
553 i = ctx->ox - xoff;
554 x = 0;
555 width = size - i;
556 } else {
557 /* Right not visible. */
558 i = 0;
559 x = xoff - ctx->ox;
560 width = size - x;
561 }
562
563 if (ctx->statustop)
564 yoff += ctx->statuslines;
565 tty_draw_line(tty, s, i, 0, width, x, yoff - ctx->oy,
566 &grid_default_cell, NULL);
567 }
568 tty_cursor(tty, 0, 0);
569 }
570
571 /* Update status line and change flags if unchanged. */
572 static uint64_t
573 screen_redraw_update(struct screen_redraw_ctx *ctx, uint64_t flags)
574 {
575 struct client *c = ctx->c;
576 struct window *w = c->session->curw->window;
577 struct window_pane *wp;
578 int redraw;
579 enum pane_lines lines;
580
581 if (c->message_string != NULL)
582 redraw = status_message_redraw(c);
583 else if (c->prompt_string != NULL)
584 redraw = status_prompt_redraw(c);
585 else
586 redraw = status_redraw(c);
587 if (!redraw && (~flags & CLIENT_REDRAWSTATUSALWAYS))
588 flags &= ~CLIENT_REDRAWSTATUS;
589
590 if (c->overlay_draw != NULL)
591 flags |= CLIENT_REDRAWOVERLAY;
592
593 if (ctx->pane_status != PANE_STATUS_OFF) {
594 lines = ctx->pane_lines;
595 redraw = 0;
596 TAILQ_FOREACH(wp, &w->panes, entry) {
597 if (screen_redraw_make_pane_status(c, wp, ctx, lines))
598 redraw = 1;
599 }
600 if (redraw)
601 flags |= CLIENT_REDRAWBORDERS;
602 }
603
604 return (flags);
605 }
606
607 /* Set up redraw context. */
608 static void
609 screen_redraw_set_context(struct client *c, struct screen_redraw_ctx *ctx)
610 {
611 struct session *s = c->session;
612 struct options *oo = s->options;
613 struct window *w = s->curw->window;
614 struct options *wo = w->options;
615 u_int lines;
616
617 memset(ctx, 0, sizeof *ctx);
618 ctx->c = c;
619
620 lines = status_line_size(c);
621 if (c->message_string != NULL || c->prompt_string != NULL)
622 lines = (lines == 0) ? 1 : lines;
623 if (lines != 0 && options_get_number(oo, "status-position") == 0)
624 ctx->statustop = 1;
625 ctx->statuslines = lines;
626
627 ctx->pane_status = options_get_number(wo, "pane-border-status");
628 ctx->pane_lines = options_get_number(wo, "pane-border-lines");
629
630 ctx->pane_scrollbars = options_get_number(wo, "pane-scrollbars");
631 ctx->pane_scrollbars_pos = options_get_number(wo,
632 "pane-scrollbars-position");
633
634 tty_window_offset(&c->tty, &ctx->ox, &ctx->oy, &ctx->sx, &ctx->sy);
635
636 log_debug("%s: %s @%u ox=%u oy=%u sx=%u sy=%u %u/%d", __func__, c->name,
637 w->id, ctx->ox, ctx->oy, ctx->sx, ctx->sy, ctx->statuslines,
638 ctx->statustop);
639 }
640
641 /* Redraw entire screen. */
642 void
643 screen_redraw_screen(struct client *c)
644 {
645 struct screen_redraw_ctx ctx;
646 uint64_t flags;
647
648 if (c->flags & CLIENT_SUSPENDED)
649 return;
650
651 screen_redraw_set_context(c, &ctx);
652
653 flags = screen_redraw_update(&ctx, c->flags);
654 if ((flags & CLIENT_ALLREDRAWFLAGS) == 0)
655 return;
656
657 tty_sync_start(&c->tty);
658 tty_update_mode(&c->tty, c->tty.mode, NULL);
659
660 if (flags & (CLIENT_REDRAWWINDOW|CLIENT_REDRAWBORDERS)) {
661 log_debug("%s: redrawing borders", c->name);
662 screen_redraw_draw_borders(&ctx);
663 if (ctx.pane_status != PANE_STATUS_OFF)
664 screen_redraw_draw_pane_status(&ctx);
665 screen_redraw_draw_pane_scrollbars(&ctx);
666 }
667 if (flags & CLIENT_REDRAWWINDOW) {
668 log_debug("%s: redrawing panes", c->name);
669 screen_redraw_draw_panes(&ctx);
670 screen_redraw_draw_pane_scrollbars(&ctx);
671 }
672 if (ctx.statuslines != 0 &&
673 (flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))) {
674 log_debug("%s: redrawing status", c->name);
675 screen_redraw_draw_status(&ctx);
676 }
677 if (c->overlay_draw != NULL && (flags & CLIENT_REDRAWOVERLAY)) {
678 log_debug("%s: redrawing overlay", c->name);
679 c->overlay_draw(c, c->overlay_data, &ctx);
680 }
681
682 tty_reset(&c->tty);
683 }
684
685 /* Redraw a single pane and its scrollbar. */
686 void
687 screen_redraw_pane(struct client *c, struct window_pane *wp,
688 int redraw_scrollbar_only)
689 {
690 struct screen_redraw_ctx ctx;
691
692 if (!window_pane_visible(wp))
693 return;
694
695 screen_redraw_set_context(c, &ctx);
696 tty_sync_start(&c->tty);
697 tty_update_mode(&c->tty, c->tty.mode, NULL);
698
699 if (!redraw_scrollbar_only)
700 screen_redraw_draw_pane(&ctx, wp);
701
702 if (window_pane_show_scrollbar(wp, ctx.pane_scrollbars))
703 screen_redraw_draw_pane_scrollbar(&ctx, wp);
704
705 tty_reset(&c->tty);
706 }
707
708 /* Get border cell style. */
709 static const struct grid_cell *
710 screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x,
711 u_int y, struct window_pane *wp)
712 {
713 struct client *c = ctx->c;
714 struct session *s = c->session;
715 struct window *w = s->curw->window;
716 struct window_pane *active = server_client_get_pane(c);
717 struct options *oo = w->options;
718 struct format_tree *ft;
719
720 if (wp->border_gc_set)
721 return (&wp->border_gc);
722 wp->border_gc_set = 1;
723
724 ft = format_create_defaults(NULL, c, s, s->curw, wp);
725 if (screen_redraw_check_is(ctx, x, y, active))
726 style_apply(&wp->border_gc, oo, "pane-active-border-style", ft);
727 else
728 style_apply(&wp->border_gc, oo, "pane-border-style", ft);
729 format_free(ft);
730
731 return (&wp->border_gc);
732 }
733
734 /* Draw a border cell. */
735 static void
736 screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j)
737 {
738 struct client *c = ctx->c;
739 struct session *s = c->session;
740 struct window *w = s->curw->window;
741 struct options *oo = w->options;
742 struct tty *tty = &c->tty;
743 struct format_tree *ft;
744 struct window_pane *wp, *active = server_client_get_pane(c);
745 struct grid_cell gc;
746 const struct grid_cell *tmp;
747 struct overlay_ranges r;
748 u_int cell_type, x = ctx->ox + i, y = ctx->oy + j;
749 int arrows = 0, border, isolates;
750
751 if (c->overlay_check != NULL) {
752 c->overlay_check(c, c->overlay_data, x, y, 1, &r);
753 if (r.nx[0] + r.nx[1] == 0)
754 return;
755 }
756
757 cell_type = screen_redraw_check_cell(ctx, x, y, &wp);
758 if (cell_type == CELL_INSIDE || cell_type == CELL_SCROLLBAR)
759 return;
760
761 if (wp == NULL) {
762 if (!ctx->no_pane_gc_set) {
763 ft = format_create_defaults(NULL, c, s, s->curw, NULL);
764 memcpy(&ctx->no_pane_gc, &grid_default_cell, sizeof gc);
765 style_add(&ctx->no_pane_gc, oo, "pane-border-style",
766 ft);
767 format_free(ft);
768 ctx->no_pane_gc_set = 1;
769 }
770 memcpy(&gc, &ctx->no_pane_gc, sizeof gc);
771 } else {
772 tmp = screen_redraw_draw_borders_style(ctx, x, y, wp);
773 if (tmp == NULL)
774 return;
775 memcpy(&gc, tmp, sizeof gc);
776
777 if (server_is_marked(s, s->curw, marked_pane.wp) &&
778 screen_redraw_check_is(ctx, x, y, marked_pane.wp))
779 gc.attr ^= GRID_ATTR_REVERSE;
780 }
781 screen_redraw_border_set(w, wp, ctx->pane_lines, cell_type, &gc);
782
783 if (cell_type == CELL_TOPBOTTOM &&
784 (c->flags & CLIENT_UTF8) &&
785 tty_term_has(tty->term, TTYC_BIDI))
786 isolates = 1;
787 else
788 isolates = 0;
789
790 if (ctx->statustop)
791 tty_cursor(tty, i, ctx->statuslines + j);
792 else
793 tty_cursor(tty, i, j);
794 if (isolates)
795 tty_puts(tty, END_ISOLATE);
796
797 switch (options_get_number(oo, "pane-border-indicators")) {
798 case PANE_BORDER_ARROWS:
799 case PANE_BORDER_BOTH:
800 arrows = 1;
801 break;
802 }
803
804 if (wp != NULL && arrows) {
805 border = screen_redraw_pane_border(ctx, active, x, y);
806 if (((i == wp->xoff + 1 &&
807 (cell_type == CELL_LEFTRIGHT ||
808 (cell_type == CELL_TOPJOIN &&
809 border == SCREEN_REDRAW_BORDER_BOTTOM) ||
810 (cell_type == CELL_BOTTOMJOIN &&
811 border == SCREEN_REDRAW_BORDER_TOP))) ||
812 (j == wp->yoff + 1 &&
813 (cell_type == CELL_TOPBOTTOM ||
814 (cell_type == CELL_LEFTJOIN &&
815 border == SCREEN_REDRAW_BORDER_RIGHT) ||
816 (cell_type == CELL_RIGHTJOIN &&
817 border == SCREEN_REDRAW_BORDER_LEFT)))) &&
818 screen_redraw_check_is(ctx, x, y, active)) {
819 gc.attr |= GRID_ATTR_CHARSET;
820 utf8_set(&gc.data, BORDER_MARKERS[border]);
821 }
822 }
823
824 tty_cell(tty, &gc, &grid_default_cell, NULL, NULL);
825 if (isolates)
826 tty_puts(tty, START_ISOLATE);
827 }
828
829 /* Draw the borders. */
830 static void
831 screen_redraw_draw_borders(struct screen_redraw_ctx *ctx)
832 {
833 struct client *c = ctx->c;
834 struct session *s = c->session;
835 struct window *w = s->curw->window;
836 struct window_pane *wp;
837 u_int i, j;
838
839 log_debug("%s: %s @%u", __func__, c->name, w->id);
840
841 TAILQ_FOREACH(wp, &w->panes, entry)
842 wp->border_gc_set = 0;
843
844 for (j = 0; j < c->tty.sy - ctx->statuslines; j++) {
845 for (i = 0; i < c->tty.sx; i++)
846 screen_redraw_draw_borders_cell(ctx, i, j);
847 }
848 }
849
850 /* Draw the panes. */
851 static void
852 screen_redraw_draw_panes(struct screen_redraw_ctx *ctx)
853 {
854 struct client *c = ctx->c;
855 struct window *w = c->session->curw->window;
856 struct window_pane *wp;
857
858 log_debug("%s: %s @%u", __func__, c->name, w->id);
859
860 TAILQ_FOREACH(wp, &w->panes, entry) {
861 if (window_pane_visible(wp))
862 screen_redraw_draw_pane(ctx, wp);
863 }
864 }
865
866 /* Draw the status line. */
867 static void
868 screen_redraw_draw_status(struct screen_redraw_ctx *ctx)
869 {
870 struct client *c = ctx->c;
871 struct window *w = c->session->curw->window;
872 struct tty *tty = &c->tty;
873 struct screen *s = c->status.active;
874 u_int i, y;
875
876 log_debug("%s: %s @%u", __func__, c->name, w->id);
877
878 if (ctx->statustop)
879 y = 0;
880 else
881 y = c->tty.sy - ctx->statuslines;
882 for (i = 0; i < ctx->statuslines; i++) {
883 tty_draw_line(tty, s, 0, i, UINT_MAX, 0, y + i,
884 &grid_default_cell, NULL);
885 }
886 }
887
888 /* Draw one pane. */
889 static void
890 screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp)
891 {
892 struct client *c = ctx->c;
893 struct window *w = c->session->curw->window;
894 struct tty *tty = &c->tty;
895 struct screen *s = wp->screen;
896 struct colour_palette *palette = &wp->palette;
897 struct grid_cell defaults;
898 u_int i, j, top, x, y, width;
899
900 log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id);
901
902 if (wp->xoff + wp->sx <= ctx->ox || wp->xoff >= ctx->ox + ctx->sx)
903 return;
904 if (ctx->statustop)
905 top = ctx->statuslines;
906 else
907 top = 0;
908 for (j = 0; j < wp->sy; j++) {
909 if (wp->yoff + j < ctx->oy || wp->yoff + j >= ctx->oy + ctx->sy)
910 continue;
911 y = top + wp->yoff + j - ctx->oy;
912
913 if (wp->xoff >= ctx->ox &&
914 wp->xoff + wp->sx <= ctx->ox + ctx->sx) {
915 /* All visible. */
916 i = 0;
917 x = wp->xoff - ctx->ox;
918 width = wp->sx;
919 } else if (wp->xoff < ctx->ox &&
920 wp->xoff + wp->sx > ctx->ox + ctx->sx) {
921 /* Both left and right not visible. */
922 i = ctx->ox;
923 x = 0;
924 width = ctx->sx;
925 } else if (wp->xoff < ctx->ox) {
926 /* Left not visible. */
927 i = ctx->ox - wp->xoff;
928 x = 0;
929 width = wp->sx - i;
930 } else {
931 /* Right not visible. */
932 i = 0;
933 x = wp->xoff - ctx->ox;
934 width = ctx->sx - x;
935 }
936 log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u",
937 __func__, c->name, wp->id, i, j, x, y, width);
938
939 tty_default_colours(&defaults, wp);
940 tty_draw_line(tty, s, i, j, width, x, y, &defaults, palette);
941 }
942
943 #ifdef ENABLE_SIXEL
944 tty_draw_images(c, wp, s);
945 #endif
946 }
947
948 /* Draw the panes scrollbars */
949 static void
950 screen_redraw_draw_pane_scrollbars(struct screen_redraw_ctx *ctx)
951 {
952 struct client *c = ctx->c;
953 struct window *w = c->session->curw->window;
954 struct window_pane *wp;
955
956 log_debug("%s: %s @%u", __func__, c->name, w->id);
957
958 TAILQ_FOREACH(wp, &w->panes, entry) {
959 if (window_pane_show_scrollbar(wp, ctx->pane_scrollbars) &&
960 window_pane_visible(wp))
961 screen_redraw_draw_pane_scrollbar(ctx, wp);
962 }
963 }
964
965 /* Draw pane scrollbar. */
966 void
967 screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *ctx,
968 struct window_pane *wp)
969 {
970 struct screen *s = wp->screen;
971 double percent_view;
972 u_int sb = ctx->pane_scrollbars, total_height, sb_h = wp->sy;
973 u_int sb_pos = ctx->pane_scrollbars_pos, slider_h, slider_y;
974 int sb_w = wp->scrollbar_style.width;
975 int sb_pad = wp->scrollbar_style.pad;
976 int cm_y, cm_size, xoff = wp->xoff, ox = ctx->ox;
977 int sb_x, sb_y = (int)(wp->yoff - ctx->oy); /* sb top */
978
979 if (window_pane_mode(wp) == WINDOW_PANE_NO_MODE) {
980 if (sb == PANE_SCROLLBARS_MODAL)
981 return;
982 /* Show slider at the bottom of the scrollbar. */
983 total_height = screen_size_y(s) + screen_hsize(s);
984 percent_view = (double)sb_h / total_height;
985 slider_h = (double)sb_h * percent_view;
986 slider_y = sb_h - slider_h;
987 } else {
988 if (TAILQ_FIRST(&wp->modes) == NULL)
989 return;
990 if (window_copy_get_current_offset(wp, &cm_y, &cm_size) == 0)
991 return;
992 total_height = cm_size + sb_h;
993 percent_view = (double)sb_h / (cm_size + sb_h);
994 slider_h = (double)sb_h * percent_view;
995 slider_y = (sb_h + 1) * ((double)cm_y / total_height);
996 }
997
998 if (sb_pos == PANE_SCROLLBARS_LEFT)
999 sb_x = xoff - sb_w - sb_pad - ox;
1000 else
1001 sb_x = xoff + wp->sx - ox;
1002
1003 if (slider_h < 1)
1004 slider_h = 1;
1005 if (slider_y >= sb_h)
1006 slider_y = sb_h - 1;
1007
1008 screen_redraw_draw_scrollbar(ctx, wp, sb_pos, sb_x, sb_y, sb_h,
1009 slider_h, slider_y);
1010
1011 /* Store current position and height of the slider */
1012 wp->sb_slider_y = slider_y; /* top of slider y pos in scrollbar */
1013 wp->sb_slider_h = slider_h; /* height of slider */
1014 }
1015
1016 static void
1017 screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx,
1018 struct window_pane *wp, int sb_pos, int sb_x, int sb_y, u_int sb_h,
1019 u_int slider_h, u_int slider_y)
1020 {
1021 struct client *c = ctx->c;
1022 struct tty *tty = &c->tty;
1023 struct grid_cell gc, slgc, *gcp;
1024 struct style *sb_style = &wp->scrollbar_style;
1025 u_int i, j, imax, jmax;
1026 u_int sb_w = sb_style->width, sb_pad = sb_style->pad;
1027 int px, py, ox = ctx->ox, oy = ctx->oy;
1028 int sx = ctx->sx, sy = ctx->sy, xoff = wp->xoff;
1029 int yoff = wp->yoff;
1030
1031 if (ctx->statustop) {
1032 sb_y += ctx->statuslines;
1033 sy += ctx->statuslines;
1034 }
1035
1036 /* Set up style for slider. */
1037 gc = sb_style->gc;
1038 memcpy(&slgc, &gc, sizeof slgc);
1039 slgc.fg = gc.bg;
1040 slgc.bg = gc.fg;
1041
1042 imax = sb_w + sb_pad;
1043 if ((int)imax + sb_x > sx)
1044 imax = sx - sb_x;
1045 jmax = sb_h;
1046 if ((int)jmax + sb_y > sy)
1047 jmax = sy - sb_y;
1048
1049 for (j = 0; j < jmax; j++) {
1050 py = sb_y + j;
1051 for (i = 0; i < imax; i++) {
1052 px = sb_x + i;
1053 if (px < xoff - ox - (int)sb_w - (int)sb_pad ||
1054 px >= sx || px < 0 ||
1055 py < yoff - oy - 1 ||
1056 py >= sy || py < 0)
1057 continue;
1058 tty_cursor(tty, px, py);
1059 if ((sb_pos == PANE_SCROLLBARS_LEFT &&
1060 i >= sb_w && i < sb_w + sb_pad) ||
1061 (sb_pos == PANE_SCROLLBARS_RIGHT &&
1062 i < sb_pad)) {
1063 tty_cell(tty, &grid_default_cell,
1064 &grid_default_cell, NULL, NULL);
1065 } else {
1066 if (j >= slider_y && j < slider_y + slider_h)
1067 gcp = &slgc;
1068 else
1069 gcp = &gc;
1070 tty_cell(tty, gcp, &grid_default_cell, NULL,
1071 NULL);
1072 }
1073 }
1074 }
1075 }
1076