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

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

Clarify right/bottom-aligned text position and fix off-by-ones

  • Property mode set to 100644
File size: 15.6 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/** Paint window decoration.
162 *
163 * @param wdecor Window decoration
164 * @return EOK on success or an error code
165 */
166errno_t ui_wdecor_paint(ui_wdecor_t *wdecor)
167{
168 errno_t rc;
169 gfx_rect_t rect;
170 gfx_rect_t trect;
171 gfx_rect_t text_rect;
172 gfx_text_fmt_t fmt;
173 gfx_coord2_t pos;
174 ui_wdecor_geom_t geom;
175
176 rect = wdecor->rect;
177 ui_wdecor_get_geom(wdecor, &geom);
178
179 if ((wdecor->style & ui_wds_frame) != 0) {
180
181 if (wdecor->res->textmode != false) {
182 rc = ui_paint_text_box(wdecor->res, &rect,
183 ui_box_double, wdecor->res->wnd_face_color);
184 if (rc != EOK)
185 return rc;
186 } else {
187 rc = ui_paint_outset_frame(wdecor->res, &rect,
188 &rect);
189 if (rc != EOK)
190 return rc;
191
192 rc = ui_paint_bevel(wdecor->res->gc, &rect,
193 wdecor->res->wnd_face_color,
194 wdecor->res->wnd_face_color, 2, &rect);
195 if (rc != EOK)
196 return rc;
197 }
198 }
199
200 if ((wdecor->style & ui_wds_titlebar) != 0) {
201 trect = geom.title_bar_rect;
202
203 if (wdecor->res->textmode == false) {
204 rc = ui_paint_bevel(wdecor->res->gc, &trect,
205 wdecor->res->wnd_shadow_color,
206 wdecor->res->wnd_highlight_color, 1, &trect);
207 if (rc != EOK)
208 return rc;
209
210 rc = gfx_set_color(wdecor->res->gc, wdecor->active ?
211 wdecor->res->tbar_act_bg_color :
212 wdecor->res->tbar_inact_bg_color);
213 if (rc != EOK)
214 return rc;
215
216 rc = gfx_fill_rect(wdecor->res->gc, &trect);
217 if (rc != EOK)
218 return rc;
219 }
220
221 gfx_text_fmt_init(&fmt);
222 fmt.color = wdecor->active ?
223 wdecor->res->tbar_act_text_color :
224 wdecor->res->tbar_inact_text_color;
225 fmt.halign = gfx_halign_center;
226 fmt.valign = gfx_valign_center;
227
228 pos.x = (trect.p0.x + trect.p1.x) / 2;
229 pos.y = (trect.p0.y + trect.p1.y) / 2;
230
231 if (wdecor->res->textmode) {
232 /* Make space around caption text */
233 gfx_text_rect(wdecor->res->font, &pos, &fmt,
234 wdecor->caption, &text_rect);
235
236 /* Only make space if caption is non-empty */
237 if (text_rect.p0.x < text_rect.p1.x) {
238 text_rect.p0.x -= 1;
239 text_rect.p1.x += 1;
240 }
241
242 rc = gfx_set_color(wdecor->res->gc, wdecor->active ?
243 wdecor->res->tbar_act_bg_color :
244 wdecor->res->tbar_inact_bg_color);
245 if (rc != EOK)
246 return rc;
247
248 rc = gfx_fill_rect(wdecor->res->gc, &text_rect);
249 if (rc != EOK)
250 return rc;
251 }
252
253 rc = gfx_puttext(wdecor->res->font, &pos, &fmt, wdecor->caption);
254 if (rc != EOK)
255 return rc;
256
257 if ((wdecor->style & ui_wds_close_btn) != 0) {
258 rc = ui_pbutton_paint(wdecor->btn_close);
259 if (rc != EOK)
260 return rc;
261 }
262 }
263
264 rc = gfx_update(wdecor->res->gc);
265 if (rc != EOK)
266 return rc;
267
268 return EOK;
269}
270
271/** Send decoration close event.
272 *
273 * @param wdecor Window decoration
274 */
275void ui_wdecor_close(ui_wdecor_t *wdecor)
276{
277 if (wdecor->cb != NULL && wdecor->cb->close != NULL)
278 wdecor->cb->close(wdecor, wdecor->arg);
279}
280
281/** Send decoration move event.
282 *
283 * @param wdecor Window decoration
284 * @param pos Position where the title bar was pressed
285 */
286void ui_wdecor_move(ui_wdecor_t *wdecor, gfx_coord2_t *pos)
287{
288 if (wdecor->cb != NULL && wdecor->cb->move != NULL)
289 wdecor->cb->move(wdecor, wdecor->arg, pos);
290}
291
292/** Send decoration resize event.
293 *
294 * @param wdecor Window decoration
295 * @param rsztype Resize type
296 * @param pos Position where the button was pressed
297 */
298void ui_wdecor_resize(ui_wdecor_t *wdecor, ui_wdecor_rsztype_t rsztype,
299 gfx_coord2_t *pos)
300{
301 if (wdecor->cb != NULL && wdecor->cb->resize != NULL)
302 wdecor->cb->resize(wdecor, wdecor->arg, rsztype, pos);
303}
304
305/** Send cursor change event.
306 *
307 * @param wdecor Window decoration
308 * @param cursor Cursor
309 */
310void ui_wdecor_set_cursor(ui_wdecor_t *wdecor, ui_stock_cursor_t cursor)
311{
312 if (wdecor->cb != NULL && wdecor->cb->set_cursor != NULL)
313 wdecor->cb->set_cursor(wdecor, wdecor->arg, cursor);
314}
315
316/** Get window decoration geometry.
317 *
318 * @param wdecor Window decoration
319 * @param geom Structure to fill in with computed geometry
320 */
321void ui_wdecor_get_geom(ui_wdecor_t *wdecor, ui_wdecor_geom_t *geom)
322{
323 gfx_coord_t frame_w;
324
325 /* Does window have a frame? */
326 if ((wdecor->style & ui_wds_frame) != 0) {
327 frame_w = wdecor->res->textmode ?
328 wdecor_frame_w_text : wdecor_frame_w;
329
330 geom->interior_rect.p0.x = wdecor->rect.p0.x + frame_w;
331 geom->interior_rect.p0.y = wdecor->rect.p0.y + frame_w;
332 geom->interior_rect.p1.x = wdecor->rect.p1.x - frame_w;
333 geom->interior_rect.p1.y = wdecor->rect.p1.y - frame_w;
334 } else {
335 geom->interior_rect = wdecor->rect;
336 }
337
338 /* Does window have a title bar? */
339 if ((wdecor->style & ui_wds_titlebar) != 0) {
340 if (wdecor->res->textmode) {
341 geom->title_bar_rect.p0 = wdecor->rect.p0;
342 geom->title_bar_rect.p1.x = wdecor->rect.p1.x;
343 geom->title_bar_rect.p1.y = wdecor->rect.p0.y + 1;
344 } else {
345 geom->title_bar_rect.p0 = geom->interior_rect.p0;
346 geom->title_bar_rect.p1.x = geom->interior_rect.p1.x;
347 geom->title_bar_rect.p1.y = geom->interior_rect.p0.y +
348 wdecor_tbar_h;
349 }
350
351 geom->app_area_rect.p0.x = geom->interior_rect.p0.x;
352 geom->app_area_rect.p0.y = geom->title_bar_rect.p1.y;
353 geom->app_area_rect.p1 = geom->interior_rect.p1;
354 } else {
355 geom->title_bar_rect.p0.x = 0;
356 geom->title_bar_rect.p0.y = 0;
357 geom->title_bar_rect.p1.x = 0;
358 geom->title_bar_rect.p1.y = 0;
359
360 geom->app_area_rect = geom->interior_rect;
361 }
362
363 /* Does window have a close button? */
364 if ((wdecor->style & ui_wds_close_btn) != 0) {
365 if (wdecor->res->textmode == false) {
366 geom->btn_close_rect.p0.x =
367 geom->title_bar_rect.p1.x - 1 - 20;
368 geom->btn_close_rect.p0.y =
369 geom->title_bar_rect.p0.y + 1;
370 geom->btn_close_rect.p1.x =
371 geom->title_bar_rect.p1.x - 1;
372 geom->btn_close_rect.p1.y =
373 geom->title_bar_rect.p0.y + 1 + 20;
374 } else {
375 geom->btn_close_rect.p0.x =
376 geom->title_bar_rect.p1.x - 1 - 3;
377 geom->btn_close_rect.p0.y =
378 geom->title_bar_rect.p0.y;
379 geom->btn_close_rect.p1.x =
380 geom->title_bar_rect.p1.x - 1;
381 geom->btn_close_rect.p1.y =
382 geom->title_bar_rect.p0.y + 1;
383 }
384 } else {
385 geom->btn_close_rect.p0.x = 0;
386 geom->btn_close_rect.p0.y = 0;
387 geom->btn_close_rect.p1.x = 0;
388 geom->btn_close_rect.p1.y = 0;
389 }
390}
391
392/** Get outer rectangle from application area rectangle.
393 *
394 * Note that this needs to work just based on a UI, without having an actual
395 * window decoration, since we need it in order to create the window
396 * and its decoration.
397 *
398 * @param style Decoration style
399 * @param app Application area rectangle
400 * @param rect Place to store (outer) window decoration rectangle
401 */
402void ui_wdecor_rect_from_app(ui_wdecor_style_t style, gfx_rect_t *app,
403 gfx_rect_t *rect)
404{
405 *rect = *app;
406
407 if ((style & ui_wds_frame) != 0) {
408 rect->p0.x -= wdecor_edge_w;
409 rect->p0.y -= wdecor_edge_h;
410 rect->p1.x += wdecor_edge_w;
411 rect->p1.y += wdecor_edge_h;
412 }
413
414 if ((style & ui_wds_titlebar) != 0)
415 rect->p0.y -= 22;
416}
417
418/** Application area rectangle from window rectangle.
419 *
420 * Note that this needs to work just based on a UI, without having an actual
421 * window decoration, since we need it in process of resizing the window,
422 * before it is actually resized.
423 *
424 * @param style Decoration style
425 * @param rect Window decoration rectangle
426 * @param app Place to store application area rectangle
427 */
428void ui_wdecor_app_from_rect(ui_wdecor_style_t style, gfx_rect_t *rect,
429 gfx_rect_t *app)
430{
431 *app = *rect;
432
433 if ((style & ui_wds_frame) != 0) {
434 app->p0.x += wdecor_edge_w;
435 app->p0.y += wdecor_edge_h;
436 app->p1.x -= wdecor_edge_w;
437 app->p1.y -= wdecor_edge_h;
438 }
439
440 if ((style & ui_wds_titlebar) != 0)
441 app->p0.y += 22;
442}
443
444/** Get resize type for pointer at the specified position.
445 *
446 * @param wdecor Window decoration
447 * @param pos Pointer position
448 * @return Resize type
449 */
450ui_wdecor_rsztype_t ui_wdecor_get_rsztype(ui_wdecor_t *wdecor,
451 gfx_coord2_t *pos)
452{
453 bool eleft, eright;
454 bool etop, ebottom;
455 bool edge;
456 bool cleft, cright;
457 bool ctop, cbottom;
458
459 /* Window not resizable? */
460 if ((wdecor->style & ui_wds_resizable) == 0)
461 return ui_wr_none;
462
463 /* Position not inside window? */
464 if (!gfx_pix_inside_rect(pos, &wdecor->rect))
465 return ui_wr_none;
466
467 /* Position is within edge width from the outside */
468 eleft = (pos->x < wdecor->rect.p0.x + wdecor_edge_w);
469 eright = (pos->x >= wdecor->rect.p1.x - wdecor_edge_w);
470 etop = (pos->y < wdecor->rect.p0.y + wdecor_edge_h);
471 ebottom = (pos->y >= wdecor->rect.p1.y - wdecor_edge_h);
472
473 /* Position is on one of the four edges */
474 edge = eleft || eright || etop || ebottom;
475
476 /* Position is within resize-corner distance from the outside */
477 cleft = (pos->x < wdecor->rect.p0.x + wdecor_corner_w);
478 cright = (pos->x >= wdecor->rect.p1.x - wdecor_corner_w);
479 ctop = (pos->y < wdecor->rect.p0.y + wdecor_corner_h);
480 cbottom = (pos->y >= wdecor->rect.p1.y - wdecor_corner_h);
481
482 /* Top-left corner */
483 if (edge && cleft && ctop)
484 return ui_wr_top_left;
485
486 /* Top-right corner */
487 if (edge && cright && ctop)
488 return ui_wr_top_right;
489
490 /* Bottom-left corner */
491 if (edge && cleft && cbottom)
492 return ui_wr_bottom_left;
493
494 /* Bottom-right corner */
495 if (edge && cright && cbottom)
496 return ui_wr_bottom_right;
497
498 /* Left edge */
499 if (eleft)
500 return ui_wr_left;
501
502 /* Right edge */
503 if (eright)
504 return ui_wr_right;
505
506 /* Top edge */
507 if (etop)
508 return ui_wr_top;
509
510 /* Bottom edge */
511 if (ebottom)
512 return ui_wr_bottom;
513
514 return ui_wr_none;
515}
516
517/** Get stock cursor to use for the specified window resize type.
518 *
519 * The resize type must be valid, otherwise behavior is undefined.
520 *
521 * @param rsztype Resize type
522 * @return Cursor to use for this resize type
523 */
524ui_stock_cursor_t ui_wdecor_cursor_from_rsztype(ui_wdecor_rsztype_t rsztype)
525{
526 switch (rsztype) {
527 case ui_wr_none:
528 return ui_curs_arrow;
529
530 case ui_wr_top:
531 case ui_wr_bottom:
532 return ui_curs_size_ud;
533
534 case ui_wr_left:
535 case ui_wr_right:
536 return ui_curs_size_lr;
537
538 case ui_wr_top_left:
539 case ui_wr_bottom_right:
540 return ui_curs_size_uldr;
541
542 case ui_wr_top_right:
543 case ui_wr_bottom_left:
544 return ui_curs_size_urdl;
545
546 default:
547 assert(false);
548 return ui_curs_arrow;
549 }
550}
551
552/** Handle window frame position event.
553 *
554 * @param wdecor Window decoration
555 * @param pos_event Position event
556 */
557void ui_wdecor_frame_pos_event(ui_wdecor_t *wdecor, pos_event_t *event)
558{
559 gfx_coord2_t pos;
560 ui_wdecor_rsztype_t rsztype;
561 ui_stock_cursor_t cursor;
562
563 pos.x = event->hpos;
564 pos.y = event->vpos;
565
566 /* Set appropriate resizing cursor, or set arrow cursor */
567
568 rsztype = ui_wdecor_get_rsztype(wdecor, &pos);
569 cursor = ui_wdecor_cursor_from_rsztype(rsztype);
570
571 ui_wdecor_set_cursor(wdecor, cursor);
572
573 /* Press on window border? */
574 if (rsztype != ui_wr_none && event->type == POS_PRESS)
575 ui_wdecor_resize(wdecor, rsztype, &pos);
576}
577
578/** Handle window decoration position event.
579 *
580 * @param wdecor Window decoration
581 * @param pos_event Position event
582 * @return @c ui_claimed iff event was claimed
583 */
584ui_evclaim_t ui_wdecor_pos_event(ui_wdecor_t *wdecor, pos_event_t *event)
585{
586 gfx_coord2_t pos;
587 ui_wdecor_geom_t geom;
588 ui_evclaim_t claim;
589
590 pos.x = event->hpos;
591 pos.y = event->vpos;
592
593 ui_wdecor_get_geom(wdecor, &geom);
594
595 if ((wdecor->style & ui_wds_close_btn) != 0) {
596 claim = ui_pbutton_pos_event(wdecor->btn_close, event);
597 if (claim == ui_claimed)
598 return ui_claimed;
599 }
600
601 ui_wdecor_frame_pos_event(wdecor, event);
602
603 if ((wdecor->style & ui_wds_titlebar) != 0) {
604 if (event->type == POS_PRESS &&
605 gfx_pix_inside_rect(&pos, &geom.title_bar_rect)) {
606 ui_wdecor_move(wdecor, &pos);
607 return ui_claimed;
608 }
609 }
610
611 return ui_unclaimed;
612}
613
614/** Window decoration close button was clicked.
615 *
616 * @param pbutton Close button
617 * @param arg Argument (ui_wdecor_t)
618 */
619static void ui_wdecor_btn_clicked(ui_pbutton_t *pbutton, void *arg)
620{
621 ui_wdecor_t *wdecor = (ui_wdecor_t *) arg;
622
623 (void) pbutton;
624 ui_wdecor_close(wdecor);
625}
626
627/** @}
628 */
Note: See TracBrowser for help on using the repository browser.