source: mainline/uspace/lib/ui/src/tab.c@ 9e240c1

ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 9e240c1 was 1eaead4, checked in by Jiri Svoboda <jiri@…>, 2 years ago

Tab set control

This allows to expand the space available in a dialog window
using stacking, with individual tabs that can be activated
by clicking the handle.

  • Property mode set to 100644
File size: 15.1 KB
Line 
1/*
2 * Copyright (c) 2023 Jiri Svoboda
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 Tab
34 */
35
36#include <adt/list.h>
37#include <errno.h>
38#include <gfx/color.h>
39#include <gfx/context.h>
40#include <gfx/render.h>
41#include <gfx/text.h>
42#include <io/pos_event.h>
43#include <stdlib.h>
44#include <str.h>
45#include <uchar.h>
46#include <ui/control.h>
47#include <ui/paint.h>
48#include <ui/popup.h>
49#include <ui/tab.h>
50#include <ui/tabset.h>
51#include <ui/resource.h>
52#include <ui/window.h>
53#include "../private/control.h"
54#include "../private/tab.h"
55#include "../private/tabset.h"
56#include "../private/resource.h"
57
58enum {
59 /** Horizontal margin before first tab handle */
60 tab_start_hmargin = 6,
61 /** Horizontal margin before first tab handle in text mode */
62 tab_start_hmargin_text = 1,
63 /** Tab handle horizontal internal padding */
64 tab_handle_hpad = 6,
65 /** Tab handle top internal padding */
66 tab_handle_top_pad = 5,
67 /** Tab handle bottom internal padding */
68 tab_handle_bottom_pad = 5,
69 /** Tab handle horizontal internal padding in text mode */
70 tab_handle_hpad_text = 1,
71 /** Tab handle top internal padding in text mode */
72 tab_handle_top_pad_text = 0,
73 /** Tab handle bototm internal padding in text mode */
74 tab_handle_bottom_pad_text = 1,
75 /** Tab handle chamfer */
76 tab_handle_chamfer = 3,
77 /** Number of pixels to pull active handle up by */
78 tab_handle_pullup = 2,
79 /** Tab frame horizontal thickness */
80 tab_frame_w = 2,
81 /** Tab frame vertical thickness */
82 tab_frame_h = 2,
83 /** Tab frame horizontal thickness in text mode */
84 tab_frame_w_text = 1,
85 /** Tab frame horizontal thickness in text mode */
86 tab_frame_h_text = 1
87};
88
89/** Selected tab handle box characters */
90static ui_box_chars_t sel_tab_box_chars = {
91 {
92 { "\u250c", "\u2500", "\u2510" },
93 { "\u2502", " ", "\u2502" },
94 { "\u2518", " ", "\u2514" }
95 }
96};
97
98/** Not selected tab handle box characters */
99static ui_box_chars_t unsel_tab_box_chars = {
100 {
101 { "\u250c", "\u2500", "\u2510" },
102 { "\u2502", " ", "\u2502" },
103 { "\u2534", "\u2500", "\u2534" }
104 }
105};
106
107/** Create new tab.
108 *
109 * @param tabset Tab set
110 * @param caption Caption
111 * @param rtab Place to store pointer to new tab
112 * @return EOK on success, ENOMEM if out of memory
113 */
114errno_t ui_tab_create(ui_tab_set_t *tabset, const char *caption,
115 ui_tab_t **rtab)
116{
117 ui_tab_t *tab;
118 ui_tab_t *prev;
119
120 tab = calloc(1, sizeof(ui_tab_t));
121 if (tab == NULL)
122 return ENOMEM;
123
124 tab->caption = str_dup(caption);
125 if (tab->caption == NULL) {
126 free(tab);
127 return ENOMEM;
128 }
129
130 prev = ui_tab_last(tabset);
131 if (prev != NULL)
132 tab->xoff = prev->xoff + ui_tab_handle_width(prev);
133 else
134 tab->xoff = tabset->res->textmode ?
135 tab_start_hmargin_text : tab_start_hmargin;
136
137 tab->tabset = tabset;
138 list_append(&tab->ltabs, &tabset->tabs);
139
140 /* This is the first tab. Select it. */
141 if (tabset->selected == NULL)
142 tabset->selected = tab;
143
144 *rtab = tab;
145 return EOK;
146}
147
148/** Destroy tab.
149 *
150 * @param tab Tab or @c NULL
151 */
152void ui_tab_destroy(ui_tab_t *tab)
153{
154 if (tab == NULL)
155 return;
156
157 /* Destroy content */
158 ui_control_destroy(tab->content);
159
160 list_remove(&tab->ltabs);
161 free(tab->caption);
162 free(tab);
163}
164
165/** Get first tab in tab bar.
166 *
167 * @param tabset Tab set
168 * @return First tab or @c NULL if there is none
169 */
170ui_tab_t *ui_tab_first(ui_tab_set_t *tabset)
171{
172 link_t *link;
173
174 link = list_first(&tabset->tabs);
175 if (link == NULL)
176 return NULL;
177
178 return list_get_instance(link, ui_tab_t, ltabs);
179}
180
181/** Get next tab in tab bar.
182 *
183 * @param cur Current tab
184 * @return Next tab or @c NULL if @a cur is the last one
185 */
186ui_tab_t *ui_tab_next(ui_tab_t *cur)
187{
188 link_t *link;
189
190 link = list_next(&cur->ltabs, &cur->tabset->tabs);
191 if (link == NULL)
192 return NULL;
193
194 return list_get_instance(link, ui_tab_t, ltabs);
195}
196
197/** Get last tab in tab bar.
198 *
199 * @param tabset Tab set
200 * @return Last tab or @c NULL if there is none
201 */
202ui_tab_t *ui_tab_last(ui_tab_set_t *tabset)
203{
204 link_t *link;
205
206 link = list_last(&tabset->tabs);
207 if (link == NULL)
208 return NULL;
209
210 return list_get_instance(link, ui_tab_t, ltabs);
211}
212
213/** Get previous tab in tab bar.
214 *
215 * @param cur Current tab
216 * @return Previous tab or @c NULL if @a cur is the fist one
217 */
218ui_tab_t *ui_tab_prev(ui_tab_t *cur)
219{
220 link_t *link;
221
222 link = list_prev(&cur->ltabs, &cur->tabset->tabs);
223 if (link == NULL)
224 return NULL;
225
226 return list_get_instance(link, ui_tab_t, ltabs);
227}
228
229/** Determine if tab is selected.
230 *
231 * @param tab Tab
232 * @return @c true iff tab is selected
233 */
234bool ui_tab_is_selected(ui_tab_t *tab)
235{
236 return tab->tabset->selected == tab;
237}
238
239/** Add control to tab.
240 *
241 * Only one control can be added to a window. If more than one control
242 * is added, the results are undefined.
243 *
244 * @param tab Tab
245 * @param control Control
246 */
247void ui_tab_add(ui_tab_t *tab, ui_control_t *control)
248{
249 assert(tab->content == NULL);
250
251 tab->content = control;
252 control->elemp = (void *) tab;
253}
254
255/** Remove control from tab.
256 *
257 * @param tab Tab
258 * @param control Control
259 */
260void ui_tab_remove(ui_tab_t *tab, ui_control_t *control)
261{
262 assert(tab->content == control);
263 assert((ui_tab_t *) control->elemp == tab);
264
265 tab->content = NULL;
266 control->elemp = NULL;
267}
268
269/** Get tab handle width.
270 *
271 * @param tab Tab
272 * @return Handle width in pixels
273 */
274gfx_coord_t ui_tab_handle_width(ui_tab_t *tab)
275{
276 ui_resource_t *res;
277 gfx_coord_t frame_w;
278 gfx_coord_t handle_hpad;
279 gfx_coord_t text_w;
280
281 res = tab->tabset->res;
282 if (!res->textmode) {
283 frame_w = tab_frame_w;
284 handle_hpad = tab_handle_hpad;
285 } else {
286 frame_w = tab_frame_w_text;
287 handle_hpad = tab_handle_hpad_text;
288 }
289
290 text_w = ui_text_width(tab->tabset->res->font, tab->caption);
291 return 2 * frame_w + 2 * handle_hpad + text_w;
292}
293
294/** Get tab handle height.
295 *
296 * @param tab Tab
297 * @return Handle height in pixels
298 */
299gfx_coord_t ui_tab_handle_height(ui_tab_t *tab)
300{
301 gfx_coord_t frame_h;
302 gfx_coord_t handle_top_pad;
303 gfx_coord_t handle_bottom_pad;
304 gfx_font_metrics_t metrics;
305 ui_resource_t *res;
306
307 res = tab->tabset->res;
308 gfx_font_get_metrics(tab->tabset->res->font, &metrics);
309
310 if (!res->textmode) {
311 frame_h = tab_frame_h;
312 handle_top_pad = tab_handle_top_pad;
313 handle_bottom_pad = tab_handle_bottom_pad;
314 } else {
315 frame_h = tab_frame_h_text;
316 handle_top_pad = tab_handle_top_pad_text;
317 handle_bottom_pad = tab_handle_bottom_pad_text;
318 }
319
320 return frame_h + handle_top_pad + metrics.ascent +
321 metrics.descent + 1 + handle_bottom_pad;
322}
323
324/** Get tab geometry.
325 *
326 * @param tab Tab
327 * @param geom Structure to fill in with computed geometry
328 */
329void ui_tab_get_geom(ui_tab_t *tab, ui_tab_geom_t *geom)
330{
331 gfx_coord_t handle_w;
332 gfx_coord_t handle_h;
333 gfx_coord_t pullup;
334 gfx_coord_t frame_w;
335 gfx_coord_t frame_h;
336 gfx_coord_t handle_hpad;
337 gfx_coord_t handle_top_pad;
338 ui_resource_t *res;
339
340 res = tab->tabset->res;
341
342 handle_w = ui_tab_handle_width(tab);
343 handle_h = ui_tab_handle_height(tab);
344 pullup = res->textmode ? 0 : tab_handle_pullup;
345
346 if (!res->textmode) {
347 frame_w = tab_frame_w;
348 frame_h = tab_frame_h;
349 handle_hpad = tab_handle_hpad;
350 handle_top_pad = tab_handle_top_pad;
351 } else {
352 frame_w = tab_frame_w_text;
353 frame_h = tab_frame_h_text;
354 handle_hpad = tab_handle_hpad_text;
355 handle_top_pad = tab_handle_top_pad_text;
356 }
357
358 /* Entire handle area */
359 geom->handle_area.p0.x = tab->tabset->rect.p0.x + tab->xoff;
360 geom->handle_area.p0.y = tab->tabset->rect.p0.y;
361 geom->handle_area.p1.x = geom->handle_area.p0.x + handle_w;
362 geom->handle_area.p1.y = geom->handle_area.p0.y + handle_h + pullup;
363
364 geom->handle = geom->handle_area;
365
366 /* If handle is selected */
367 if (!ui_tab_is_selected(tab)) {
368 /* Push top of handle down a bit */
369 geom->handle.p0.y += pullup;
370 /* Do not paint background over tab body frame */
371 geom->handle_area.p1.y -= pullup;
372 }
373
374 /* Caption text position */
375 geom->text_pos.x = geom->handle.p0.x + frame_w + handle_hpad;
376 geom->text_pos.y = geom->handle.p0.y + frame_h + handle_top_pad;
377
378 /* Tab body */
379 geom->body.p0.x = tab->tabset->rect.p0.x;
380 geom->body.p0.y = tab->tabset->rect.p0.y + handle_h - frame_h +
381 pullup;
382 geom->body.p1 = tab->tabset->rect.p1;
383}
384
385/** Get UI resource from tab.
386 *
387 * @param tab Tab
388 * @return UI resource
389 */
390ui_resource_t *ui_tab_get_res(ui_tab_t *tab)
391{
392 return tab->tabset->res;
393}
394
395/** Paint tab handle frame.
396 *
397 * @param gc Graphic context
398 * @param rect Rectangle
399 * @param chamfer Chamfer
400 * @param hi_color Highlight color
401 * @param sh_color Shadow color
402 * @param selected Tab is selected
403 * @param irect Place to store interior rectangle
404 * @return EOK on success or an error code
405 */
406errno_t ui_tab_paint_handle_frame(gfx_context_t *gc, gfx_rect_t *rect,
407 gfx_coord_t chamfer, gfx_color_t *hi_color, gfx_color_t *sh_color,
408 bool selected, gfx_rect_t *irect)
409{
410 gfx_rect_t r;
411 gfx_coord_t i;
412 errno_t rc;
413
414 rc = gfx_set_color(gc, hi_color);
415 if (rc != EOK)
416 goto error;
417
418 /* Left side */
419 r.p0.x = rect->p0.x;
420 r.p0.y = rect->p0.y + chamfer;
421 r.p1.x = rect->p0.x + 1;
422 r.p1.y = rect->p1.y - 2;
423 rc = gfx_fill_rect(gc, &r);
424 if (rc != EOK)
425 goto error;
426
427 /* Top-left chamfer */
428 for (i = 1; i < chamfer; i++) {
429 r.p0.x = rect->p0.x + i;
430 r.p0.y = rect->p0.y + chamfer - i;
431 r.p1.x = r.p0.x + 1;
432 r.p1.y = r.p0.y + 1;
433 rc = gfx_fill_rect(gc, &r);
434 if (rc != EOK)
435 goto error;
436 }
437
438 /* Top side */
439 r.p0.x = rect->p0.x + chamfer;
440 r.p0.y = rect->p0.y;
441 r.p1.x = rect->p1.x - chamfer;
442 r.p1.y = rect->p0.y + 1;
443 rc = gfx_fill_rect(gc, &r);
444 if (rc != EOK)
445 goto error;
446
447 rc = gfx_set_color(gc, sh_color);
448 if (rc != EOK)
449 goto error;
450
451 /* Top-right chamfer */
452 for (i = 1; i < chamfer; i++) {
453 r.p0.x = rect->p1.x - 1 - i;
454 r.p0.y = rect->p0.y + chamfer - i;
455 r.p1.x = r.p0.x + 1;
456 r.p1.y = r.p0.y + 1;
457 rc = gfx_fill_rect(gc, &r);
458 if (rc != EOK)
459 goto error;
460 }
461
462 /* Right side */
463 r.p0.x = rect->p1.x - 1;
464 r.p0.y = rect->p0.y + chamfer;
465 r.p1.x = rect->p1.x;
466 r.p1.y = rect->p1.y - 2;
467 rc = gfx_fill_rect(gc, &r);
468 if (rc != EOK)
469 goto error;
470
471 irect->p0.x = rect->p0.x + 1;
472 irect->p0.y = rect->p0.y + 1;
473 irect->p1.x = rect->p1.x - 1;
474 irect->p1.y = rect->p1.y;
475 return EOK;
476error:
477 return rc;
478}
479
480/** Paint tab body frame.
481 *
482 * @param tab Tab
483 * @return EOK on success or an error code
484 */
485errno_t ui_tab_paint_body_frame(ui_tab_t *tab)
486{
487 gfx_rect_t bg_rect;
488 ui_tab_geom_t geom;
489 ui_resource_t *res;
490 errno_t rc;
491
492 res = ui_tab_get_res(tab);
493 ui_tab_get_geom(tab, &geom);
494
495 if (!res->textmode) {
496 rc = ui_paint_outset_frame(res, &geom.body, &bg_rect);
497 if (rc != EOK)
498 goto error;
499 } else {
500 rc = ui_paint_text_box(res, &geom.body, ui_box_single,
501 res->wnd_face_color);
502 if (rc != EOK)
503 goto error;
504
505 bg_rect.p0.x = geom.body.p0.x + 1;
506 bg_rect.p0.y = geom.body.p0.y + 1;
507 bg_rect.p1.x = geom.body.p1.x - 1;
508 bg_rect.p1.y = geom.body.p1.y - 1;
509 }
510
511 rc = gfx_set_color(res->gc, res->wnd_face_color);
512 if (rc != EOK)
513 goto error;
514
515 rc = gfx_fill_rect(res->gc, &bg_rect);
516 if (rc != EOK)
517 goto error;
518
519 return EOK;
520error:
521 return rc;
522}
523
524/** Paint tab frame.
525 *
526 * @param tab Tab
527 * @return EOK on success or an error code
528 */
529errno_t ui_tab_paint_frame(ui_tab_t *tab)
530{
531 gfx_rect_t r0;
532 ui_tab_geom_t geom;
533 ui_resource_t *res;
534 errno_t rc;
535
536 res = ui_tab_get_res(tab);
537 ui_tab_get_geom(tab, &geom);
538
539 /* Paint handle background */
540
541 rc = gfx_set_color(res->gc, res->wnd_face_color);
542 if (rc != EOK)
543 goto error;
544
545 rc = gfx_fill_rect(res->gc, &geom.handle_area);
546 if (rc != EOK)
547 goto error;
548
549 /* Paint handle frame */
550 if (!res->textmode) {
551 rc = ui_tab_paint_handle_frame(res->gc, &geom.handle,
552 tab_handle_chamfer, res->wnd_frame_hi_color, res->wnd_frame_sh_color,
553 ui_tab_is_selected(tab), &r0);
554 if (rc != EOK)
555 goto error;
556
557 rc = ui_tab_paint_handle_frame(res->gc, &r0, tab_handle_chamfer - 1,
558 res->wnd_highlight_color, res->wnd_shadow_color,
559 ui_tab_is_selected(tab), &r0);
560 if (rc != EOK)
561 goto error;
562 } else {
563 rc = ui_paint_text_box_custom(res, &geom.handle,
564 ui_tab_is_selected(tab) ? &sel_tab_box_chars :
565 &unsel_tab_box_chars, res->wnd_face_color);
566 if (rc != EOK)
567 goto error;
568 }
569
570 return EOK;
571error:
572 return rc;
573}
574
575/** Paint tab.
576 *
577 * @param tab Tab
578 * @return EOK on success or an error code
579 */
580errno_t ui_tab_paint(ui_tab_t *tab)
581{
582 gfx_text_fmt_t fmt;
583 ui_tab_geom_t geom;
584 ui_resource_t *res;
585 errno_t rc;
586
587 res = ui_tab_get_res(tab);
588 ui_tab_get_geom(tab, &geom);
589
590 rc = ui_tab_paint_frame(tab);
591 if (rc != EOK)
592 goto error;
593
594 /* Paint caption */
595
596 gfx_text_fmt_init(&fmt);
597 fmt.font = res->font;
598 fmt.halign = gfx_halign_left;
599 fmt.valign = gfx_valign_top;
600 fmt.color = res->wnd_text_color;
601
602 rc = gfx_puttext(&geom.text_pos, &fmt, tab->caption);
603 if (rc != EOK)
604 goto error;
605
606 if (tab->content != NULL && ui_tab_is_selected(tab)) {
607 /* Paint content */
608 rc = ui_control_paint(tab->content);
609 if (rc != EOK)
610 goto error;
611 }
612
613 rc = gfx_update(res->gc);
614 if (rc != EOK)
615 goto error;
616
617 return EOK;
618error:
619 return rc;
620}
621
622/** Handle position event in tab.
623 *
624 * @param tab Tab
625 * @param event Position event
626 * @return ui_claimed iff the event was claimed
627 */
628ui_evclaim_t ui_tab_pos_event(ui_tab_t *tab, pos_event_t *event)
629{
630 ui_tab_geom_t geom;
631 gfx_coord2_t epos;
632
633 ui_tab_get_geom(tab, &geom);
634 epos.x = event->hpos;
635 epos.y = event->vpos;
636
637 /* Event inside tab handle? */
638 if (gfx_pix_inside_rect(&epos, &geom.handle)) {
639 /* Select tab? */
640 if (event->type == POS_PRESS && event->btn_num == 1 &&
641 !ui_tab_is_selected(tab))
642 ui_tab_set_select(tab->tabset, tab);
643
644 /* Claim event */
645 return ui_claimed;
646 }
647
648 /* Deliver event to content control, if any */
649 if (ui_tab_is_selected(tab) && tab->content != NULL)
650 return ui_control_pos_event(tab->content, event);
651
652 return ui_unclaimed;
653}
654
655/** Handle keyboard event in tab.
656 *
657 * @param tab Tab
658 * @param event Keyboard event
659 * @return ui_claimed iff the event was claimed
660 */
661ui_evclaim_t ui_tab_kbd_event(ui_tab_t *tab, kbd_event_t *event)
662{
663 /* Deliver event to content control, if any */
664 if (ui_tab_is_selected(tab) && tab->content != NULL)
665 return ui_control_kbd_event(tab->content, event);
666
667 return ui_unclaimed;
668}
669
670/** @}
671 */
Note: See TracBrowser for help on using the repository browser.