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

Last change on this file was 3c54869, checked in by Jiri Svoboda <jiri@…>, 2 years ago

Highlight active window in task bar

  • Property mode set to 100644
File size: 15.1 KB
RevLine 
[f80690a]1/*
[3c54869]2 * Copyright (c) 2023 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
[3c54869]190/** Get button light status.
191 *
192 * @param pbutton Button
193 * @return @c true iff light is on
194 */
195bool ui_pbutton_get_light(ui_pbutton_t *pbutton)
196{
197 return pbutton->light;
198}
199
200/** Turn button light on or off.
201 *
202 * @param pbutton Button
203 * @param light @c true iff button should be lit
204 */
205void ui_pbutton_set_light(ui_pbutton_t *pbutton, bool light)
206{
207 pbutton->light = light;
208}
209
[f1f433d]210/** Set push button caption.
211 *
212 * @param pbutton Button
213 * @param caption New caption
214 * @return EOK on success, ENOMEM if out of memory
215 */
216errno_t ui_pbutton_set_caption(ui_pbutton_t *pbutton, const char *caption)
217{
218 char *dcaption;
219
220 dcaption = str_dup(caption);
221 if (dcaption == NULL)
222 return ENOMEM;
223
224 free(pbutton->caption);
225 pbutton->caption = dcaption;
226 return EOK;
227}
228
[c9a7adc]229/** Paint outer button frame.
[47728678]230 *
231 * @param pbutton Push button
232 * @return EOK on success or an error code
233 */
[c9a7adc]234static errno_t ui_pbutton_paint_frame(ui_pbutton_t *pbutton)
[47728678]235{
[c9a7adc]236 gfx_rect_t rect;
237 gfx_coord_t thickness;
[47728678]238 errno_t rc;
239
[c9a7adc]240 thickness = pbutton->isdefault ? 2 : 1;
241
[de9992c]242 rc = gfx_set_color(pbutton->res->gc, pbutton->res->btn_frame_color);
[c9a7adc]243 if (rc != EOK)
244 goto error;
245
246 rect.p0.x = pbutton->rect.p0.x + 1;
247 rect.p0.y = pbutton->rect.p0.y;
248 rect.p1.x = pbutton->rect.p1.x - 1;
249 rect.p1.y = pbutton->rect.p0.y + thickness;
250 rc = gfx_fill_rect(pbutton->res->gc, &rect);
251 if (rc != EOK)
252 goto error;
253
254 rect.p0.x = pbutton->rect.p0.x + 1;
255 rect.p0.y = pbutton->rect.p1.y - thickness;
256 rect.p1.x = pbutton->rect.p1.x - 1;
257 rect.p1.y = pbutton->rect.p1.y;
258 rc = gfx_fill_rect(pbutton->res->gc, &rect);
259 if (rc != EOK)
260 goto error;
261
262 rect.p0.x = pbutton->rect.p0.x;
263 rect.p0.y = pbutton->rect.p0.y + 1;
264 rect.p1.x = pbutton->rect.p0.x + thickness;
265 rect.p1.y = pbutton->rect.p1.y - 1;
266 rc = gfx_fill_rect(pbutton->res->gc, &rect);
267 if (rc != EOK)
268 goto error;
269
270 rect.p0.x = pbutton->rect.p1.x - thickness;
271 rect.p0.y = pbutton->rect.p0.y + 1;
272 rect.p1.x = pbutton->rect.p1.x;
273 rect.p1.y = pbutton->rect.p1.y - 1;
274 rc = gfx_fill_rect(pbutton->res->gc, &rect);
275 if (rc != EOK)
276 goto error;
277
278 return EOK;
279error:
280 return rc;
281}
282
283/** Paint outset button bevel.
284 *
285 * @param pbutton Push button
286 * @return EOK on success or an error code
287 */
288static errno_t ui_pbutton_paint_outset(ui_pbutton_t *pbutton,
289 gfx_rect_t *rect)
290{
[de9992c]291 return ui_paint_bevel(pbutton->res->gc, rect,
292 pbutton->res->btn_highlight_color,
293 pbutton->res->btn_shadow_color, 2, NULL);
[c9a7adc]294}
295
296/** Paint inset button bevel.
297 *
298 * @param pbutton Push button
299 * @return EOK on success or an error code
300 */
301static errno_t ui_pbutton_paint_inset(ui_pbutton_t *pbutton,
302 gfx_rect_t *rect)
303{
[de9992c]304 return ui_paint_bevel(pbutton->res->gc, rect,
305 pbutton->res->btn_shadow_color,
306 pbutton->res->btn_face_color, 2, NULL);
[c9a7adc]307}
308
[cd74fa8]309/** Paint button text shadow.
[c9a7adc]310 *
311 * @param pbutton Push button
312 * @return EOK on success or an error code
313 */
[cd74fa8]314static errno_t ui_pbutton_paint_text_shadow(ui_pbutton_t *pbutton)
315{
316 gfx_rect_t rect;
317 errno_t rc;
318
319 rect.p0.x = pbutton->rect.p0.x + 1;
320 rect.p0.y = pbutton->rect.p0.y + 1;
321 rect.p1.x = pbutton->rect.p1.x;
322 rect.p1.y = pbutton->rect.p1.y;
323
324 rc = gfx_set_color(pbutton->res->gc, pbutton->res->btn_shadow_color);
325 if (rc != EOK)
326 goto error;
327
328 rc = gfx_fill_rect(pbutton->res->gc, &rect);
329 if (rc != EOK)
330 goto error;
331
332 return EOK;
333error:
334 return rc;
335}
336
337/** Paint push button in graphic mode.
338 *
339 * @param pbutton Push button
340 * @return EOK on success or an error code
341 */
342static errno_t ui_pbutton_paint_gfx(ui_pbutton_t *pbutton)
[c9a7adc]343{
344 gfx_coord2_t pos;
345 gfx_text_fmt_t fmt;
346 gfx_rect_t rect;
[68d68e9]347 gfx_rect_t irect;
[c9a7adc]348 gfx_coord_t thickness;
[3c54869]349 gfx_color_t *color;
[8ef48ece]350 bool depressed;
[c9a7adc]351 errno_t rc;
352
353 thickness = pbutton->isdefault ? 2 : 1;
[8ef48ece]354 depressed = pbutton->held && pbutton->inside;
[c9a7adc]355
356 rect.p0.x = pbutton->rect.p0.x + thickness;
357 rect.p0.y = pbutton->rect.p0.y + thickness;
358 rect.p1.x = pbutton->rect.p1.x - thickness;
359 rect.p1.y = pbutton->rect.p1.y - thickness;
360
[3c54869]361 color = pbutton->light ? pbutton->res->btn_face_lit_color :
362 pbutton->res->btn_face_color;
363
364 rc = gfx_set_color(pbutton->res->gc, color);
[c9a7adc]365 if (rc != EOK)
366 goto error;
367
368 rc = gfx_fill_rect(pbutton->res->gc, &rect);
369 if (rc != EOK)
370 goto error;
371
[47728678]372 /* Center of button rectangle */
[c9a7adc]373 pos.x = (rect.p0.x + rect.p1.x) / 2;
374 pos.y = (rect.p0.y + rect.p1.y) / 2;
[47728678]375
[8ef48ece]376 if (depressed) {
[f6df5a3]377 pos.x += ui_pb_press_dx;
378 pos.y += ui_pb_press_dy;
379 }
380
[d68239a1]381 if (pbutton->decor_ops != NULL && pbutton->decor_ops->paint != NULL) {
382 /* Custom decoration */
383 rc = pbutton->decor_ops->paint(pbutton, pbutton->decor_arg,
384 &pos);
385 if (rc != EOK)
386 goto error;
387 } else {
388 /* Text decoration */
[68d68e9]389 ui_paint_get_inset_frame_inside(pbutton->res, &rect, &irect);
[d68239a1]390 gfx_text_fmt_init(&fmt);
391 fmt.font = pbutton->res->font;
392 fmt.color = pbutton->res->btn_text_color;
393 fmt.halign = gfx_halign_center;
394 fmt.valign = gfx_valign_center;
[68d68e9]395 fmt.abbreviate = true;
396 fmt.width = irect.p1.x - irect.p0.x - 2 * ui_pb_pad_x;
[d68239a1]397
398 rc = gfx_puttext(&pos, &fmt, pbutton->caption);
399 if (rc != EOK)
400 goto error;
401 }
[47728678]402
[c9a7adc]403 rc = ui_pbutton_paint_frame(pbutton);
404 if (rc != EOK)
405 goto error;
406
[8ef48ece]407 if (depressed) {
[c9a7adc]408 rc = ui_pbutton_paint_inset(pbutton, &rect);
409 if (rc != EOK)
410 goto error;
411 } else {
412 rc = ui_pbutton_paint_outset(pbutton, &rect);
413 if (rc != EOK)
414 goto error;
415 }
[47728678]416
[2ab8ab3]417 rc = gfx_update(pbutton->res->gc);
418 if (rc != EOK)
419 goto error;
420
[47728678]421 return EOK;
422error:
423 return rc;
424}
425
[cd74fa8]426/** Paint push button in text mode.
427 *
428 * @param pbutton Push button
429 * @return EOK on success or an error code
430 */
431static errno_t ui_pbutton_paint_text(ui_pbutton_t *pbutton)
432{
433 gfx_coord2_t pos;
434 gfx_text_fmt_t fmt;
435 gfx_rect_t rect;
436 bool depressed;
437 errno_t rc;
438
[8b22d44]439 if ((pbutton->flags & ui_pbf_no_text_depress) == 0)
440 depressed = pbutton->held && pbutton->inside;
441 else
442 depressed = false;
[cd74fa8]443
444 rc = gfx_set_color(pbutton->res->gc, pbutton->res->wnd_face_color);
445 if (rc != EOK)
446 goto error;
447
448 rc = gfx_fill_rect(pbutton->res->gc, &pbutton->rect);
449 if (rc != EOK)
450 goto error;
451
452 rect.p0.x = pbutton->rect.p0.x + (depressed ? 1 : 0);
453 rect.p0.y = pbutton->rect.p0.y;
454 rect.p1.x = pbutton->rect.p1.x - 1 + (depressed ? 1 : 0);
455 rect.p1.y = pbutton->rect.p0.y + 1;
456
[bc52b5b]457 rc = gfx_set_color(pbutton->res->gc, pbutton->res->btn_face_color);
[cd74fa8]458 if (rc != EOK)
459 goto error;
460
461 rc = gfx_fill_rect(pbutton->res->gc, &rect);
462 if (rc != EOK)
463 goto error;
464
465 /* Center of button rectangle */
466 pos.x = (rect.p0.x + rect.p1.x) / 2;
467 pos.y = (rect.p0.y + rect.p1.y) / 2;
468
469 gfx_text_fmt_init(&fmt);
[4583015]470 fmt.font = pbutton->res->font;
[cd74fa8]471 fmt.color = pbutton->res->btn_text_color;
472 fmt.halign = gfx_halign_center;
473 fmt.valign = gfx_valign_center;
[68d68e9]474 fmt.abbreviate = true;
475 fmt.width = rect.p1.x - rect.p0.x - 2 * ui_pb_pad_x_text;
[795c6f7]476 if (fmt.width < 1)
477 fmt.width = 1;
[cd74fa8]478
[4583015]479 rc = gfx_puttext(&pos, &fmt, pbutton->caption);
[cd74fa8]480 if (rc != EOK)
481 goto error;
482
483 if (!depressed) {
484 rc = ui_pbutton_paint_text_shadow(pbutton);
485 if (rc != EOK)
486 goto error;
487 }
488
489 rc = gfx_update(pbutton->res->gc);
490 if (rc != EOK)
491 goto error;
492
493 return EOK;
494error:
495 return rc;
496}
497
498/** Paint push button.
499 *
500 * @param pbutton Push button
501 * @return EOK on success or an error code
502 */
503errno_t ui_pbutton_paint(ui_pbutton_t *pbutton)
504{
505 if (pbutton->res->textmode)
506 return ui_pbutton_paint_text(pbutton);
507 else
508 return ui_pbutton_paint_gfx(pbutton);
509}
510
[f6df5a3]511/** Press down button.
512 *
513 * @param pbutton Push button
514 */
515void ui_pbutton_press(ui_pbutton_t *pbutton)
516{
[8ef48ece]517 if (pbutton->held)
518 return;
519
520 pbutton->inside = true;
[f6df5a3]521 pbutton->held = true;
[8ef48ece]522 (void) ui_pbutton_paint(pbutton);
[d4ea1f6]523 ui_pbutton_down(pbutton);
[f6df5a3]524}
525
526/** Release button.
527 *
528 * @param pbutton Push button
529 */
530void ui_pbutton_release(ui_pbutton_t *pbutton)
531{
[8ef48ece]532 if (!pbutton->held)
533 return;
534
[f6df5a3]535 pbutton->held = false;
[174be87]536 ui_pbutton_up(pbutton);
[8ef48ece]537
538 if (pbutton->inside) {
539 (void) ui_pbutton_paint(pbutton);
540 ui_pbutton_clicked(pbutton);
541 }
542}
543
544/** Pointer entered button.
545 *
546 * @param pbutton Push button
547 */
548void ui_pbutton_enter(ui_pbutton_t *pbutton)
549{
550 if (pbutton->inside)
551 return;
552
553 pbutton->inside = true;
554 if (pbutton->held)
555 (void) ui_pbutton_paint(pbutton);
556}
557
558/** Pointer left button.
559 *
560 * @param pbutton Push button
561 */
562void ui_pbutton_leave(ui_pbutton_t *pbutton)
563{
564 if (!pbutton->inside)
565 return;
566
567 pbutton->inside = false;
568 if (pbutton->held)
569 (void) ui_pbutton_paint(pbutton);
570}
571
[d4ea1f6]572/** Send button clicked event.
[8ef48ece]573 *
574 * @param pbutton Push button
575 */
576void ui_pbutton_clicked(ui_pbutton_t *pbutton)
577{
578 if (pbutton->cb != NULL && pbutton->cb->clicked != NULL)
579 pbutton->cb->clicked(pbutton, pbutton->arg);
[f6df5a3]580}
581
[d4ea1f6]582/** Send button down event.
583 *
584 * @param pbutton Push button
585 */
586void ui_pbutton_down(ui_pbutton_t *pbutton)
587{
588 if (pbutton->cb != NULL && pbutton->cb->down != NULL)
589 pbutton->cb->down(pbutton, pbutton->arg);
590}
591
592/** Send button up event.
593 *
594 * @param pbutton Push button
595 */
596void ui_pbutton_up(ui_pbutton_t *pbutton)
597{
598 if (pbutton->cb != NULL && pbutton->cb->up != NULL)
599 pbutton->cb->up(pbutton, pbutton->arg);
600}
601
[faca61b8]602/** Handle push button position event.
603 *
604 * @param pbutton Push button
605 * @param pos_event Position event
[a2f173b]606 * @return @c ui_claimed iff the event is claimed
[faca61b8]607 */
[a2f173b]608ui_evclaim_t ui_pbutton_pos_event(ui_pbutton_t *pbutton, pos_event_t *event)
[faca61b8]609{
610 gfx_coord2_t pos;
[8ef48ece]611 bool inside;
[faca61b8]612
613 pos.x = event->hpos;
614 pos.y = event->vpos;
615
[8ef48ece]616 inside = gfx_pix_inside_rect(&pos, &pbutton->rect);
[faca61b8]617
[8ef48ece]618 switch (event->type) {
619 case POS_PRESS:
[a2f173b]620 if (inside) {
[8ef48ece]621 ui_pbutton_press(pbutton);
[a2f173b]622 return ui_claimed;
623 }
[8ef48ece]624 break;
625 case POS_RELEASE:
[a2f173b]626 if (pbutton->held) {
627 ui_pbutton_release(pbutton);
628 return ui_claimed;
629 }
[8ef48ece]630 break;
631 case POS_UPDATE:
632 if (inside && !pbutton->inside) {
633 ui_pbutton_enter(pbutton);
[a2f173b]634 return ui_claimed;
[8ef48ece]635 } else if (!inside && pbutton->inside) {
636 ui_pbutton_leave(pbutton);
637 }
638 break;
[8edec53]639 case POS_DCLICK:
640 break;
[faca61b8]641 }
[a2f173b]642
643 return ui_unclaimed;
[faca61b8]644}
645
[c6f00b40]646/** Destroy push button control.
647 *
648 * @param arg Argument (ui_pbutton_t *)
649 */
650void ui_pbutton_ctl_destroy(void *arg)
651{
652 ui_pbutton_t *pbutton = (ui_pbutton_t *) arg;
653
654 ui_pbutton_destroy(pbutton);
655}
656
[4df6607]657/** Paint push button control.
658 *
659 * @param arg Argument (ui_pbutton_t *)
660 * @return EOK on success or an error code
661 */
662errno_t ui_pbutton_ctl_paint(void *arg)
663{
664 ui_pbutton_t *pbutton = (ui_pbutton_t *) arg;
665
666 return ui_pbutton_paint(pbutton);
667}
668
[8009dc27]669/** Handle push button control position event.
670 *
671 * @param arg Argument (ui_pbutton_t *)
672 * @param pos_event Position event
673 * @return @c ui_claimed iff the event is claimed
674 */
675ui_evclaim_t ui_pbutton_ctl_pos_event(void *arg, pos_event_t *event)
676{
677 ui_pbutton_t *pbutton = (ui_pbutton_t *) arg;
678
679 return ui_pbutton_pos_event(pbutton, event);
680}
681
[f80690a]682/** @}
683 */
Note: See TracBrowser for help on using the repository browser.