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)->color, 0, SCREEN_COLOR_SIZE); } while (0)
31 #define clear_screen_char_color(schar) \
32 do { (schar)->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
);
144 /*! Used by viewer to copy over a document.
145 * When doing frame drawing @a x can be different than 0. */
147 draw_line(struct terminal
*term
, int x
, int y
, int l
, struct screen_char
*line
)
149 struct screen_char
*screen_char
= get_char(term
, x
, y
);
153 if_assert_failed
return;
154 if (!screen_char
) return;
156 size
= int_min(l
, term
->width
- x
);
157 if (size
== 0) return;
161 struct screen_char
*sc
;
163 if (line
[0].data
== UCS_NO_CHAR
&& x
== 0) {
164 unicode_val_T data_save
;
167 data_save
= sc
->data
;
168 sc
->data
= UCS_ORPHAN_CELL
;
169 copy_screen_chars(screen_char
, line
, 1);
170 sc
->data
= data_save
;
176 /* Instead of displaying double-width character at last column
177 * display only UCS_ORPHAN_CELL. */
178 if (size
- 1 > 0 && unicode_to_cell(line
[size
- 1].data
) == 2) {
179 unicode_val_T data_save
;
181 sc
= &line
[size
- 1];
182 data_save
= sc
->data
;
183 sc
->data
= UCS_ORPHAN_CELL
;
184 copy_screen_chars(screen_char
, line
, size
);
185 sc
->data
= data_save
;
187 copy_screen_chars(screen_char
, line
, size
);
191 copy_screen_chars(screen_char
, line
, size
);
192 set_screen_dirty(term
->screen
, y
, y
);
196 draw_border(struct terminal
*term
, struct box
*box
,
197 struct color_pair
*color
, int width
)
199 static const enum border_char p1
[] = {
207 static const enum border_char p2
[] = {
215 const enum border_char
*p
= (width
> 1) ? p2
: p1
;
216 struct box borderbox
;
218 set_box(&borderbox
, box
->x
- 1, box
->y
- 1,
219 box
->width
+ 2, box
->height
+ 2);
221 if (borderbox
.width
> 2) {
224 /* Horizontal top border */
225 set_box(&bbox
, box
->x
, borderbox
.y
, box
->width
, 1);
226 draw_box(term
, &bbox
, p
[5], SCREEN_ATTR_FRAME
, color
);
228 /* Horizontal bottom border */
229 bbox
.y
+= borderbox
.height
- 1;
230 draw_box(term
, &bbox
, p
[5], SCREEN_ATTR_FRAME
, color
);
233 if (borderbox
.height
> 2) {
236 /* Vertical left border */
237 set_box(&bbox
, borderbox
.x
, box
->y
, 1, box
->height
);
238 draw_box(term
, &bbox
, p
[4], SCREEN_ATTR_FRAME
, color
);
240 /* Vertical right border */
241 bbox
.x
+= borderbox
.width
- 1;
242 draw_box(term
, &bbox
, p
[4], SCREEN_ATTR_FRAME
, color
);
245 if (borderbox
.width
> 1 && borderbox
.height
> 1) {
246 int right
= borderbox
.x
+ borderbox
.width
- 1;
247 int bottom
= borderbox
.y
+ borderbox
.height
- 1;
249 /* Upper left corner */
250 draw_border_char(term
, borderbox
.x
, borderbox
.y
, p
[0], color
);
251 /* Upper right corner */
252 draw_border_char(term
, right
, borderbox
.y
, p
[1], color
);
253 /* Lower left corner */
254 draw_border_char(term
, borderbox
.x
, bottom
, p
[2], color
);
255 /* Lower right corner */
256 draw_border_char(term
, right
, bottom
, p
[3], color
);
259 set_screen_dirty(term
->screen
, borderbox
.y
, borderbox
.y
+ borderbox
.height
);
263 /** Checks cells left and right to the box for broken double-width chars.
264 * Replace it with UCS_ORPHAN_CELL.
273 * 1,2,3,4 - needs to be checked, # - shadow , +,-,| - border
277 fix_dwchar_around_box(struct terminal
*term
, struct box
*box
, int border
,
278 int shadow_width
, int shadow_height
)
280 struct screen_char
*schar
;
287 x
= box
->x
- border
- 1;
290 height
= box
->height
+ 2 * border
;
292 schar
= get_char(term
, x
, y
);
293 for (;height
--; schar
+= term
->width
)
294 if (unicode_to_cell(schar
->data
) == 2)
295 schar
->data
= UCS_ORPHAN_CELL
;
299 x
= box
->x
- border
+ shadow_width
- 1;
300 if (x
> 0 && x
< term
->width
) {
301 y
= box
->y
+ border
+ box
->height
;
302 height
= shadow_height
;
304 schar
= get_char(term
, x
, y
);
305 for (;height
--; schar
+= term
->width
)
306 if (unicode_to_cell(schar
->data
) == 2)
307 schar
->data
= UCS_ORPHAN_CELL
;
311 x
= box
->x
+ box
->width
+ border
;
312 if (x
< term
->width
) {
314 height
= shadow_height
;
316 schar
= get_char(term
, x
, y
);
317 for (;height
--; schar
+= term
->width
)
318 if (schar
->data
== UCS_NO_CHAR
)
319 schar
->data
= UCS_ORPHAN_CELL
;
323 x
= box
->x
+ box
->width
+ border
+ shadow_width
;
324 if (x
< term
->width
) {
325 y
= box
->y
- border
+ shadow_height
;
326 height
= box
->height
+ 2 * border
;
328 schar
= get_char(term
, x
, y
);
329 for (;height
--; schar
+= term
->width
)
330 if (schar
->data
== UCS_NO_CHAR
)
331 schar
->data
= UCS_ORPHAN_CELL
;
338 draw_char(struct terminal
*term
, int x
, int y
,
339 unicode_val_T data
, enum screen_char_attr attr
,
340 struct color_pair
*color
)
343 draw_char(struct terminal
*term
, int x
, int y
,
344 unsigned char data
, enum screen_char_attr attr
,
345 struct color_pair
*color
)
346 #endif /* CONFIG_UTF8 */
348 struct screen_char
*screen_char
= get_char(term
, x
, y
);
350 if (!screen_char
) return;
352 screen_char
->data
= data
;
353 screen_char
->attr
= attr
;
354 set_term_color(screen_char
, color
, 0,
355 get_opt_int_tree(term
->spec
, "colors", NULL
));
357 set_screen_dirty(term
->screen
, y
, y
);
361 draw_box(struct terminal
*term
, struct box
*box
,
362 unsigned char data
, enum screen_char_attr attr
,
363 struct color_pair
*color
)
365 struct screen_char
*line
, *pos
, *end
;
368 line
= get_char(term
, box
->x
, box
->y
);
371 height
= int_min(box
->height
, term
->height
- box
->y
);
372 width
= int_min(box
->width
, term
->width
- box
->x
);
374 if (height
<= 0 || width
<= 0) return;
376 /* Compose off the ending screen position in the areas first line. */
377 end
= &line
[width
- 1];
381 set_term_color(end
, color
, 0,
382 get_opt_int_tree(term
->spec
, "colors", NULL
));
384 clear_screen_char_color(end
);
387 /* Draw the first area line. */
388 for (pos
= line
; pos
< end
; pos
++) {
389 copy_screen_chars(pos
, end
, 1);
392 /* Now make @end point to the last line */
393 /* For the rest of the area use the first area line. */
397 copy_screen_chars(pos
, line
, width
);
400 set_screen_dirty(term
->screen
, box
->y
, box
->y
+ box
->height
);
404 draw_shadow(struct terminal
*term
, struct box
*box
,
405 struct color_pair
*color
, int width
, int height
)
410 set_box(&dbox
, box
->x
+ width
, box
->y
+ box
->height
,
411 box
->width
- width
, height
);
413 draw_box(term
, &dbox
, ' ', 0, color
);
416 set_box(&dbox
, box
->x
+ box
->width
, box
->y
+ height
,
419 draw_box(term
, &dbox
, ' ', 0, color
);
424 draw_text_utf8(struct terminal
*term
, int x
, int y
,
425 unsigned char *text
, int length
,
426 enum screen_char_attr attr
, struct color_pair
*color
)
428 struct screen_char
*start
, *pos
;
429 unsigned char *end
= text
+ length
;
432 assert(text
&& length
>= 0);
433 if_assert_failed
return;
435 if (length
<= 0) return;
436 if (x
>= term
->width
) return;
438 data
= utf8_to_unicode(&text
, end
);
439 if (data
== UCS_NO_CHAR
) return;
440 start
= get_char(term
, x
, y
);
443 set_term_color(start
, color
, 0,
444 get_opt_int_tree(term
->spec
, "colors", NULL
));
447 if (start
->data
== UCS_NO_CHAR
&& x
- 1 > 0)
448 draw_char_data(term
, x
- 1, y
, UCS_ORPHAN_CELL
);
452 if (unicode_to_cell(data
) == 2) {
453 /* Is there enough room for whole double-width char? */
454 if (x
+ 1 < term
->width
) {
459 pos
->data
= UCS_NO_CHAR
;
462 pos
->data
= UCS_ORPHAN_CELL
;
470 for (; x
< term
->width
; x
++, pos
++) {
471 data
= utf8_to_unicode(&text
, end
);
472 if (data
== UCS_NO_CHAR
) break;
473 if (color
) copy_screen_chars(pos
, start
, 1);
475 if (unicode_to_cell(data
) == 2) {
476 /* Is there enough room for whole double-width char? */
477 if (x
+ 1 < term
->width
) {
482 pos
->data
= UCS_NO_CHAR
;
485 pos
->data
= UCS_ORPHAN_CELL
;
491 set_screen_dirty(term
->screen
, y
, y
);
494 #endif /* CONFIG_UTF8 */
497 draw_text(struct terminal
*term
, int x
, int y
,
498 unsigned char *text
, int length
,
499 enum screen_char_attr attr
, struct color_pair
*color
)
502 struct screen_char
*pos
, *end
;
504 assert(text
&& length
>= 0);
505 if_assert_failed
return;
507 if (x
>= term
->width
|| y
>= term
->height
) return;
511 draw_text_utf8(term
, x
, y
, text
, length
, attr
, color
);
514 #endif /* CONFIG_UTF8 */
516 if (length
<= 0) return;
517 pos
= get_char(term
, x
, y
);
520 end_pos
= int_min(length
, term
->width
- x
) - 1;
523 /* Detect attempt to set @end to a point outside @text,
524 * it may occur in case of bad calculations. --Zas */
526 INTERNAL("end_pos < 0 !!");
529 int textlen
= strlen(text
);
531 if (end_pos
>= textlen
) {
532 INTERNAL("end_pos (%d) >= text length (%d) !!", end_pos
, textlen
);
533 end_pos
= textlen
- 1;
538 end
= &pos
[int_max(0, end_pos
)];
541 /* Use the last char as template. */
543 set_term_color(end
, color
, 0,
544 get_opt_int_tree(term
->spec
, "colors", NULL
));
546 for (; pos
< end
&& *text
; text
++, pos
++) {
548 copy_screen_chars(pos
, end
, 1);
554 for (; pos
<= end
&& *text
; text
++, pos
++) {
559 set_screen_dirty(term
->screen
, y
, y
);
563 draw_dlg_text(struct dialog_data
*dlg_data
, int x
, int y
,
564 unsigned char *text
, int length
,
565 enum screen_char_attr attr
, struct color_pair
*color
)
567 struct terminal
*term
= dlg_data
->win
->term
;
568 struct box
*box
= &dlg_data
->real_box
;
571 int y_max
= box
->y
+ box
->height
;
574 if (y
< box
->y
|| y
>= y_max
) return;
576 draw_text(term
, x
, y
, text
, length
, attr
, color
);
581 set_cursor(struct terminal
*term
, int x
, int y
, int blockable
)
583 assert(term
&& term
->screen
);
584 if_assert_failed
return;
586 if (blockable
&& get_opt_bool_tree(term
->spec
, "block_cursor", NULL
)) {
588 y
= term
->height
- 1;
591 if (term
->screen
->cx
!= x
|| term
->screen
->cy
!= y
) {
592 check_range(term
, x
, y
);
594 set_screen_dirty(term
->screen
, int_min(term
->screen
->cy
, y
),
595 int_max(term
->screen
->cy
, y
));
596 term
->screen
->cx
= x
;
597 term
->screen
->cy
= y
;
602 set_dlg_cursor(struct terminal
*term
, struct dialog_data
*dlg_data
, int x
, int y
, int blockable
)
604 struct box
*box
= &dlg_data
->real_box
;
606 assert(term
&& term
->screen
);
607 if_assert_failed
return;
610 int y_max
= box
->y
+ box
->height
;
613 if (y
< box
->y
|| y
>= y_max
) return;
615 set_cursor(term
, x
, y
, blockable
);
620 clear_terminal(struct terminal
*term
)
624 set_box(&box
, 0, 0, term
->width
, term
->height
);
625 draw_box(term
, &box
, ' ', 0, NULL
);
626 set_cursor(term
, 0, 0, 1);