source: mainline/uspace/lib/ui/src/paint.c@ c38ab6c

ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since c38ab6c was 96c6a00, checked in by Jiri Svoboda <jiri@…>, 4 years ago

Menu accelerators

Open a menu by pressing F10, then accelerator key (e.g. 'F').

  • Property mode set to 100644
File size: 14.9 KB
RevLine 
[de9992c]1/*
[96c6a00]2 * Copyright (c) 2022 Jiri Svoboda
[de9992c]3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libui
30 * @{
31 */
32/**
33 * @file Painting routines
34 */
35
36#include <errno.h>
37#include <gfx/color.h>
38#include <gfx/context.h>
39#include <gfx/render.h>
[ff6e91b]40#include <gfx/text.h>
[96c6a00]41#include <ui/accel.h>
[de9992c]42#include <ui/paint.h>
[ff6e91b]43#include <stdlib.h>
44#include <str.h>
[d70dc1c4]45#include "../private/resource.h"
[de9992c]46
[ff6e91b]47/** Single box characters */
48static ui_box_chars_t single_box_chars = {
49 {
50 { "\u250c", "\u2500", "\u2510" },
51 { "\u2502", " ", "\u2502" },
52 { "\u2514", "\u2500", "\u2518" }
53 }
54};
55
56/** Double box characters */
57static ui_box_chars_t double_box_chars = {
58 {
59 { "\u2554", "\u2550", "\u2557" },
60 { "\u2551", " ", "\u2551" },
61 { "\u255a", "\u2550", "\u255d" }
62 }
63};
64
[81ec7e1]65/** Single horizontal brace characters */
66static ui_brace_chars_t single_hbrace_chars = {
67 .start = "\u251c",
68 .middle = "\u2500",
69 .end = "\u2524"
70};
71
72/** Double horizontal brace characters */
73static ui_brace_chars_t double_hbrace_chars = {
74 .start = "\u2560",
75 .middle = "\u2550",
76 .end = "\u2563"
77};
78
[de9992c]79/** Paint bevel.
80 *
81 * @param gc Graphic context
82 * @param rect Rectangle to paint into
83 * @param tlcolor Top-left color
84 * @param brcolor Bottom-right color
85 * @param thickness Bevel thickness in pixels
86 * @param inside Place to store rectangle of the interior or @c NULL
[d63623f]87 * @return EOK on success or an error code
[de9992c]88 */
89errno_t ui_paint_bevel(gfx_context_t *gc, gfx_rect_t *rect,
90 gfx_color_t *tlcolor, gfx_color_t *brcolor, gfx_coord_t thickness,
91 gfx_rect_t *inside)
92{
93 gfx_rect_t frect;
94 gfx_coord_t i;
95 errno_t rc;
96
97 /* Top left */
98
99 rc = gfx_set_color(gc, tlcolor);
100 if (rc != EOK)
101 goto error;
102
103 for (i = 0; i < thickness; i++) {
104 frect.p0.x = rect->p0.x + i;
105 frect.p0.y = rect->p0.y + i;
106 frect.p1.x = rect->p1.x - i - 1;
107 frect.p1.y = rect->p0.y + i + 1;
108 rc = gfx_fill_rect(gc, &frect);
109 if (rc != EOK)
110 goto error;
111
112 frect.p0.x = rect->p0.x + i;
113 frect.p0.y = rect->p0.y + i + 1;
114 frect.p1.x = rect->p0.x + i + 1;
115 frect.p1.y = rect->p1.y - i - 1;
116 rc = gfx_fill_rect(gc, &frect);
117 if (rc != EOK)
118 goto error;
119 }
120
121 /* Bottom right */
122
123 rc = gfx_set_color(gc, brcolor);
124 if (rc != EOK)
125 goto error;
126
127 for (i = 0; i < thickness; i++) {
128 frect.p0.x = rect->p0.x + i;
129 frect.p0.y = rect->p1.y - i - 1;
130 frect.p1.x = rect->p1.x - i - 1;
131 frect.p1.y = rect->p1.y - i;
132 rc = gfx_fill_rect(gc, &frect);
133 if (rc != EOK)
134 goto error;
135
136 frect.p0.x = rect->p1.x - i - 1;
137 frect.p0.y = rect->p0.y + i;
138 frect.p1.x = rect->p1.x - i;
139 frect.p1.y = rect->p1.y - i;
140 rc = gfx_fill_rect(gc, &frect);
141 if (rc != EOK)
142 goto error;
143 }
144
[d63623f]145 if (inside != NULL)
146 ui_paint_get_bevel_inside(gc, rect, thickness, inside);
[de9992c]147
148 return EOK;
149error:
150 return rc;
151}
152
[d63623f]153/** Get bevel interior rectangle.
154 *
155 * Get the bevel interior rectangle without painting it.
156 *
157 * @param gc Graphic context
158 * @param rect Rectangle to paint into
159 * @param thickness Bevel thickness in pixels
160 * @param inside Place to store rectangle of the interior
161 */
162void ui_paint_get_bevel_inside(gfx_context_t *gc, gfx_rect_t *rect,
163 gfx_coord_t thickness, gfx_rect_t *inside)
164{
165 inside->p0.x = rect->p0.x + thickness;
166 inside->p0.y = rect->p0.y + thickness;
167 inside->p1.x = rect->p1.x - thickness;
168 inside->p1.y = rect->p1.y - thickness;
169}
170
[d70dc1c4]171/** Paint inset frame.
172 *
173 * @param resource UI resource
174 * @param rect Rectangle to paint onto
175 * @param inside Place to store inside rectangle or @c NULL
176 * @return EOK on success or an error code
177 */
178errno_t ui_paint_inset_frame(ui_resource_t *resource, gfx_rect_t *rect,
179 gfx_rect_t *inside)
180{
181 gfx_rect_t frame;
182 errno_t rc;
183
184 rc = ui_paint_bevel(resource->gc, rect,
185 resource->wnd_shadow_color, resource->wnd_highlight_color,
186 1, &frame);
187 if (rc != EOK)
188 goto error;
189
190 rc = ui_paint_bevel(resource->gc, &frame,
191 resource->wnd_frame_sh_color, resource->wnd_frame_hi_color,
192 1, inside);
193 if (rc != EOK)
194 goto error;
195
196 return EOK;
197error:
198 return rc;
199}
200
[d63623f]201/** Get inset frame interior rectangle.
202 *
203 * This allows one to get the interior rectangle without actually painting
204 * the inset frame.
205 *
206 * @param resource UI resource
207 * @param rect Rectangle to paint onto
208 * @param inside Place to store inside rectangle or @c NULL
209 */
210void ui_paint_get_inset_frame_inside(ui_resource_t *resource, gfx_rect_t *rect,
211 gfx_rect_t *inside)
212{
213 ui_paint_get_bevel_inside(resource->gc, rect, 2, inside);
214}
215
[214aefb]216/** Paint outset frame.
217 *
218 * @param resource UI resource
219 * @param rect Rectangle to paint onto
220 * @param inside Place to store inside rectangle or @c NULL
221 * @return EOK on success or an error code
222 */
223errno_t ui_paint_outset_frame(ui_resource_t *resource, gfx_rect_t *rect,
224 gfx_rect_t *inside)
225{
226 gfx_rect_t frame;
227 errno_t rc;
228
229 rc = ui_paint_bevel(resource->gc, rect,
230 resource->wnd_frame_hi_color, resource->wnd_frame_sh_color,
231 1, &frame);
232 if (rc != EOK)
233 goto error;
234
235 rc = ui_paint_bevel(resource->gc, &frame,
236 resource->wnd_highlight_color, resource->wnd_shadow_color,
237 1, inside);
238 if (rc != EOK)
239 goto error;
240
241 return EOK;
242error:
243 return rc;
244}
245
[7020d1f]246/** Paint filled circle vertical scanline.
247 *
248 * @param gc Graphic context
249 * @param center Coordinates of the center of the circle
250 * @param x X-coordinate of the scanline
251 * @param y0 Lowest Y coordinate of the scanline (inclusive)
252 * @param y1 Highest Y coordinate of the scanline (inclusive)
253 * @param part Which part(s) of cicle to paint
254 * @return EOK on success or an error code
255 */
256static errno_t ui_paint_fcircle_line(gfx_context_t *gc, gfx_coord2_t *center,
257 gfx_coord_t x, gfx_coord_t y0, gfx_coord_t y1, ui_fcircle_part_t part)
258{
259 gfx_rect_t rect;
260 gfx_rect_t trect;
261
262 rect.p0.x = x;
263 rect.p0.y = y0;
264 rect.p1.x = x + 1;
265 rect.p1.y = y1;
266
267 /* Clip to upper-left/lower-right half of circle, if required */
268
269 if ((part & ui_fcircle_upleft) == 0) {
270 if (rect.p0.y < -rect.p1.x)
271 rect.p0.y = -rect.p1.x;
272 }
273
274 if ((part & ui_fcircle_lowright) == 0) {
275 if (rect.p1.y > -rect.p1.x)
276 rect.p1.y = -rect.p1.x;
277 }
278
279 /* If coordinates are reversed, there is nothing to do */
280 if (rect.p1.y <= rect.p0.y)
281 return EOK;
282
283 gfx_rect_translate(center, &rect, &trect);
284
285 return gfx_fill_rect(gc, &trect);
286}
287
288/** Paint filled circle scanlines corresponding to octant point.
289 *
290 * Fills the four vertical scanlines lying between the eight reflections
291 * of a circle point.
292 *
293 * @param gc Graphic context
294 * @param center Coordinates of the center
295 * @param r Radius in pixels
296 * @param part Which part(s) of cicle to paint
297 * @return EOK on success or an error code
298 */
299static int ui_paint_fcircle_point(gfx_context_t *gc, gfx_coord2_t *center,
300 gfx_coord2_t *p, ui_fcircle_part_t part)
301{
302 errno_t rc;
303
304 rc = ui_paint_fcircle_line(gc, center, +p->x, -p->y, p->y + 1, part);
305 if (rc != EOK)
306 return rc;
307 rc = ui_paint_fcircle_line(gc, center, -p->x, -p->y, p->y + 1, part);
308 if (rc != EOK)
309 return rc;
310 rc = ui_paint_fcircle_line(gc, center, +p->y, -p->x, p->x + 1, part);
311 if (rc != EOK)
312 return rc;
313 rc = ui_paint_fcircle_line(gc, center, -p->y, -p->x, p->x + 1, part);
314 if (rc != EOK)
315 return rc;
316
317 return EOK;
318}
319
320/** Paint a filled circle.
321 *
322 * @param gc Graphic context
323 * @param center Coordinates of the center
324 * @param r Radius in pixels
325 * @param part Which part(s) of cicle to paint
326 * @return EOK on success or an error code
327 */
328errno_t ui_paint_filled_circle(gfx_context_t *gc, gfx_coord2_t *center,
329 gfx_coord_t r, ui_fcircle_part_t part)
330{
331 gfx_coord2_t p;
332 gfx_coord_t f;
333 errno_t rc;
334
335 /* Run through one octant using circle midpoint algorithm */
336
337 p.x = r;
338 p.y = 0;
339 f = 1 - r;
340
341 rc = ui_paint_fcircle_point(gc, center, &p, part);
342 if (rc != EOK)
343 return rc;
344
345 while (p.x > p.y) {
346 if (f < 0) {
347 f = f + 2 * p.y + 3;
348 } else {
349 f = f + 2 * (p.y - p.x) + 5;
350 --p.x;
351 }
352 ++p.y;
353
354 rc = ui_paint_fcircle_point(gc, center, &p, part);
355 if (rc != EOK)
356 return rc;
357 }
358
359 return EOK;
360}
361
[ff6e91b]362/** Paint a text box.
363 *
364 * @param resource UI resource
365 * @param rect Rectangle inside which to paint the box
366 * @param style Box style
367 * @param color Color
368 * @return EOK on success or an error code
369 */
370errno_t ui_paint_text_box(ui_resource_t *resource, gfx_rect_t *rect,
371 ui_box_style_t style, gfx_color_t *color)
372{
373 errno_t rc;
374 gfx_text_fmt_t fmt;
375 gfx_rect_t srect;
376 gfx_coord2_t pos;
377 gfx_coord2_t dim;
378 size_t bufsz;
379 size_t off;
380 int i;
381 gfx_coord_t y;
382 char *str = NULL;
383 ui_box_chars_t *boxc = NULL;
384
385 gfx_rect_points_sort(rect, &srect);
386 gfx_rect_dims(&srect, &dim);
387
388 /* Is rectangle large enough to hold box? */
389 if (dim.x < 2 || dim.y < 2)
390 return EOK;
391
392 switch (style) {
393 case ui_box_single:
394 boxc = &single_box_chars;
395 break;
396 case ui_box_double:
397 boxc = &double_box_chars;
398 break;
399 }
400
401 if (boxc == NULL)
402 return EINVAL;
403
404 gfx_text_fmt_init(&fmt);
[4583015]405 fmt.font = resource->font;
[ff6e91b]406 fmt.color = color;
407
408 bufsz = str_size(boxc->c[0][0]) +
409 str_size(boxc->c[0][1]) * (dim.x - 2) +
410 str_size(boxc->c[0][2]) + 1;
411
412 str = malloc(bufsz);
413 if (str == NULL)
414 return ENOMEM;
415
416 /* Top edge and corners */
417
418 str_cpy(str, bufsz, boxc->c[0][0]);
419 off = str_size(boxc->c[0][0]);
420
421 for (i = 1; i < dim.x - 1; i++) {
422 str_cpy(str + off, bufsz - off, boxc->c[0][1]);
423 off += str_size(boxc->c[0][1]);
424 }
425
426 str_cpy(str + off, bufsz - off, boxc->c[0][2]);
427 off += str_size(boxc->c[0][2]);
428 str[off] = '\0';
429
430 pos = rect->p0;
[4583015]431 rc = gfx_puttext(&pos, &fmt, str);
[ff6e91b]432 if (rc != EOK)
433 goto error;
434
435 /* Vertical edges */
436 for (y = rect->p0.y + 1; y < rect->p1.y - 1; y++) {
437 pos.y = y;
438
439 pos.x = rect->p0.x;
[4583015]440 rc = gfx_puttext(&pos, &fmt, boxc->c[1][0]);
[ff6e91b]441 if (rc != EOK)
442 goto error;
443
444 pos.x = rect->p1.x - 1;
[4583015]445 rc = gfx_puttext(&pos, &fmt, boxc->c[1][2]);
[ff6e91b]446 if (rc != EOK)
447 goto error;
448 }
449
450 /* Bottom edge and corners */
451
452 str_cpy(str, bufsz, boxc->c[2][0]);
453 off = str_size(boxc->c[2][0]);
454
455 for (i = 1; i < dim.x - 1; i++) {
456 str_cpy(str + off, bufsz - off, boxc->c[2][1]);
457 off += str_size(boxc->c[2][1]);
458 }
459
460 str_cpy(str + off, bufsz - off, boxc->c[2][2]);
461 off += str_size(boxc->c[2][2]);
462 str[off] = '\0';
463
464 pos.x = rect->p0.x;
465 pos.y = rect->p1.y - 1;
[4583015]466 rc = gfx_puttext(&pos, &fmt, str);
[ff6e91b]467 if (rc != EOK)
468 goto error;
469
470 free(str);
471 return EOK;
472error:
473 if (str != NULL)
474 free(str);
475 return rc;
476}
477
[81ec7e1]478/** Paint a text horizontal brace.
479 *
480 * @param resource UI resource
481 * @param rect Rectangle inside which to paint the brace (height should
482 * be 1).
483 * @param style Box style
484 * @param color Color
485 * @return EOK on success or an error code
486 */
487errno_t ui_paint_text_hbrace(ui_resource_t *resource, gfx_rect_t *rect,
488 ui_box_style_t style, gfx_color_t *color)
489{
490 errno_t rc;
491 gfx_text_fmt_t fmt;
492 gfx_rect_t srect;
493 gfx_coord2_t pos;
494 gfx_coord2_t dim;
495 size_t bufsz;
496 size_t off;
497 int i;
498 char *str = NULL;
499 ui_brace_chars_t *hbc = NULL;
500
501 gfx_rect_points_sort(rect, &srect);
502 gfx_rect_dims(&srect, &dim);
503
504 /* Is rectangle large enough to hold brace? */
505 if (dim.x < 2 || dim.y < 1)
506 return EOK;
507
508 switch (style) {
509 case ui_box_single:
510 hbc = &single_hbrace_chars;
511 break;
512 case ui_box_double:
513 hbc = &double_hbrace_chars;
514 break;
515 }
516
517 if (hbc == NULL)
518 return EINVAL;
519
520 gfx_text_fmt_init(&fmt);
[4583015]521 fmt.font = resource->font;
[81ec7e1]522 fmt.color = color;
523
524 bufsz = str_size(hbc->start) +
525 str_size(hbc->middle) * (dim.x - 2) +
526 str_size(hbc->end) + 1;
527
528 str = malloc(bufsz);
529 if (str == NULL)
530 return ENOMEM;
531
532 str_cpy(str, bufsz, hbc->start);
533 off = str_size(hbc->start);
534
535 for (i = 1; i < dim.x - 1; i++) {
536 str_cpy(str + off, bufsz - off, hbc->middle);
537 off += str_size(hbc->middle);
538 }
539
540 str_cpy(str + off, bufsz - off, hbc->end);
541 off += str_size(hbc->end);
542 str[off] = '\0';
543
544 pos = rect->p0;
[4583015]545 rc = gfx_puttext(&pos, &fmt, str);
[81ec7e1]546 if (rc != EOK)
547 goto error;
548
549 free(str);
550 return EOK;
551error:
552 if (str != NULL)
553 free(str);
554 return rc;
555}
556
[ca2680d]557/** Initialize UI text formatting structure.
558 *
559 * UI text formatting structure must always be initialized using this function
560 * first.
561 *
562 * @param fmt UI text formatting structure
563 */
564void ui_text_fmt_init(ui_text_fmt_t *fmt)
565{
566 memset(fmt, 0, sizeof(ui_text_fmt_t));
567}
568
569/** Compute UI text width.
570 *
571 * @param font Font
572 * @param str String
573 * @return Text width
574 */
575gfx_coord_t ui_text_width(gfx_font_t *font, const char *str)
576{
577 gfx_coord_t w;
578 gfx_coord_t tw;
579 const char *sp;
580
581 /* Text including tildes */
582 w = gfx_text_width(font, str);
583 tw = gfx_text_width(font, "~");
584
585 /* Subtract width of singular tildes */
586 sp = str;
587 while (*sp != '\0') {
588 if (*sp == '~' && sp[1] != '~')
589 w -= tw;
590 ++sp;
591 }
592
593 return w;
594}
595
596/** Paint text (with highlighting markup).
597 *
598 * Paint some text with highlighted sections enclosed with tilde characters
599 * ('~'). A literal tilde can be printed with "~~". Text can be highlighted
600 * with an underline or different color, based on display capabilities.
601 *
602 * @param pos Anchor position
603 * @param fmt Text formatting
604 * @param str String containing '~' markup
605 *
606 * @return EOK on success or an error code
607 */
608errno_t ui_paint_text(gfx_coord2_t *pos, ui_text_fmt_t *fmt, const char *str)
609{
610 gfx_coord2_t tpos;
611 gfx_text_fmt_t tfmt;
612 char *buf;
613 char *endptr;
614 const char *sp;
615 errno_t rc;
616
617 /* Break down string into list of (non)highlighted parts */
[96c6a00]618 rc = ui_accel_process(str, &buf, &endptr);
[ca2680d]619 if (rc != EOK)
620 return rc;
621
622 gfx_text_fmt_init(&tfmt);
623 tfmt.font = fmt->font;
624 tfmt.color = fmt->color;
625 tfmt.halign = fmt->halign;
626 tfmt.justify_width = fmt->justify_width;
627 tfmt.valign = fmt->valign;
628 tfmt.underline = false;
629
630 tpos = *pos;
631 sp = buf;
632 while (sp != endptr) {
633 /* Print the current string */
634 rc = gfx_puttext(&tpos, &tfmt, sp);
635 if (rc != EOK)
636 goto error;
637
638 gfx_text_cont(&tpos, &tfmt, sp, &tpos, &tfmt);
639 tfmt.underline = !tfmt.underline;
640 tfmt.color = tfmt.underline ? fmt->hgl_color : fmt->color;
641
642 /* Skip to the next string */
643 while (*sp != '\0')
644 ++sp;
645 ++sp;
646 }
647
648 free(buf);
649 return EOK;
650error:
651 free(buf);
652 return rc;
653}
654
[de9992c]655/** @}
656 */
Note: See TracBrowser for help on using the repository browser.