1 /* radio button widget
2 *
3 * Copyright (C) 2013-2016 Robin Gareus <robin@gareus.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #ifndef _ROB_TK_CBTN_H_
21 #define _ROB_TK_CBTN_H_
22
23 #define GBT_LED_RADIUS (11.0)
24 enum GedLedMode {
25 GBT_LED_RADIO = -2,
26 GBT_LED_LEFT = -1,
27 GBT_LED_OFF = 0,
28 GBT_LED_RIGHT = 1
29 };
30
31 typedef struct {
32 RobWidget* rw;
33
34 bool sensitive;
35 bool prelight;
36 bool enabled;
37
38 enum GedLedMode show_led;
39 bool flat_button;
40 bool radiomode;
41 int temporary_mode;
42
43 bool (*cb) (RobWidget* w, void* handle);
44 void* handle;
45
46 void (*touch_cb) (void*, uint32_t, bool);
47 void* touch_hd;
48 uint32_t touch_id;
49
50 void (*ttip) (RobWidget* rw, bool on, void* handle);
51 void* ttip_handle;
52
53 cairo_pattern_t* btn_enabled;
54 cairo_pattern_t* btn_inactive;
55 cairo_pattern_t* btn_led;
56 cairo_surface_t* sf_txt_normal;
57 cairo_surface_t* sf_txt_enabled;
58 char *txt;
59 float scale;
60
61 float w_width, w_height, l_width, l_height;
62 float c_on[4];
63 float coff[4];
64 float c_ck[4];
65 pthread_mutex_t _mutex;
66 } RobTkCBtn;
67
68 /******************************************************************************
69 * some helpers
70 */
71
robtk_cbtn_update_enabled(RobTkCBtn * d,bool enabled)72 static void robtk_cbtn_update_enabled(RobTkCBtn * d, bool enabled) {
73 if (enabled != d->enabled) {
74 d->enabled = enabled;
75 if (d->cb) d->cb(d->rw, d->handle);
76 queue_draw(d->rw);
77 }
78 }
79
create_cbtn_pattern(RobTkCBtn * d)80 static void create_cbtn_pattern(RobTkCBtn * d) {
81 float c_bg[4]; get_color_from_theme(1, c_bg);
82
83 if (d->btn_inactive) cairo_pattern_destroy(d->btn_inactive);
84 if (d->btn_enabled) cairo_pattern_destroy(d->btn_enabled);
85 if (d->btn_led) cairo_pattern_destroy(d->btn_led);
86
87 d->btn_inactive = cairo_pattern_create_linear (0.0, 0.0, 0.0, d->w_height);
88 cairo_pattern_add_color_stop_rgb (d->btn_inactive, ISBRIGHT(c_bg) ? 0.5 : 0.0, SHADE_RGB(c_bg, 1.95));
89 cairo_pattern_add_color_stop_rgb (d->btn_inactive, ISBRIGHT(c_bg) ? 0.0 : 0.5, SHADE_RGB(c_bg, 0.75));
90
91 d->btn_enabled = cairo_pattern_create_linear (0.0, 0.0, 0.0, d->w_height);
92 if (d->show_led == GBT_LED_OFF) {
93 cairo_pattern_add_color_stop_rgb (d->btn_enabled, ISBRIGHT(d->c_ck) ? 0.5 : 0.0, SHADE_RGB(d->c_ck, 0.5));
94 cairo_pattern_add_color_stop_rgb (d->btn_enabled, ISBRIGHT(d->c_ck) ? 0.0 : 0.5, SHADE_RGB(d->c_ck, 1.0));
95 } else {
96 cairo_pattern_add_color_stop_rgb (d->btn_enabled, ISBRIGHT(c_bg) ? 0.5 : 0.0, SHADE_RGB(c_bg, .95));
97 cairo_pattern_add_color_stop_rgb (d->btn_enabled, ISBRIGHT(c_bg) ? 0.0 : 0.5, SHADE_RGB(c_bg, 2.4));
98 }
99
100 d->btn_led = cairo_pattern_create_linear (0.0, 0.0, 0.0, GBT_LED_RADIUS);
101 cairo_pattern_add_color_stop_rgba (d->btn_led, 0.0, 0.0, 0.0, 0.0, 0.4);
102 cairo_pattern_add_color_stop_rgba (d->btn_led, 1.0, 1.0, 1.0, 1.0, 0.7);
103 }
104
create_cbtn_text_surface(RobTkCBtn * d)105 static void create_cbtn_text_surface (RobTkCBtn* d) {
106 float c_col[4];
107 get_color_from_theme(0, c_col);
108 pthread_mutex_lock (&d->_mutex);
109 PangoFontDescription *font = get_font_from_theme();
110 d->scale = d->rw->widget_scale;
111
112 create_text_surface3 (&d->sf_txt_normal,
113 ceil(d->l_width * d->rw->widget_scale),
114 ceil(d->l_height * d->rw->widget_scale),
115 floor (d->rw->widget_scale * (d->l_width / 2.0)) + 1,
116 floor (d->rw->widget_scale * (d->l_height / 2.0)) + 1,
117 d->txt, font, c_col, d->rw->widget_scale);
118
119 /* sf_txt_enabled, is used with btn_enabled surface, see create_cbtn_pattern() */
120 bool bright_text;
121 if (d->show_led == GBT_LED_OFF) {
122 /*btn_enabled c_ck is shaded by .5 .. 1.0 */
123 bright_text = luminance_rgb (d->c_ck) < .6;
124 } else {
125 /*btn_enabled c_bg is brighted by .95 .. 2.4*/
126 get_color_from_theme(1, c_col);
127 bright_text = luminance_rgb (c_col) < .21;
128 }
129 if (bright_text) {
130 c_col[0] = 1.f;
131 c_col[1] = 1.f;
132 c_col[2] = 1.f;
133 c_col[3] = 1.f;
134 } else {
135 c_col[0] = 0;
136 c_col[1] = 0;
137 c_col[2] = 0;
138 c_col[3] = 1.f;
139 }
140
141 create_text_surface3 (&d->sf_txt_enabled,
142 ceil(d->l_width * d->rw->widget_scale),
143 ceil(d->l_height * d->rw->widget_scale),
144 floor (d->rw->widget_scale * (d->l_width / 2.0)) + 1,
145 floor (d->rw->widget_scale * (d->l_height / 2.0)) + 1,
146 d->txt, font, c_col, d->rw->widget_scale);
147 pango_font_description_free(font);
148 pthread_mutex_unlock (&d->_mutex);
149 }
150
151
152 /******************************************************************************
153 * main drawing callback
154 */
155
robtk_cbtn_expose_event(RobWidget * handle,cairo_t * cr,cairo_rectangle_t * ev)156 static bool robtk_cbtn_expose_event(RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) {
157 RobTkCBtn * d = (RobTkCBtn *)GET_HANDLE(handle);
158
159 if (d->scale != d->rw->widget_scale) {
160 create_cbtn_text_surface(d);
161 }
162 if (pthread_mutex_trylock (&d->_mutex)) {
163 queue_draw(d->rw);
164 return TRUE;
165 }
166
167 cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height);
168 cairo_clip (cr);
169 float led_r, led_g, led_b; // TODO consolidate with c[]
170
171 cairo_scale (cr, d->rw->widget_scale, d->rw->widget_scale);
172
173 float c[4];
174 get_color_from_theme(1, c);
175
176 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
177
178 if (!d->sensitive) {
179 led_r = c[0];
180 led_g = c[1];
181 led_b = c[2];
182 } else if (d->enabled) {
183 if (d->radiomode) {
184 led_r = .3; led_g = .8; led_b = .1;
185 } else {
186 led_r = d->c_on[0]; led_g = d->c_on[1]; led_b = d->c_on[2];
187 }
188 } else {
189 if (d->radiomode) {
190 led_r = .1; led_g = .3; led_b = .1;
191 } else {
192 led_r = d->coff[0]; led_g = d->coff[1]; led_b = d->coff[2];
193 }
194 }
195
196
197 if (!d->flat_button) {
198 if (d->enabled) {
199 cairo_set_source(cr, d->btn_enabled);
200 } else if (!d->sensitive) {
201 cairo_set_source_rgb (cr, c[0], c[1], c[2]);
202 } else {
203 cairo_set_source(cr, d->btn_inactive);
204 }
205
206 rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD);
207 cairo_fill_preserve (cr);
208 if (!d->sensitive && d->enabled) {
209 cairo_set_source_rgba (cr, c[0], c[1], c[2], .6);
210 cairo_fill_preserve (cr);
211 }
212 cairo_set_line_width (cr, .75);
213 cairo_set_source_rgba (cr, .0, .0, .0, 1.0);
214 cairo_stroke(cr);
215 } else {
216 cairo_set_source_rgb (cr, c[0], c[1], c[2]);
217 rounded_rectangle(cr, 2, 2, d->w_width - 3, d->w_height - 3, C_RAD);
218 cairo_fill(cr);
219 }
220
221 const float lspace = d->w_width - d->l_width - (d->show_led ? GBT_LED_RADIUS + 6 : 0);
222 const float lhpad = d->show_led < 0 ? GBT_LED_RADIUS + 6 : 0;
223 const float xalign = rint((lhpad + lspace * d->rw->xalign) * d->scale);
224 const float yalign = rint((d->w_height - d->l_height) * d->rw->yalign * d->scale);
225
226 cairo_save (cr);
227
228 cairo_scale (cr, 1.0 / d->rw->widget_scale, 1.0 / d->rw->widget_scale);
229 if (d->flat_button && !d->sensitive) {
230 cairo_set_operator (cr, CAIRO_OPERATOR_EXCLUSION);
231 cairo_set_source_surface(cr, d->sf_txt_normal, xalign, yalign);
232 } else if (!d->flat_button && d->enabled) {
233 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
234 cairo_set_source_surface(cr, d->sf_txt_enabled, xalign, yalign);
235 } else {
236 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
237 cairo_set_source_surface(cr, d->sf_txt_normal, xalign, yalign);
238 }
239 cairo_paint (cr);
240 cairo_restore (cr);
241
242
243 if (d->show_led) {
244 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
245 cairo_save(cr);
246 if (d->show_led == GBT_LED_LEFT || d->show_led == GBT_LED_RADIO) {
247 cairo_translate(cr, GBT_LED_RADIUS/2 + 7, d->w_height/2.0 + 1);
248 } else {
249 cairo_translate(cr, d->w_width - GBT_LED_RADIUS/2 - 7, d->w_height/2.0 + 1);
250 }
251 cairo_set_source (cr, d->btn_led);
252 cairo_arc (cr, 0, 0, GBT_LED_RADIUS/2, 0, 2 * M_PI);
253 cairo_fill(cr);
254
255 cairo_set_source_rgb (cr, 0, 0, 0);
256 cairo_arc (cr, 0, 0, GBT_LED_RADIUS/2 - 2, 0, 2 * M_PI);
257 cairo_fill(cr);
258 cairo_set_source_rgba (cr, led_r, led_g, led_b, 1.0);
259 cairo_arc (cr, 0, 0, GBT_LED_RADIUS/2 - 3, 0, 2 * M_PI);
260 cairo_fill(cr);
261 cairo_restore(cr);
262 }
263
264
265 if (d->sensitive && d->prelight) {
266 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
267 if (ISBRIGHT(c)) {
268 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, .1);
269 } else {
270 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, .1);
271 }
272 if (d->flat_button) {
273 rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD);
274 cairo_fill(cr);
275 } else {
276 rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD);
277 cairo_fill_preserve(cr);
278 cairo_set_line_width (cr, .75);
279 cairo_set_source_rgba (cr, .0, .0, .0, 1.0);
280 cairo_stroke(cr);
281 }
282 }
283 pthread_mutex_unlock (&d->_mutex);
284 return TRUE;
285 }
286
287
288 /******************************************************************************
289 * UI callbacks
290 */
291
robtk_cbtn_mousedown(RobWidget * handle,RobTkBtnEvent * event)292 static RobWidget* robtk_cbtn_mousedown(RobWidget *handle, RobTkBtnEvent *event) {
293 RobTkCBtn * d = (RobTkCBtn *)GET_HANDLE(handle);
294 if (!d->sensitive) { return NULL; }
295 if (!d->prelight) { return NULL; }
296 if (d->radiomode && d->enabled) { return NULL; }
297 if (d->touch_cb && event->button == 1) {
298 d->touch_cb (d->touch_hd, d->touch_id, true);
299 }
300 if (d->ttip) {
301 d->ttip (d->rw, false, d->ttip_handle);
302 }
303 if ( ((d->temporary_mode & 1) && event->button == 3)
304 || ((d->temporary_mode & 2) && event->state & ROBTK_MOD_SHIFT)
305 || ((d->temporary_mode & 4) && event->state & ROBTK_MOD_CTRL)
306 )
307 {
308 robtk_cbtn_update_enabled(d, ! d->enabled);
309 }
310 return NULL;
311 }
312
robtk_cbtn_mouseup(RobWidget * handle,RobTkBtnEvent * event)313 static RobWidget* robtk_cbtn_mouseup(RobWidget *handle, RobTkBtnEvent *event) {
314 RobTkCBtn * d = (RobTkCBtn *)GET_HANDLE(handle);
315 if (!d->sensitive) { return NULL; }
316 if (d->radiomode && d->enabled) { return NULL; }
317 if (event->button !=1 && !((d->temporary_mode & 1) && event->button == 3)) { return NULL; }
318 if (d->prelight) {
319 robtk_cbtn_update_enabled(d, ! d->enabled);
320 }
321 if (d->touch_cb && event->button == 1) {
322 d->touch_cb (d->touch_hd, d->touch_id, false);
323 }
324 return NULL;
325 }
326
robtk_cbtn_enter_notify(RobWidget * handle)327 static void robtk_cbtn_enter_notify(RobWidget *handle) {
328 RobTkCBtn * d = (RobTkCBtn *)GET_HANDLE(handle);
329 if (!d->prelight) {
330 d->prelight = TRUE;
331 queue_draw(d->rw);
332 }
333 if (d->ttip) {
334 d->ttip (d->rw, true, d->ttip_handle);
335 }
336 }
337
robtk_cbtn_leave_notify(RobWidget * handle)338 static void robtk_cbtn_leave_notify(RobWidget *handle) {
339 RobTkCBtn * d = (RobTkCBtn *)GET_HANDLE(handle);
340 if (d->prelight) {
341 d->prelight = FALSE;
342 queue_draw(d->rw);
343 }
344 if (d->ttip) {
345 d->ttip (d->rw, false, d->ttip_handle);
346 }
347 }
348
349 /******************************************************************************
350 * RobWidget stuff
351 */
352
353 static void
priv_cbtn_size_request(RobWidget * handle,int * w,int * h)354 priv_cbtn_size_request(RobWidget* handle, int *w, int *h) {
355 RobTkCBtn* d = (RobTkCBtn*)GET_HANDLE(handle);
356 *w = d->l_width * d->rw->widget_scale;
357 *h = d->l_height * d->rw->widget_scale;
358 }
359
360 static void
priv_cbtn_size_allocate(RobWidget * handle,int w,int h)361 priv_cbtn_size_allocate(RobWidget* handle, int w, int h) {
362 RobTkCBtn* d = (RobTkCBtn*)GET_HANDLE(handle);
363 bool recreate_patterns = FALSE;
364 if (h != d->w_height * d->rw->widget_scale) recreate_patterns = TRUE;
365 if (w != d->w_width * d->rw->widget_scale) d->scale = 0; // re-layout
366 d->w_width = w / d->rw->widget_scale;
367 d->w_height = h / d->rw->widget_scale;
368 if (recreate_patterns) {
369 d->scale = 0;
370 create_cbtn_pattern(d);
371 }
372 robwidget_set_size(handle, w, h);
373 }
374
375
376 /******************************************************************************
377 * public functions
378 */
379
robtk_cbtn_new(const char * txt,enum GedLedMode led,bool flat)380 static RobTkCBtn * robtk_cbtn_new(const char* txt, enum GedLedMode led, bool flat) {
381 assert(txt);
382 RobTkCBtn *d = (RobTkCBtn *) calloc(1, sizeof(RobTkCBtn));
383
384 d->flat_button = flat;
385 d->show_led = led;
386 d->cb = NULL;
387 d->handle = NULL;
388 d->touch_cb = NULL;
389 d->touch_hd = NULL;
390 d->touch_id = 0;
391 d->ttip = NULL;
392 d->ttip_handle = NULL;
393 d->sensitive = TRUE;
394 d->radiomode = FALSE;
395 d->temporary_mode = 0;
396 d->prelight = FALSE;
397 d->enabled = FALSE;
398 d->txt = strdup(txt);
399 d->scale = 1.0;
400 pthread_mutex_init (&d->_mutex, 0);
401
402 d->c_on[0] = .8; d->c_on[1] = .3; d->c_on[2] = .1; d->c_on[3] = 1.0;
403 d->coff[0] = .3; d->coff[1] = .1; d->coff[2] = .1; d->coff[3] = 1.0;
404 d->c_ck[0] = .2; d->c_ck[1] = .7; d->c_ck[2] = .22; d->c_ck[3] = 1.0;
405
406 if (led == GBT_LED_RADIO) {
407 d->radiomode = TRUE;
408 }
409
410 int ww, wh;
411 PangoFontDescription *fd = get_font_from_theme();
412 get_text_geometry(txt, fd, &ww, &wh);
413 pango_font_description_free(fd);
414
415 assert(d->show_led || ww > 0);
416 d->w_width = ((ww > 0) ? (ww + 14) : 7) + (d->show_led ? GBT_LED_RADIUS + 6 : 0);
417 d->w_height = wh + 8;
418 d->l_width = d->w_width;
419 d->l_height = d->w_height;
420
421 d->rw = robwidget_new(d);
422
423 create_cbtn_text_surface(d);
424
425 robwidget_set_alignment(d->rw, .5, .5);
426 ROBWIDGET_SETNAME(d->rw, "cbtn");
427
428 robwidget_set_size_request(d->rw, priv_cbtn_size_request);
429 robwidget_set_size_allocate(d->rw, priv_cbtn_size_allocate);
430 robwidget_set_expose_event(d->rw, robtk_cbtn_expose_event);
431 robwidget_set_mousedown(d->rw, robtk_cbtn_mousedown);
432 robwidget_set_mouseup(d->rw, robtk_cbtn_mouseup);
433 robwidget_set_enter_notify(d->rw, robtk_cbtn_enter_notify);
434 robwidget_set_leave_notify(d->rw, robtk_cbtn_leave_notify);
435
436 create_cbtn_pattern(d);
437
438 return d;
439 }
440
robtk_cbtn_destroy(RobTkCBtn * d)441 static void robtk_cbtn_destroy(RobTkCBtn *d) {
442 robwidget_destroy(d->rw);
443 cairo_pattern_destroy(d->btn_enabled);
444 cairo_pattern_destroy(d->btn_inactive);
445 cairo_pattern_destroy(d->btn_led);
446 cairo_surface_destroy(d->sf_txt_normal);
447 cairo_surface_destroy(d->sf_txt_enabled);
448 pthread_mutex_destroy(&d->_mutex);
449 free(d->txt);
450 free(d);
451 }
452
robtk_cbtn_set_alignment(RobTkCBtn * d,float x,float y)453 static void robtk_cbtn_set_alignment(RobTkCBtn *d, float x, float y) {
454 robwidget_set_alignment(d->rw, x, y);
455 }
456
robtk_cbtn_widget(RobTkCBtn * d)457 static RobWidget * robtk_cbtn_widget(RobTkCBtn *d) {
458 return d->rw;
459 }
460
robtk_cbtn_set_callback(RobTkCBtn * d,bool (* cb)(RobWidget * w,void * handle),void * handle)461 static void robtk_cbtn_set_callback(RobTkCBtn *d, bool (*cb) (RobWidget* w, void* handle), void* handle) {
462 d->cb = cb;
463 d->handle = handle;
464 }
465
robtk_cbtn_set_touch(RobTkCBtn * d,void (* cb)(void *,uint32_t,bool),void * handle,uint32_t id)466 static void robtk_cbtn_set_touch(RobTkCBtn *d, void (*cb) (void*, uint32_t, bool), void* handle, uint32_t id) {
467 d->touch_cb = cb;
468 d->touch_hd = handle;
469 d->touch_id = id;
470 }
471
robtk_cbtn_annotation_callback(RobTkCBtn * d,void (* cb)(RobWidget * w,bool,void * handle),void * handle)472 static void robtk_cbtn_annotation_callback(RobTkCBtn *d, void (*cb) (RobWidget* w, bool, void* handle), void* handle) {
473 d->ttip = cb;
474 d->ttip_handle = handle;
475 }
476
robtk_cbtn_set_active(RobTkCBtn * d,bool v)477 static void robtk_cbtn_set_active(RobTkCBtn *d, bool v) {
478 robtk_cbtn_update_enabled(d, v);
479 }
480
robtk_cbtn_set_sensitive(RobTkCBtn * d,bool s)481 static void robtk_cbtn_set_sensitive(RobTkCBtn *d, bool s) {
482 if (d->sensitive != s) {
483 d->sensitive = s;
484 queue_draw(d->rw);
485 }
486 }
487
robtk_cbtn_set_text(RobTkCBtn * d,const char * txt)488 static void robtk_cbtn_set_text(RobTkCBtn *d, const char *txt) {
489 free (d->txt);
490 d->txt = strdup (txt);
491 create_cbtn_text_surface (d);
492 queue_draw (d->rw);
493 }
494
robtk_cbtn_set_color_on(RobTkCBtn * d,float r,float g,float b)495 static void robtk_cbtn_set_color_on(RobTkCBtn *d, float r, float g, float b) {
496 d->c_on[0] = r;
497 d->c_on[1] = g;
498 d->c_on[2] = b;
499 }
500
robtk_cbtn_set_color_off(RobTkCBtn * d,float r,float g,float b)501 static void robtk_cbtn_set_color_off(RobTkCBtn *d, float r, float g, float b) {
502 d->coff[0] = r;
503 d->coff[1] = g;
504 d->coff[2] = b;
505 }
506
robtk_cbtn_set_color_checked(RobTkCBtn * d,float r,float g,float b)507 static void robtk_cbtn_set_color_checked(RobTkCBtn *d, float r, float g, float b) {
508 d->c_ck[0] = r;
509 d->c_ck[1] = g;
510 d->c_ck[2] = b;
511 create_cbtn_pattern (d);
512 }
513
robtk_cbtn_set_temporary_mode(RobTkCBtn * d,int i)514 static void robtk_cbtn_set_temporary_mode(RobTkCBtn *d, int i) {
515 if (d->radiomode) return;
516 d->temporary_mode = i;
517 }
518
robtk_cbtn_get_active(RobTkCBtn * d)519 static bool robtk_cbtn_get_active(RobTkCBtn *d) {
520 return (d->enabled);
521 }
522 #endif
523