source: mainline/uspace/lib/ui/src/pbutton.c@ cdd6fc9

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

Fix rendering of very small buttons

In text mode we have some narrow buttons (scrollbar, close button etc.)
where if we subtract shadow width and padding with we arrive at negative
width. Trying to format the text to negative width causes trouble.
While somewhat hackish, we allow at least one character to be printed
even for very small buttons.

This fixes e.g. Navigator not starting.

  • Property mode set to 100644
File size: 14.6 KB
RevLine 
[f80690a]1/*
[d68239a1]2 * Copyright (c) 2022 Jiri Svoboda
[f80690a]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 Push button
[d68239a1]34 *
35 * Push button either uses text as decoration, or it can use a caller-provided
36 * function to paint the decoration.
[f80690a]37 */
38
39#include <errno.h>
[47728678]40#include <gfx/color.h>
41#include <gfx/context.h>
42#include <gfx/render.h>
43#include <gfx/text.h>
[faca61b8]44#include <io/pos_event.h>
[f80690a]45#include <stdlib.h>
[47728678]46#include <str.h>
[8009dc27]47#include <ui/control.h>
[de9992c]48#include <ui/paint.h>
[f80690a]49#include <ui/pbutton.h>
50#include "../private/pbutton.h"
[47728678]51#include "../private/resource.h"
[f80690a]52
[f6df5a3]53/** Caption movement when button is pressed down */
54enum {
[c9a7adc]55 ui_pb_press_dx = 1,
[68d68e9]56 ui_pb_press_dy = 1,
57 ui_pb_pad_x = 2,
58 ui_pb_pad_x_text = 1
[f6df5a3]59};
60
[c6f00b40]61static void ui_pbutton_ctl_destroy(void *);
[4df6607]62static errno_t ui_pbutton_ctl_paint(void *);
[8009dc27]63static ui_evclaim_t ui_pbutton_ctl_pos_event(void *, pos_event_t *);
64
65/** Push button control ops */
66ui_control_ops_t ui_pbutton_ops = {
[c6f00b40]67 .destroy = ui_pbutton_ctl_destroy,
[4df6607]68 .paint = ui_pbutton_ctl_paint,
[8009dc27]69 .pos_event = ui_pbutton_ctl_pos_event
70};
71
[f80690a]72/** Create new push button.
73 *
[47728678]74 * @param resource UI resource
[f80690a]75 * @param caption Caption
76 * @param rpbutton Place to store pointer to new push button
77 * @return EOK on success, ENOMEM if out of memory
78 */
[3583ffb]79errno_t ui_pbutton_create(ui_resource_t *resource, const char *caption,
[47728678]80 ui_pbutton_t **rpbutton)
[f80690a]81{
82 ui_pbutton_t *pbutton;
[8009dc27]83 errno_t rc;
[f80690a]84
85 pbutton = calloc(1, sizeof(ui_pbutton_t));
86 if (pbutton == NULL)
87 return ENOMEM;
88
[8009dc27]89 rc = ui_control_new(&ui_pbutton_ops, (void *) pbutton,
90 &pbutton->control);
91 if (rc != EOK) {
92 free(pbutton);
93 return rc;
94 }
95
[47728678]96 pbutton->caption = str_dup(caption);
97 if (pbutton->caption == NULL) {
[8009dc27]98 ui_control_delete(pbutton->control);
[47728678]99 free(pbutton);
100 return ENOMEM;
101 }
102
[3583ffb]103 pbutton->res = resource;
[f80690a]104 *rpbutton = pbutton;
105 return EOK;
106}
107
108/** Destroy push button.
109 *
110 * @param pbutton Push button or @c NULL
111 */
112void ui_pbutton_destroy(ui_pbutton_t *pbutton)
113{
114 if (pbutton == NULL)
115 return;
116
[8009dc27]117 ui_control_delete(pbutton->control);
[f1f433d]118 free(pbutton->caption);
[f80690a]119 free(pbutton);
120}
121
[8009dc27]122/** Get base control from push button.
123 *
124 * @param pbutton Push button
125 * @return Control
126 */
127ui_control_t *ui_pbutton_ctl(ui_pbutton_t *pbutton)
128{
129 return pbutton->control;
130}
131
[8ef48ece]132/** Set push button callbacks.
133 *
134 * @param pbutton Push button
135 * @param cb Push button callbacks
136 * @param arg Callback argument
137 */
138void ui_pbutton_set_cb(ui_pbutton_t *pbutton, ui_pbutton_cb_t *cb, void *arg)
139{
140 pbutton->cb = cb;
141 pbutton->arg = arg;
142}
143
[d68239a1]144/** Set push button decoration ops.
145 *
146 * @param pbutton Push button
147 * @param ops Push button decoration callbacks
148 * @param arg Decoration ops argument
149 */
150void ui_pbutton_set_decor_ops(ui_pbutton_t *pbutton,
151 ui_pbutton_decor_ops_t *ops, void *arg)
152{
153 pbutton->decor_ops = ops;
154 pbutton->decor_arg = arg;
155}
156
[8b22d44]157/** Set push button flag.s
158 *
159 * @param pbutton Push button
160 * @param flags Flags
161 */
162void ui_pbutton_set_flags(ui_pbutton_t *pbutton, ui_pbutton_flags_t flags)
163{
164 pbutton->flags = flags;
165}
166
[47728678]167/** Set button rectangle.
168 *
169 * @param pbutton Button
[ba09d06]170 * @param rect New button rectangle
[47728678]171 */
172void ui_pbutton_set_rect(ui_pbutton_t *pbutton, gfx_rect_t *rect)
173{
174 pbutton->rect = *rect;
175}
176
[c9a7adc]177/** Set default flag.
178 *
179 * Default button is the one activated by Enter key, it is marked
180 * by thicker frame.
181 *
182 * @param pbutton Button
183 * @param isdefault @c true iff button is default
184 */
185void ui_pbutton_set_default(ui_pbutton_t *pbutton, bool isdefault)
186{
187 pbutton->isdefault = isdefault;
188}
189
[f1f433d]190/** Set push button caption.
191 *
192 * @param pbutton Button
193 * @param caption New caption
194 * @return EOK on success, ENOMEM if out of memory
195 */
196errno_t ui_pbutton_set_caption(ui_pbutton_t *pbutton, const char *caption)
197{
198 char *dcaption;
199
200 dcaption = str_dup(caption);
201 if (dcaption == NULL)
202 return ENOMEM;
203
204 free(pbutton->caption);
205 pbutton->caption = dcaption;
206 return EOK;
207}
208
[c9a7adc]209/** Paint outer button frame.
[47728678]210 *
211 * @param pbutton Push button
212 * @return EOK on success or an error code
213 */
[c9a7adc]214static errno_t ui_pbutton_paint_frame(ui_pbutton_t *pbutton)
[47728678]215{
[c9a7adc]216 gfx_rect_t rect;
217 gfx_coord_t thickness;
[47728678]218 errno_t rc;
219
[c9a7adc]220 thickness = pbutton->isdefault ? 2 : 1;
221
[de9992c]222 rc = gfx_set_color(pbutton->res->gc, pbutton->res->btn_frame_color);
[c9a7adc]223 if (rc != EOK)
224 goto error;
225
226 rect.p0.x = pbutton->rect.p0.x + 1;
227 rect.p0.y = pbutton->rect.p0.y;
228 rect.p1.x = pbutton->rect.p1.x - 1;
229 rect.p1.y = pbutton->rect.p0.y + thickness;
230 rc = gfx_fill_rect(pbutton->res->gc, &rect);
231 if (rc != EOK)
232 goto error;
233
234 rect.p0.x = pbutton->rect.p0.x + 1;
235 rect.p0.y = pbutton->rect.p1.y - thickness;
236 rect.p1.x = pbutton->rect.p1.x - 1;
237 rect.p1.y = pbutton->rect.p1.y;
238 rc = gfx_fill_rect(pbutton->res->gc, &rect);
239 if (rc != EOK)
240 goto error;
241
242 rect.p0.x = pbutton->rect.p0.x;
243 rect.p0.y = pbutton->rect.p0.y + 1;
244 rect.p1.x = pbutton->rect.p0.x + thickness;
245 rect.p1.y = pbutton->rect.p1.y - 1;
246 rc = gfx_fill_rect(pbutton->res->gc, &rect);
247 if (rc != EOK)
248 goto error;
249
250 rect.p0.x = pbutton->rect.p1.x - thickness;
251 rect.p0.y = pbutton->rect.p0.y + 1;
252 rect.p1.x = pbutton->rect.p1.x;
253 rect.p1.y = pbutton->rect.p1.y - 1;
254 rc = gfx_fill_rect(pbutton->res->gc, &rect);
255 if (rc != EOK)
256 goto error;
257
258 return EOK;
259error:
260 return rc;
261}
262
263/** Paint outset button bevel.
264 *
265 * @param pbutton Push button
266 * @return EOK on success or an error code
267 */
268static errno_t ui_pbutton_paint_outset(ui_pbutton_t *pbutton,
269 gfx_rect_t *rect)
270{
[de9992c]271 return ui_paint_bevel(pbutton->res->gc, rect,
272 pbutton->res->btn_highlight_color,
273 pbutton->res->btn_shadow_color, 2, NULL);
[c9a7adc]274}
275
276/** Paint inset button bevel.
277 *
278 * @param pbutton Push button
279 * @return EOK on success or an error code
280 */
281static errno_t ui_pbutton_paint_inset(ui_pbutton_t *pbutton,
282 gfx_rect_t *rect)
283{
[de9992c]284 return ui_paint_bevel(pbutton->res->gc, rect,
285 pbutton->res->btn_shadow_color,
286 pbutton->res->btn_face_color, 2, NULL);
[c9a7adc]287}
288
[cd74fa8]289/** Paint button text shadow.
[c9a7adc]290 *
291 * @param pbutton Push button
292 * @return EOK on success or an error code
293 */
[cd74fa8]294static errno_t ui_pbutton_paint_text_shadow(ui_pbutton_t *pbutton)
295{
296 gfx_rect_t rect;
297 errno_t rc;
298
299 rect.p0.x = pbutton->rect.p0.x + 1;
300 rect.p0.y = pbutton->rect.p0.y + 1;
301 rect.p1.x = pbutton->rect.p1.x;
302 rect.p1.y = pbutton->rect.p1.y;
303
304 rc = gfx_set_color(pbutton->res->gc, pbutton->res->btn_shadow_color);
305 if (rc != EOK)
306 goto error;
307
308 rc = gfx_fill_rect(pbutton->res->gc, &rect);
309 if (rc != EOK)
310 goto error;
311
312 return EOK;
313error:
314 return rc;
315}
316
317/** Paint push button in graphic mode.
318 *
319 * @param pbutton Push button
320 * @return EOK on success or an error code
321 */
322static errno_t ui_pbutton_paint_gfx(ui_pbutton_t *pbutton)
[c9a7adc]323{
324 gfx_coord2_t pos;
325 gfx_text_fmt_t fmt;
326 gfx_rect_t rect;
[68d68e9]327 gfx_rect_t irect;
[c9a7adc]328 gfx_coord_t thickness;
[8ef48ece]329 bool depressed;
[c9a7adc]330 errno_t rc;
331
332 thickness = pbutton->isdefault ? 2 : 1;
[8ef48ece]333 depressed = pbutton->held && pbutton->inside;
[c9a7adc]334
335 rect.p0.x = pbutton->rect.p0.x + thickness;
336 rect.p0.y = pbutton->rect.p0.y + thickness;
337 rect.p1.x = pbutton->rect.p1.x - thickness;
338 rect.p1.y = pbutton->rect.p1.y - thickness;
339
[de9992c]340 rc = gfx_set_color(pbutton->res->gc, pbutton->res->btn_face_color);
[c9a7adc]341 if (rc != EOK)
342 goto error;
343
344 rc = gfx_fill_rect(pbutton->res->gc, &rect);
345 if (rc != EOK)
346 goto error;
347
[47728678]348 /* Center of button rectangle */
[c9a7adc]349 pos.x = (rect.p0.x + rect.p1.x) / 2;
350 pos.y = (rect.p0.y + rect.p1.y) / 2;
[47728678]351
[8ef48ece]352 if (depressed) {
[f6df5a3]353 pos.x += ui_pb_press_dx;
354 pos.y += ui_pb_press_dy;
355 }
356
[d68239a1]357 if (pbutton->decor_ops != NULL && pbutton->decor_ops->paint != NULL) {
358 /* Custom decoration */
359 rc = pbutton->decor_ops->paint(pbutton, pbutton->decor_arg,
360 &pos);
361 if (rc != EOK)
362 goto error;
363 } else {
364 /* Text decoration */
[68d68e9]365 ui_paint_get_inset_frame_inside(pbutton->res, &rect, &irect);
[d68239a1]366 gfx_text_fmt_init(&fmt);
367 fmt.font = pbutton->res->font;
368 fmt.color = pbutton->res->btn_text_color;
369 fmt.halign = gfx_halign_center;
370 fmt.valign = gfx_valign_center;
[68d68e9]371 fmt.abbreviate = true;
372 fmt.width = irect.p1.x - irect.p0.x - 2 * ui_pb_pad_x;
[d68239a1]373
374 rc = gfx_puttext(&pos, &fmt, pbutton->caption);
375 if (rc != EOK)
376 goto error;
377 }
[47728678]378
[c9a7adc]379 rc = ui_pbutton_paint_frame(pbutton);
380 if (rc != EOK)
381 goto error;
382
[8ef48ece]383 if (depressed) {
[c9a7adc]384 rc = ui_pbutton_paint_inset(pbutton, &rect);
385 if (rc != EOK)
386 goto error;
387 } else {
388 rc = ui_pbutton_paint_outset(pbutton, &rect);
389 if (rc != EOK)
390 goto error;
391 }
[47728678]392
[2ab8ab3]393 rc = gfx_update(pbutton->res->gc);
394 if (rc != EOK)
395 goto error;
396
[47728678]397 return EOK;
398error:
399 return rc;
400}
401
[cd74fa8]402/** Paint push button in text mode.
403 *
404 * @param pbutton Push button
405 * @return EOK on success or an error code
406 */
407static errno_t ui_pbutton_paint_text(ui_pbutton_t *pbutton)
408{
409 gfx_coord2_t pos;
410 gfx_text_fmt_t fmt;
411 gfx_rect_t rect;
412 bool depressed;
413 errno_t rc;
414
[8b22d44]415 if ((pbutton->flags & ui_pbf_no_text_depress) == 0)
416 depressed = pbutton->held && pbutton->inside;
417 else
418 depressed = false;
[cd74fa8]419
420 rc = gfx_set_color(pbutton->res->gc, pbutton->res->wnd_face_color);
421 if (rc != EOK)
422 goto error;
423
424 rc = gfx_fill_rect(pbutton->res->gc, &pbutton->rect);
425 if (rc != EOK)
426 goto error;
427
428 rect.p0.x = pbutton->rect.p0.x + (depressed ? 1 : 0);
429 rect.p0.y = pbutton->rect.p0.y;
430 rect.p1.x = pbutton->rect.p1.x - 1 + (depressed ? 1 : 0);
431 rect.p1.y = pbutton->rect.p0.y + 1;
432
[bc52b5b]433 rc = gfx_set_color(pbutton->res->gc, pbutton->res->btn_face_color);
[cd74fa8]434 if (rc != EOK)
435 goto error;
436
437 rc = gfx_fill_rect(pbutton->res->gc, &rect);
438 if (rc != EOK)
439 goto error;
440
441 /* Center of button rectangle */
442 pos.x = (rect.p0.x + rect.p1.x) / 2;
443 pos.y = (rect.p0.y + rect.p1.y) / 2;
444
445 gfx_text_fmt_init(&fmt);
[4583015]446 fmt.font = pbutton->res->font;
[cd74fa8]447 fmt.color = pbutton->res->btn_text_color;
448 fmt.halign = gfx_halign_center;
449 fmt.valign = gfx_valign_center;
[68d68e9]450 fmt.abbreviate = true;
451 fmt.width = rect.p1.x - rect.p0.x - 2 * ui_pb_pad_x_text;
[795c6f7]452 if (fmt.width < 1)
453 fmt.width = 1;
[cd74fa8]454
[4583015]455 rc = gfx_puttext(&pos, &fmt, pbutton->caption);
[cd74fa8]456 if (rc != EOK)
457 goto error;
458
459 if (!depressed) {
460 rc = ui_pbutton_paint_text_shadow(pbutton);
461 if (rc != EOK)
462 goto error;
463 }
464
465 rc = gfx_update(pbutton->res->gc);
466 if (rc != EOK)
467 goto error;
468
469 return EOK;
470error:
471 return rc;
472}
473
474/** Paint push button.
475 *
476 * @param pbutton Push button
477 * @return EOK on success or an error code
478 */
479errno_t ui_pbutton_paint(ui_pbutton_t *pbutton)
480{
481 if (pbutton->res->textmode)
482 return ui_pbutton_paint_text(pbutton);
483 else
484 return ui_pbutton_paint_gfx(pbutton);
485}
486
[f6df5a3]487/** Press down button.
488 *
489 * @param pbutton Push button
490 */
491void ui_pbutton_press(ui_pbutton_t *pbutton)
492{
[8ef48ece]493 if (pbutton->held)
494 return;
495
496 pbutton->inside = true;
[f6df5a3]497 pbutton->held = true;
[8ef48ece]498 (void) ui_pbutton_paint(pbutton);
[d4ea1f6]499 ui_pbutton_down(pbutton);
[f6df5a3]500}
501
502/** Release button.
503 *
504 * @param pbutton Push button
505 */
506void ui_pbutton_release(ui_pbutton_t *pbutton)
507{
[8ef48ece]508 if (!pbutton->held)
509 return;
510
[f6df5a3]511 pbutton->held = false;
[174be87]512 ui_pbutton_up(pbutton);
[8ef48ece]513
514 if (pbutton->inside) {
515 (void) ui_pbutton_paint(pbutton);
516 ui_pbutton_clicked(pbutton);
517 }
518}
519
520/** Pointer entered button.
521 *
522 * @param pbutton Push button
523 */
524void ui_pbutton_enter(ui_pbutton_t *pbutton)
525{
526 if (pbutton->inside)
527 return;
528
529 pbutton->inside = true;
530 if (pbutton->held)
531 (void) ui_pbutton_paint(pbutton);
532}
533
534/** Pointer left button.
535 *
536 * @param pbutton Push button
537 */
538void ui_pbutton_leave(ui_pbutton_t *pbutton)
539{
540 if (!pbutton->inside)
541 return;
542
543 pbutton->inside = false;
544 if (pbutton->held)
545 (void) ui_pbutton_paint(pbutton);
546}
547
[d4ea1f6]548/** Send button clicked event.
[8ef48ece]549 *
550 * @param pbutton Push button
551 */
552void ui_pbutton_clicked(ui_pbutton_t *pbutton)
553{
554 if (pbutton->cb != NULL && pbutton->cb->clicked != NULL)
555 pbutton->cb->clicked(pbutton, pbutton->arg);
[f6df5a3]556}
557
[d4ea1f6]558/** Send button down event.
559 *
560 * @param pbutton Push button
561 */
562void ui_pbutton_down(ui_pbutton_t *pbutton)
563{
564 if (pbutton->cb != NULL && pbutton->cb->down != NULL)
565 pbutton->cb->down(pbutton, pbutton->arg);
566}
567
568/** Send button up event.
569 *
570 * @param pbutton Push button
571 */
572void ui_pbutton_up(ui_pbutton_t *pbutton)
573{
574 if (pbutton->cb != NULL && pbutton->cb->up != NULL)
575 pbutton->cb->up(pbutton, pbutton->arg);
576}
577
[faca61b8]578/** Handle push button position event.
579 *
580 * @param pbutton Push button
581 * @param pos_event Position event
[a2f173b]582 * @return @c ui_claimed iff the event is claimed
[faca61b8]583 */
[a2f173b]584ui_evclaim_t ui_pbutton_pos_event(ui_pbutton_t *pbutton, pos_event_t *event)
[faca61b8]585{
586 gfx_coord2_t pos;
[8ef48ece]587 bool inside;
[faca61b8]588
589 pos.x = event->hpos;
590 pos.y = event->vpos;
591
[8ef48ece]592 inside = gfx_pix_inside_rect(&pos, &pbutton->rect);
[faca61b8]593
[8ef48ece]594 switch (event->type) {
595 case POS_PRESS:
[a2f173b]596 if (inside) {
[8ef48ece]597 ui_pbutton_press(pbutton);
[a2f173b]598 return ui_claimed;
599 }
[8ef48ece]600 break;
601 case POS_RELEASE:
[a2f173b]602 if (pbutton->held) {
603 ui_pbutton_release(pbutton);
604 return ui_claimed;
605 }
[8ef48ece]606 break;
607 case POS_UPDATE:
608 if (inside && !pbutton->inside) {
609 ui_pbutton_enter(pbutton);
[a2f173b]610 return ui_claimed;
[8ef48ece]611 } else if (!inside && pbutton->inside) {
612 ui_pbutton_leave(pbutton);
613 }
614 break;
[8edec53]615 case POS_DCLICK:
616 break;
[faca61b8]617 }
[a2f173b]618
619 return ui_unclaimed;
[faca61b8]620}
621
[c6f00b40]622/** Destroy push button control.
623 *
624 * @param arg Argument (ui_pbutton_t *)
625 */
626void ui_pbutton_ctl_destroy(void *arg)
627{
628 ui_pbutton_t *pbutton = (ui_pbutton_t *) arg;
629
630 ui_pbutton_destroy(pbutton);
631}
632
[4df6607]633/** Paint push button control.
634 *
635 * @param arg Argument (ui_pbutton_t *)
636 * @return EOK on success or an error code
637 */
638errno_t ui_pbutton_ctl_paint(void *arg)
639{
640 ui_pbutton_t *pbutton = (ui_pbutton_t *) arg;
641
642 return ui_pbutton_paint(pbutton);
643}
644
[8009dc27]645/** Handle push button control position event.
646 *
647 * @param arg Argument (ui_pbutton_t *)
648 * @param pos_event Position event
649 * @return @c ui_claimed iff the event is claimed
650 */
651ui_evclaim_t ui_pbutton_ctl_pos_event(void *arg, pos_event_t *event)
652{
653 ui_pbutton_t *pbutton = (ui_pbutton_t *) arg;
654
655 return ui_pbutton_pos_event(pbutton, event);
656}
657
[f80690a]658/** @}
659 */
Note: See TracBrowser for help on using the repository browser.