source: mainline/uspace/lib/ui/src/wdecor.c@ f05f413

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

Allow console application to set the terminal window caption

  • Property mode set to 100644
File size: 16.0 KB
Line 
1/*
2 * Copyright (c) 2021 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 Window decoration
34 */
35
36#include <assert.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 <ui/paint.h>
46#include <ui/pbutton.h>
47#include <ui/wdecor.h>
48#include "../private/resource.h"
49#include "../private/wdecor.h"
50
51static void ui_wdecor_btn_clicked(ui_pbutton_t *, void *);
52
53static ui_pbutton_cb_t ui_wdecor_btn_close_cb = {
54 .clicked = ui_wdecor_btn_clicked
55};
56
57enum {
58 wdecor_corner_w = 24,
59 wdecor_corner_h = 24,
60 wdecor_edge_w = 4,
61 wdecor_edge_h = 4,
62 wdecor_tbar_h = 22,
63 wdecor_frame_w = 4,
64 wdecor_frame_w_text = 1
65};
66
67/** Create new window decoration.
68 *
69 * @param resource UI resource
70 * @param caption Window caption
71 * @param style Style
72 * @param rwdecor Place to store pointer to new window decoration
73 * @return EOK on success, ENOMEM if out of memory
74 */
75errno_t ui_wdecor_create(ui_resource_t *resource, const char *caption,
76 ui_wdecor_style_t style, ui_wdecor_t **rwdecor)
77{
78 ui_wdecor_t *wdecor;
79 errno_t rc;
80
81 wdecor = calloc(1, sizeof(ui_wdecor_t));
82 if (wdecor == NULL)
83 return ENOMEM;
84
85 wdecor->caption = str_dup(caption);
86 if (wdecor->caption == NULL) {
87 free(wdecor);
88 return ENOMEM;
89 }
90
91 rc = ui_pbutton_create(resource, "X", &wdecor->btn_close);
92 if (rc != EOK) {
93 free(wdecor->caption);
94 free(wdecor);
95 return rc;
96 }
97
98 ui_pbutton_set_cb(wdecor->btn_close, &ui_wdecor_btn_close_cb,
99 (void *)wdecor);
100
101 wdecor->res = resource;
102 wdecor->active = true;
103 wdecor->style = style;
104 *rwdecor = wdecor;
105 return EOK;
106}
107
108/** Destroy window decoration.
109 *
110 * @param wdecor Window decoration or @c NULL
111 */
112void ui_wdecor_destroy(ui_wdecor_t *wdecor)
113{
114 if (wdecor == NULL)
115 return;
116
117 ui_pbutton_destroy(wdecor->btn_close);
118 free(wdecor->caption);
119 free(wdecor);
120}
121
122/** Set window decoration callbacks.
123 *
124 * @param wdecor Window decoration
125 * @param cb Window decoration callbacks
126 * @param arg Callback argument
127 */
128void ui_wdecor_set_cb(ui_wdecor_t *wdecor, ui_wdecor_cb_t *cb, void *arg)
129{
130 wdecor->cb = cb;
131 wdecor->arg = arg;
132}
133
134/** Set window decoration rectangle.
135 *
136 * @param wdecor Window decoration
137 * @param rect New window decoration rectangle
138 */
139void ui_wdecor_set_rect(ui_wdecor_t *wdecor, gfx_rect_t *rect)
140{
141 ui_wdecor_geom_t geom;
142
143 wdecor->rect = *rect;
144
145 ui_wdecor_get_geom(wdecor, &geom);
146 ui_pbutton_set_rect(wdecor->btn_close, &geom.btn_close_rect);
147}
148
149/** Set active flag.
150 *
151 * Active window is the one receiving keyboard events.
152 *
153 * @param wdecor Window decoration
154 * @param active @c true iff window is active
155 */
156void ui_wdecor_set_active(ui_wdecor_t *wdecor, bool active)
157{
158 wdecor->active = active;
159}
160
161/** Change caption.
162 *
163 * @param wdecor Window decoration
164 * @param caption New caption
165 *
166 * @return EOK on success or an error code
167 */
168errno_t ui_wdecor_set_caption(ui_wdecor_t *wdecor, const char *caption)
169{
170 char *cdup;
171
172 cdup = str_dup(caption);
173 if (cdup == NULL)
174 return ENOMEM;
175
176 free(wdecor->caption);
177 wdecor->caption = cdup;
178
179 ui_wdecor_paint(wdecor);
180 return EOK;
181}
182
183/** Paint window decoration.
184 *
185 * @param wdecor Window decoration
186 * @return EOK on success or an error code
187 */
188errno_t ui_wdecor_paint(ui_wdecor_t *wdecor)
189{
190 errno_t rc;
191 gfx_rect_t rect;
192 gfx_rect_t trect;
193 gfx_rect_t text_rect;
194 gfx_text_fmt_t fmt;
195 gfx_coord2_t pos;
196 ui_wdecor_geom_t geom;
197
198 rect = wdecor->rect;
199 ui_wdecor_get_geom(wdecor, &geom);
200
201 if ((wdecor->style & ui_wds_frame) != 0) {
202
203 if (wdecor->res->textmode != false) {
204 rc = ui_paint_text_box(wdecor->res, &rect,
205 ui_box_double, wdecor->res->wnd_face_color);
206 if (rc != EOK)
207 return rc;
208 } else {
209 rc = ui_paint_outset_frame(wdecor->res, &rect,
210 &rect);
211 if (rc != EOK)
212 return rc;
213
214 rc = ui_paint_bevel(wdecor->res->gc, &rect,
215 wdecor->res->wnd_face_color,
216 wdecor->res->wnd_face_color, 2, &rect);
217 if (rc != EOK)
218 return rc;
219 }
220 }
221
222 if ((wdecor->style & ui_wds_titlebar) != 0) {
223 trect = geom.title_bar_rect;
224
225 if (wdecor->res->textmode == false) {
226 rc = ui_paint_bevel(wdecor->res->gc, &trect,
227 wdecor->res->wnd_shadow_color,
228 wdecor->res->wnd_highlight_color, 1, &trect);
229 if (rc != EOK)
230 return rc;
231
232 rc = gfx_set_color(wdecor->res->gc, wdecor->active ?
233 wdecor->res->tbar_act_bg_color :
234 wdecor->res->tbar_inact_bg_color);
235 if (rc != EOK)
236 return rc;
237
238 rc = gfx_fill_rect(wdecor->res->gc, &trect);
239 if (rc != EOK)
240 return rc;
241 }
242
243 gfx_text_fmt_init(&fmt);
244 fmt.color = wdecor->active ?
245 wdecor->res->tbar_act_text_color :
246 wdecor->res->tbar_inact_text_color;
247 fmt.halign = gfx_halign_center;
248 fmt.valign = gfx_valign_center;
249
250 pos.x = (trect.p0.x + trect.p1.x) / 2;
251 pos.y = (trect.p0.y + trect.p1.y) / 2;
252
253 if (wdecor->res->textmode) {
254 /* Make space around caption text */
255 gfx_text_rect(wdecor->res->font, &pos, &fmt,
256 wdecor->caption, &text_rect);
257
258 /* Only make space if caption is non-empty */
259 if (text_rect.p0.x < text_rect.p1.x) {
260 text_rect.p0.x -= 1;
261 text_rect.p1.x += 1;
262 }
263
264 rc = gfx_set_color(wdecor->res->gc, wdecor->active ?
265 wdecor->res->tbar_act_bg_color :
266 wdecor->res->tbar_inact_bg_color);
267 if (rc != EOK)
268 return rc;
269
270 rc = gfx_fill_rect(wdecor->res->gc, &text_rect);
271 if (rc != EOK)
272 return rc;
273 }
274
275 rc = gfx_puttext(wdecor->res->font, &pos, &fmt, wdecor->caption);
276 if (rc != EOK)
277 return rc;
278
279 if ((wdecor->style & ui_wds_close_btn) != 0) {
280 rc = ui_pbutton_paint(wdecor->btn_close);
281 if (rc != EOK)
282 return rc;
283 }
284 }
285
286 rc = gfx_update(wdecor->res->gc);
287 if (rc != EOK)
288 return rc;
289
290 return EOK;
291}
292
293/** Send decoration close event.
294 *
295 * @param wdecor Window decoration
296 */
297void ui_wdecor_close(ui_wdecor_t *wdecor)
298{
299 if (wdecor->cb != NULL && wdecor->cb->close != NULL)
300 wdecor->cb->close(wdecor, wdecor->arg);
301}
302
303/** Send decoration move event.
304 *
305 * @param wdecor Window decoration
306 * @param pos Position where the title bar was pressed
307 */
308void ui_wdecor_move(ui_wdecor_t *wdecor, gfx_coord2_t *pos)
309{
310 if (wdecor->cb != NULL && wdecor->cb->move != NULL)
311 wdecor->cb->move(wdecor, wdecor->arg, pos);
312}
313
314/** Send decoration resize event.
315 *
316 * @param wdecor Window decoration
317 * @param rsztype Resize type
318 * @param pos Position where the button was pressed
319 */
320void ui_wdecor_resize(ui_wdecor_t *wdecor, ui_wdecor_rsztype_t rsztype,
321 gfx_coord2_t *pos)
322{
323 if (wdecor->cb != NULL && wdecor->cb->resize != NULL)
324 wdecor->cb->resize(wdecor, wdecor->arg, rsztype, pos);
325}
326
327/** Send cursor change event.
328 *
329 * @param wdecor Window decoration
330 * @param cursor Cursor
331 */
332void ui_wdecor_set_cursor(ui_wdecor_t *wdecor, ui_stock_cursor_t cursor)
333{
334 if (wdecor->cb != NULL && wdecor->cb->set_cursor != NULL)
335 wdecor->cb->set_cursor(wdecor, wdecor->arg, cursor);
336}
337
338/** Get window decoration geometry.
339 *
340 * @param wdecor Window decoration
341 * @param geom Structure to fill in with computed geometry
342 */
343void ui_wdecor_get_geom(ui_wdecor_t *wdecor, ui_wdecor_geom_t *geom)
344{
345 gfx_coord_t frame_w;
346
347 /* Does window have a frame? */
348 if ((wdecor->style & ui_wds_frame) != 0) {
349 frame_w = wdecor->res->textmode ?
350 wdecor_frame_w_text : wdecor_frame_w;
351
352 geom->interior_rect.p0.x = wdecor->rect.p0.x + frame_w;
353 geom->interior_rect.p0.y = wdecor->rect.p0.y + frame_w;
354 geom->interior_rect.p1.x = wdecor->rect.p1.x - frame_w;
355 geom->interior_rect.p1.y = wdecor->rect.p1.y - frame_w;
356 } else {
357 geom->interior_rect = wdecor->rect;
358 }
359
360 /* Does window have a title bar? */
361 if ((wdecor->style & ui_wds_titlebar) != 0) {
362 if (wdecor->res->textmode) {
363 geom->title_bar_rect.p0 = wdecor->rect.p0;
364 geom->title_bar_rect.p1.x = wdecor->rect.p1.x;
365 geom->title_bar_rect.p1.y = wdecor->rect.p0.y + 1;
366 } else {
367 geom->title_bar_rect.p0 = geom->interior_rect.p0;
368 geom->title_bar_rect.p1.x = geom->interior_rect.p1.x;
369 geom->title_bar_rect.p1.y = geom->interior_rect.p0.y +
370 wdecor_tbar_h;
371 }
372
373 geom->app_area_rect.p0.x = geom->interior_rect.p0.x;
374 geom->app_area_rect.p0.y = geom->title_bar_rect.p1.y;
375 geom->app_area_rect.p1 = geom->interior_rect.p1;
376 } else {
377 geom->title_bar_rect.p0.x = 0;
378 geom->title_bar_rect.p0.y = 0;
379 geom->title_bar_rect.p1.x = 0;
380 geom->title_bar_rect.p1.y = 0;
381
382 geom->app_area_rect = geom->interior_rect;
383 }
384
385 /* Does window have a close button? */
386 if ((wdecor->style & ui_wds_close_btn) != 0) {
387 if (wdecor->res->textmode == false) {
388 geom->btn_close_rect.p0.x =
389 geom->title_bar_rect.p1.x - 1 - 20;
390 geom->btn_close_rect.p0.y =
391 geom->title_bar_rect.p0.y + 1;
392 geom->btn_close_rect.p1.x =
393 geom->title_bar_rect.p1.x - 1;
394 geom->btn_close_rect.p1.y =
395 geom->title_bar_rect.p0.y + 1 + 20;
396 } else {
397 geom->btn_close_rect.p0.x =
398 geom->title_bar_rect.p1.x - 1 - 3;
399 geom->btn_close_rect.p0.y =
400 geom->title_bar_rect.p0.y;
401 geom->btn_close_rect.p1.x =
402 geom->title_bar_rect.p1.x - 1;
403 geom->btn_close_rect.p1.y =
404 geom->title_bar_rect.p0.y + 1;
405 }
406 } else {
407 geom->btn_close_rect.p0.x = 0;
408 geom->btn_close_rect.p0.y = 0;
409 geom->btn_close_rect.p1.x = 0;
410 geom->btn_close_rect.p1.y = 0;
411 }
412}
413
414/** Get outer rectangle from application area rectangle.
415 *
416 * Note that this needs to work just based on a UI, without having an actual
417 * window decoration, since we need it in order to create the window
418 * and its decoration.
419 *
420 * @param style Decoration style
421 * @param app Application area rectangle
422 * @param rect Place to store (outer) window decoration rectangle
423 */
424void ui_wdecor_rect_from_app(ui_wdecor_style_t style, gfx_rect_t *app,
425 gfx_rect_t *rect)
426{
427 *rect = *app;
428
429 if ((style & ui_wds_frame) != 0) {
430 rect->p0.x -= wdecor_edge_w;
431 rect->p0.y -= wdecor_edge_h;
432 rect->p1.x += wdecor_edge_w;
433 rect->p1.y += wdecor_edge_h;
434 }
435
436 if ((style & ui_wds_titlebar) != 0)
437 rect->p0.y -= 22;
438}
439
440/** Application area rectangle from window rectangle.
441 *
442 * Note that this needs to work just based on a UI, without having an actual
443 * window decoration, since we need it in process of resizing the window,
444 * before it is actually resized.
445 *
446 * @param style Decoration style
447 * @param rect Window decoration rectangle
448 * @param app Place to store application area rectangle
449 */
450void ui_wdecor_app_from_rect(ui_wdecor_style_t style, gfx_rect_t *rect,
451 gfx_rect_t *app)
452{
453 *app = *rect;
454
455 if ((style & ui_wds_frame) != 0) {
456 app->p0.x += wdecor_edge_w;
457 app->p0.y += wdecor_edge_h;
458 app->p1.x -= wdecor_edge_w;
459 app->p1.y -= wdecor_edge_h;
460 }
461
462 if ((style & ui_wds_titlebar) != 0)
463 app->p0.y += 22;
464}
465
466/** Get resize type for pointer at the specified position.
467 *
468 * @param wdecor Window decoration
469 * @param pos Pointer position
470 * @return Resize type
471 */
472ui_wdecor_rsztype_t ui_wdecor_get_rsztype(ui_wdecor_t *wdecor,
473 gfx_coord2_t *pos)
474{
475 bool eleft, eright;
476 bool etop, ebottom;
477 bool edge;
478 bool cleft, cright;
479 bool ctop, cbottom;
480
481 /* Window not resizable? */
482 if ((wdecor->style & ui_wds_resizable) == 0)
483 return ui_wr_none;
484
485 /* Position not inside window? */
486 if (!gfx_pix_inside_rect(pos, &wdecor->rect))
487 return ui_wr_none;
488
489 /* Position is within edge width from the outside */
490 eleft = (pos->x < wdecor->rect.p0.x + wdecor_edge_w);
491 eright = (pos->x >= wdecor->rect.p1.x - wdecor_edge_w);
492 etop = (pos->y < wdecor->rect.p0.y + wdecor_edge_h);
493 ebottom = (pos->y >= wdecor->rect.p1.y - wdecor_edge_h);
494
495 /* Position is on one of the four edges */
496 edge = eleft || eright || etop || ebottom;
497
498 /* Position is within resize-corner distance from the outside */
499 cleft = (pos->x < wdecor->rect.p0.x + wdecor_corner_w);
500 cright = (pos->x >= wdecor->rect.p1.x - wdecor_corner_w);
501 ctop = (pos->y < wdecor->rect.p0.y + wdecor_corner_h);
502 cbottom = (pos->y >= wdecor->rect.p1.y - wdecor_corner_h);
503
504 /* Top-left corner */
505 if (edge && cleft && ctop)
506 return ui_wr_top_left;
507
508 /* Top-right corner */
509 if (edge && cright && ctop)
510 return ui_wr_top_right;
511
512 /* Bottom-left corner */
513 if (edge && cleft && cbottom)
514 return ui_wr_bottom_left;
515
516 /* Bottom-right corner */
517 if (edge && cright && cbottom)
518 return ui_wr_bottom_right;
519
520 /* Left edge */
521 if (eleft)
522 return ui_wr_left;
523
524 /* Right edge */
525 if (eright)
526 return ui_wr_right;
527
528 /* Top edge */
529 if (etop)
530 return ui_wr_top;
531
532 /* Bottom edge */
533 if (ebottom)
534 return ui_wr_bottom;
535
536 return ui_wr_none;
537}
538
539/** Get stock cursor to use for the specified window resize type.
540 *
541 * The resize type must be valid, otherwise behavior is undefined.
542 *
543 * @param rsztype Resize type
544 * @return Cursor to use for this resize type
545 */
546ui_stock_cursor_t ui_wdecor_cursor_from_rsztype(ui_wdecor_rsztype_t rsztype)
547{
548 switch (rsztype) {
549 case ui_wr_none:
550 return ui_curs_arrow;
551
552 case ui_wr_top:
553 case ui_wr_bottom:
554 return ui_curs_size_ud;
555
556 case ui_wr_left:
557 case ui_wr_right:
558 return ui_curs_size_lr;
559
560 case ui_wr_top_left:
561 case ui_wr_bottom_right:
562 return ui_curs_size_uldr;
563
564 case ui_wr_top_right:
565 case ui_wr_bottom_left:
566 return ui_curs_size_urdl;
567
568 default:
569 assert(false);
570 return ui_curs_arrow;
571 }
572}
573
574/** Handle window frame position event.
575 *
576 * @param wdecor Window decoration
577 * @param pos_event Position event
578 */
579void ui_wdecor_frame_pos_event(ui_wdecor_t *wdecor, pos_event_t *event)
580{
581 gfx_coord2_t pos;
582 ui_wdecor_rsztype_t rsztype;
583 ui_stock_cursor_t cursor;
584
585 pos.x = event->hpos;
586 pos.y = event->vpos;
587
588 /* Set appropriate resizing cursor, or set arrow cursor */
589
590 rsztype = ui_wdecor_get_rsztype(wdecor, &pos);
591 cursor = ui_wdecor_cursor_from_rsztype(rsztype);
592
593 ui_wdecor_set_cursor(wdecor, cursor);
594
595 /* Press on window border? */
596 if (rsztype != ui_wr_none && event->type == POS_PRESS)
597 ui_wdecor_resize(wdecor, rsztype, &pos);
598}
599
600/** Handle window decoration position event.
601 *
602 * @param wdecor Window decoration
603 * @param pos_event Position event
604 * @return @c ui_claimed iff event was claimed
605 */
606ui_evclaim_t ui_wdecor_pos_event(ui_wdecor_t *wdecor, pos_event_t *event)
607{
608 gfx_coord2_t pos;
609 ui_wdecor_geom_t geom;
610 ui_evclaim_t claim;
611
612 pos.x = event->hpos;
613 pos.y = event->vpos;
614
615 ui_wdecor_get_geom(wdecor, &geom);
616
617 if ((wdecor->style & ui_wds_close_btn) != 0) {
618 claim = ui_pbutton_pos_event(wdecor->btn_close, event);
619 if (claim == ui_claimed)
620 return ui_claimed;
621 }
622
623 ui_wdecor_frame_pos_event(wdecor, event);
624
625 if ((wdecor->style & ui_wds_titlebar) != 0) {
626 if (event->type == POS_PRESS &&
627 gfx_pix_inside_rect(&pos, &geom.title_bar_rect)) {
628 ui_wdecor_move(wdecor, &pos);
629 return ui_claimed;
630 }
631 }
632
633 return ui_unclaimed;
634}
635
636/** Window decoration close button was clicked.
637 *
638 * @param pbutton Close button
639 * @param arg Argument (ui_wdecor_t)
640 */
641static void ui_wdecor_btn_clicked(ui_pbutton_t *pbutton, void *arg)
642{
643 ui_wdecor_t *wdecor = (ui_wdecor_t *) arg;
644
645 (void) pbutton;
646 ui_wdecor_close(wdecor);
647}
648
649/** @}
650 */
Note: See TracBrowser for help on using the repository browser.