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

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

Scrollbar needs custom button decorations

Push button now allows setting a 'custom decoration' which means
instead of painting the button text a callback function is invoked
to paint the decoration.

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