1 /** Public terminal drawing API. Frontend for the screen image in memory.
10 #include "bfu/dialog.h"
11 #include "config/options.h"
12 #include "intl/charsets.h"
13 #include "terminal/color.h"
14 #include "terminal/draw.h"
15 #include "terminal/screen.h"
16 #include "terminal/terminal.h"
17 #include "util/color.h"
20 /** Makes sure that @a x and @a y are within the dimensions of the terminal. */
21 #define check_range(term, x, y) \
23 int_bounds(&(x), 0, (term)->width - 1); \
24 int_bounds(&(y), 0, (term)->height - 1); \
27 #if SCREEN_COLOR_SIZE > 1
28 #define clear_screen_char_color(schar) \
29 do { memset((schar)->c.color, 0, SCREEN_COLOR_SIZE); } while (0)
31 #define clear_screen_char_color(schar) \
32 do { (schar)->c.color[0] = 0; } while (0)
37 NONSTATIC_INLINE
struct screen_char
*
38 get_char(struct terminal
*term
, int x
, int y
)
40 assert(term
&& term
->screen
&& term
->screen
->image
);
41 if_assert_failed
return NULL
;
42 check_range(term
, x
, y
);
44 return &term
->screen
->image
[x
+ term
->width
* y
];
48 draw_border_cross(struct terminal
*term
, int x
, int y
,
49 enum border_cross_direction dir
, struct color_pair
*color
)
51 static const unsigned char border_trans
[2][4] = {
52 /* Used for BORDER_X_{RIGHT,LEFT}: */
53 { BORDER_SVLINE
, BORDER_SRTEE
, BORDER_SLTEE
},
54 /* Used for BORDER_X_{DOWN,UP}: */
55 { BORDER_SHLINE
, BORDER_SDTEE
, BORDER_SUTEE
},
57 struct screen_char
*screen_char
= get_char(term
, x
, y
);
60 if (!screen_char
) return;
61 if (!(screen_char
->attr
& SCREEN_ATTR_FRAME
)) return;
63 /* First check if there is already a horizontal/vertical line, so that
64 * we will have to replace with a T char. Example: if there is a '|'
65 * and the direction is right, replace with a '|-' T char.
67 * If this is not the case check if there is a T char and we are adding
68 * the direction so that we end up with a cross. Example : if there is
69 * a '|-' and the direction is left, replace with a '+' (cross) char. */
71 if (screen_char
->data
== border_trans
[d
][0]) {
72 screen_char
->data
= border_trans
[d
][1 + (dir
& 1)];
74 } else if (screen_char
->data
== border_trans
[d
][2 - (dir
& 1)]) {
75 screen_char
->data
= BORDER_SCROSS
;
78 set_term_color(screen_char
, color
, 0,
79 get_opt_int_tree(term
->spec
, "colors", NULL
));
83 draw_border_char(struct terminal
*term
, int x
, int y
,
84 enum border_char border
, struct color_pair
*color
)
86 struct screen_char
*screen_char
= get_char(term
, x
, y
);
88 if (!screen_char
) return;
90 screen_char
->data
= (unsigned char) border
;
91 screen_char
->attr
= SCREEN_ATTR_FRAME
;
92 set_term_color(screen_char
, color
, 0,
93 get_opt_int_tree(term
->spec
, "colors", NULL
));
94 set_screen_dirty(term
->screen
, y
, y
);
98 draw_char_color(struct terminal
*term
, int x
, int y
, struct color_pair
*color
)
100 struct screen_char
*screen_char
= get_char(term
, x
, y
);
102 if (!screen_char
) return;
104 set_term_color(screen_char
, color
, 0,
105 get_opt_int_tree(term
->spec
, "colors", NULL
));
106 set_screen_dirty(term
->screen
, y
, y
);
109 /*! The @a data parameter here is like screen_char.data: UCS-4 if the
110 * charset of the terminal is UTF-8 (possible only if CONFIG_UTF8 is
111 * defined), and a byte otherwise. */
114 draw_char_data(struct terminal
*term
, int x
, int y
, unicode_val_T data
)
116 draw_char_data(struct terminal
*term
, int x
, int y
, unsigned char data
)
117 #endif /* CONFIG_UTF8 */
119 struct screen_char
*screen_char
= get_char(term
, x
, y
);
121 if (!screen_char
) return;
123 screen_char
->data
= data
;
127 /* Detect attempt to draw double-width char on the last
128 * column of terminal. The unicode_to_cell(data) call is
129 * in principle wrong if CONFIG_UTF8 is defined but the
130 * charset of the terminal is not UTF-8, because @data
131 * is then a byte in that charset; but unicode_to_cell
132 * returns 1 for U+0000...U+00FF so it's not a problem. */
133 if (unicode_to_cell(data
) == 2 && x
+ 1 > term
->width
)
134 INTERNAL("Attempt to draw double-width glyph on last column!");
135 #endif /* CONFIG_DEBUG */
137 if (data
== UCS_NO_CHAR
)
138 screen_char
->attr
= 0;
139 #endif /* CONFIG_UTF8 */
141 set_screen_dirty(term
->screen
, y
, y
);
145 draw_space(struct terminal
*term
, int x
, int y
, struct screen_char
*color
)
147 struct screen_char
*screen_char
= get_char(term
, x
, y
);
149 if (!screen_char
) return;
151 screen_char
->data
= ' ';
152 if (color
) screen_char
->c
= color
->c
;
155 /*! Used by viewer to copy over a document.
156 * When doing frame drawing @a x can be different than 0. */
158 draw_line(struct terminal
*term
, int x
, int y
, int l
, struct screen_char
*line
)
160 struct screen_char
*screen_char
= get_char(term
, x
, y
);
164 if_assert_failed
return;
165 if (!screen_char
) return;
167 size
= int_min(l
, term
->width
- x
);
168 if (size
== 0) return;
172 struct screen_char
*sc
;
174 if (line
[0].data
== UCS_NO_CHAR
&& x
== 0) {
175 unicode_val_T data_save
;
178 data_save
= sc
->data
;
179 sc
->data
= UCS_ORPHAN_CELL
;
180 copy_screen_chars(screen_char
, line
, 1);
181 sc
->data
= data_save
;
187 /* Instead of displaying double-width character at last column
188 * display only UCS_ORPHAN_CELL. */
189 if (size
- 1 > 0 && unicode_to_cell(line
[size
- 1].data
) == 2) {
190 unicode_val_T data_save
;
192 sc
= &line
[size
- 1];
193 data_save
= sc
->data
;
194 sc
->data
= UCS_ORPHAN_CELL
;
195 copy_screen_chars(screen_char
, line
, size
);
196 sc
->data
= data_save
;
198 copy_screen_chars(screen_char
, line
, size
);
202 copy_screen_chars(screen_char
, line
, size
);
203 set_screen_dirty(term
->screen
, y
, y
);
207 draw_border(struct terminal
*term
, struct box
*box
,
208 struct color_pair
*color
, int width
)
210 static const enum border_char p1
[] = {
218 static const enum border_char p2
[] = {
226 const enum border_char
*p
= (width
> 1) ? p2
: p1
;
227 struct box borderbox
;
229 set_box(&borderbox
, box
->x
- 1, box
->y
- 1,
230 box
->width
+ 2, box
->height
+ 2);
232 if (borderbox
.width
> 2) {
235 /* Horizontal top border */
236 set_box(&bbox
, box
->x
, borderbox
.y
, box
->width
, 1);
237 draw_box(term
, &bbox
, p
[5], SCREEN_ATTR_FRAME
, color
);
239 /* Horizontal bottom border */
240 bbox
.y
+= borderbox
.height
- 1;
241 draw_box(term
, &bbox
, p
[5], SCREEN_ATTR_FRAME
, color
);
244 if (borderbox
.height
> 2) {
247 /* Vertical left border */
248 set_box(&bbox
, borderbox
.x
, box
->y
, 1, box
->height
);
249 draw_box(term
, &bbox
, p
[4], SCREEN_ATTR_FRAME
, color
);
251 /* Vertical right border */
252 bbox
.x
+= borderbox
.width
- 1;
253 draw_box(term
, &bbox
, p
[4], SCREEN_ATTR_FRAME
, color
);
256 if (borderbox
.width
> 1 && borderbox
.height
> 1) {
257 int right
= borderbox
.x
+ borderbox
.width
- 1;
258 int bottom
= borderbox
.y
+ borderbox
.height
- 1;
260 /* Upper left corner */
261 draw_border_char(term
, borderbox
.x
, borderbox
.y
, p
[0], color
);
262 /* Upper right corner */
263 draw_border_char(term
, right
, borderbox
.y
, p
[1], color
);
264 /* Lower left corner */
265 draw_border_char(term
, borderbox
.x
, bottom
, p
[2], color
);
266 /* Lower right corner */
267 draw_border_char(term
, right
, bottom
, p
[3], color
);
270 set_screen_dirty(term
->screen
, borderbox
.y
, borderbox
.y
+ borderbox
.height
);
274 /** Checks cells left and right to the box for broken double-width chars.
275 * Replace it with UCS_ORPHAN_CELL.
284 * 1,2,3,4 - needs to be checked, # - shadow , +,-,| - border
288 fix_dwchar_around_box(struct terminal
*term
, struct box
*box
, int border
,
289 int shadow_width
, int shadow_height
)
291 struct screen_char
*schar
;
298 x
= box
->x
- border
- 1;
301 height
= box
->height
+ 2 * border
;
303 schar
= get_char(term
, x
, y
);
304 for (;height
--; schar
+= term
->width
)
305 if (unicode_to_cell(schar
->data
) == 2)
306 schar
->data
= UCS_ORPHAN_CELL
;
310 x
= box
->x
- border
+ shadow_width
- 1;
311 if (x
> 0 && x
< term
->width
) {
312 y
= box
->y
+ border
+ box
->height
;
313 height
= shadow_height
;
315 schar
= get_char(term
, x
, y
);
316 for (;height
--; schar
+= term
->width
)
317 if (unicode_to_cell(schar
->data
) == 2)
318 schar
->data
= UCS_ORPHAN_CELL
;
322 x
= box
->x
+ box
->width
+ border
;
323 if (x
< term
->width
) {
325 height
= shadow_height
;
327 schar
= get_char(term
, x
, y
);
328 for (;height
--; schar
+= term
->width
)
329 if (schar
->data
== UCS_NO_CHAR
)
330 schar
->data
= UCS_ORPHAN_CELL
;
334 x
= box
->x
+ box
->width
+ border
+ shadow_width
;
335 if (x
< term
->width
) {
336 y
= box
->y
- border
+ shadow_height
;
337 height
= box
->height
+ 2 * border
;
339 schar
= get_char(term
, x
, y
);
340 for (;height
--; schar
+= term
->width
)
341 if (schar
->data
== UCS_NO_CHAR
)
342 schar
->data
= UCS_ORPHAN_CELL
;
349 draw_char(struct terminal
*term
, int x
, int y
,
350 unicode_val_T data
, enum screen_char_attr attr
,
351 struct color_pair
*color
)
354 draw_char(struct terminal
*term
, int x
, int y
,
355 unsigned char data
, enum screen_char_attr attr
,
356 struct color_pair
*color
)
357 #endif /* CONFIG_UTF8 */
359 struct screen_char
*screen_char
= get_char(term
, x
, y
);
361 if (!screen_char
) return;
363 screen_char
->data
= data
;
364 screen_char
->attr
= attr
;
365 set_term_color(screen_char
, color
, 0,
366 get_opt_int_tree(term
->spec
, "colors", NULL
));
368 set_screen_dirty(term
->screen
, y
, y
);
372 draw_box(struct terminal
*term
, struct box
*box
,
373 unsigned char data
, enum screen_char_attr attr
,
374 struct color_pair
*color
)
376 struct screen_char
*line
, *pos
, *end
;
379 line
= get_char(term
, box
->x
, box
->y
);
382 height
= int_min(box
->height
, term
->height
- box
->y
);
383 width
= int_min(box
->width
, term
->width
- box
->x
);
385 if (height
<= 0 || width
<= 0) return;
387 /* Compose off the ending screen position in the areas first line. */
388 end
= &line
[width
- 1];
392 set_term_color(end
, color
, 0,
393 get_opt_int_tree(term
->spec
, "colors", NULL
));
395 clear_screen_char_color(end
);
398 /* Draw the first area line. */
399 for (pos
= line
; pos
< end
; pos
++) {
400 copy_screen_chars(pos
, end
, 1);
403 /* Now make @end point to the last line */
404 /* For the rest of the area use the first area line. */
408 copy_screen_chars(pos
, line
, width
);
411 set_screen_dirty(term
->screen
, box
->y
, box
->y
+ box
->height
);
415 draw_shadow(struct terminal
*term
, struct box
*box
,
416 struct color_pair
*color
, int width
, int height
)
421 set_box(&dbox
, box
->x
+ width
, box
->y
+ box
->height
,
422 box
->width
- width
, height
);
424 draw_box(term
, &dbox
, ' ', 0, color
);
427 set_box(&dbox
, box
->x
+ box
->width
, box
->y
+ height
,
430 draw_box(term
, &dbox
, ' ', 0, color
);
435 draw_text_utf8(struct terminal
*term
, int x
, int y
,
436 unsigned char *text
, int length
,
437 enum screen_char_attr attr
, struct color_pair
*color
)
439 struct screen_char
*start
, *pos
;
440 unsigned char *end
= text
+ length
;
443 assert(text
&& length
>= 0);
444 if_assert_failed
return;
446 if (length
<= 0) return;
447 if (x
>= term
->width
) return;
449 data
= utf8_to_unicode(&text
, end
);
450 if (data
== UCS_NO_CHAR
) return;
451 start
= get_char(term
, x
, y
);
454 set_term_color(start
, color
, 0,
455 get_opt_int_tree(term
->spec
, "colors", NULL
));
458 if (start
->data
== UCS_NO_CHAR
&& x
- 1 > 0)
459 draw_char_data(term
, x
- 1, y
, UCS_ORPHAN_CELL
);
463 if (unicode_to_cell(data
) == 2) {
464 /* Is there enough room for whole double-width char? */
465 if (x
+ 1 < term
->width
) {
470 pos
->data
= UCS_NO_CHAR
;
473 pos
->data
= UCS_ORPHAN_CELL
;
481 for (; x
< term
->width
; x
++, pos
++) {
482 data
= utf8_to_unicode(&text
, end
);
483 if (data
== UCS_NO_CHAR
) break;
484 if (color
) copy_screen_chars(pos
, start
, 1);
486 if (unicode_to_cell(data
) == 2) {
487 /* Is there enough room for whole double-width char? */
488 if (x
+ 1 < term
->width
) {
493 pos
->data
= UCS_NO_CHAR
;
496 pos
->data
= UCS_ORPHAN_CELL
;
502 set_screen_dirty(term
->screen
, y
, y
);
505 #endif /* CONFIG_UTF8 */
508 draw_text(struct terminal
*term
, int x
, int y
,
509 unsigned char *text
, int length
,
510 enum screen_char_attr attr
, struct color_pair
*color
)
513 struct screen_char
*pos
, *end
;
515 assert(text
&& length
>= 0);
516 if_assert_failed
return;
518 if (x
>= term
->width
|| y
>= term
->height
) return;
522 draw_text_utf8(term
, x
, y
, text
, length
, attr
, color
);
525 #endif /* CONFIG_UTF8 */
527 if (length
<= 0) return;
528 pos
= get_char(term
, x
, y
);
531 end_pos
= int_min(length
, term
->width
- x
) - 1;
534 /* Detect attempt to set @end to a point outside @text,
535 * it may occur in case of bad calculations. --Zas */
537 INTERNAL("end_pos < 0 !!");
540 int textlen
= strlen(text
);
542 if (end_pos
>= textlen
) {
543 INTERNAL("end_pos (%d) >= text length (%d) !!", end_pos
, textlen
);
544 end_pos
= textlen
- 1;
549 end
= &pos
[int_max(0, end_pos
)];
552 /* Use the last char as template. */
554 set_term_color(end
, color
, 0,
555 get_opt_int_tree(term
->spec
, "colors", NULL
));
557 for (; pos
< end
&& *text
; text
++, pos
++) {
559 copy_screen_chars(pos
, end
, 1);
565 for (; pos
<= end
&& *text
; text
++, pos
++) {
570 set_screen_dirty(term
->screen
, y
, y
);
574 draw_dlg_text(struct dialog_data
*dlg_data
, int x
, int y
,
575 unsigned char *text
, int length
,
576 enum screen_char_attr attr
, struct color_pair
*color
)
578 struct terminal
*term
= dlg_data
->win
->term
;
579 struct box
*box
= &dlg_data
->real_box
;
582 int y_max
= box
->y
+ box
->height
;
585 if (y
< box
->y
|| y
>= y_max
) return;
587 draw_text(term
, x
, y
, text
, length
, attr
, color
);
592 set_cursor(struct terminal
*term
, int x
, int y
, int blockable
)
594 assert(term
&& term
->screen
);
595 if_assert_failed
return;
597 if (blockable
&& get_opt_bool_tree(term
->spec
, "block_cursor", NULL
)) {
599 y
= term
->height
- 1;
602 if (term
->screen
->cx
!= x
|| term
->screen
->cy
!= y
) {
603 check_range(term
, x
, y
);
605 set_screen_dirty(term
->screen
, int_min(term
->screen
->cy
, y
),
606 int_max(term
->screen
->cy
, y
));
607 term
->screen
->cx
= x
;
608 term
->screen
->cy
= y
;
613 set_dlg_cursor(struct terminal
*term
, struct dialog_data
*dlg_data
, int x
, int y
, int blockable
)
615 struct box
*box
= &dlg_data
->real_box
;
617 assert(term
&& term
->screen
);
618 if_assert_failed
return;
621 int y_max
= box
->y
+ box
->height
;
624 if (y
< box
->y
|| y
>= y_max
) return;
626 set_cursor(term
, x
, y
, blockable
);
631 clear_terminal(struct terminal
*term
)
635 set_box(&box
, 0, 0, term
->width
, term
->height
);
636 draw_box(term
, &box
, ' ', 0, NULL
);
637 set_cursor(term
, 0, 0, 1);