1 /* scale/slider 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_SCALE_H_
21 #define _ROB_TK_SCALE_H_
22 
23 /* default values used by robtk_scale_new()
24  * for calling robtk_scale_new_with_size()
25  */
26 #define GSC_LENGTH 250
27 #define GSC_GIRTH 18
28 
29 typedef struct {
30 	RobWidget *rw;
31 
32 	float min;
33 	float max;
34 	float acc;
35 	float cur;
36 	float dfl;
37 
38 	float drag_x, drag_y, drag_c;
39 	bool sensitive;
40 	bool prelight;
41 
42 	bool (*cb) (RobWidget* w, void* handle);
43 	void* handle;
44 
45 	void (*touch_cb) (void*, uint32_t, bool);
46 	void*    touch_hd;
47 	uint32_t touch_id;
48 	bool     touching;
49 
50 	cairo_pattern_t* dpat;
51 	cairo_pattern_t* fpat;
52 	cairo_surface_t* bg;
53 
54 	float w_width, w_height;
55 	bool horiz;
56 
57 	char **mark_txt;
58 	float *mark_val;
59 	int    mark_cnt;
60 	bool mark_expose;
61 	PangoFontDescription *mark_font;
62 	float c_txt[4];
63 	float mark_space;
64 
65 	pthread_mutex_t _mutex;
66 
67 } RobTkScale;
68 
69 
robtk_scale_round_length(RobTkScale * d,float val)70 static int robtk_scale_round_length(RobTkScale * d, float val) {
71 	if (d->horiz) {
72 		return rint((d->w_width - 8) *  (val - d->min) / (d->max - d->min));
73 	} else {
74 		return rint((d->w_height - 8) * (1.0 - (val - d->min) / (d->max - d->min)));
75 	}
76 }
77 
robtk_scale_update_value(RobTkScale * d,float val)78 static void robtk_scale_update_value(RobTkScale * d, float val) {
79 	if (val < d->min) val = d->min;
80 	if (val > d->max) val = d->max;
81 	if (val != d->cur) {
82 		float oldval = d->cur;
83 		d->cur = val;
84 		if (d->cb) d->cb(d->rw, d->handle);
85 		if (robtk_scale_round_length(d, oldval) != robtk_scale_round_length(d, val)) {
86 			val = robtk_scale_round_length(d, val);
87 			oldval = robtk_scale_round_length(d, oldval);
88 			cairo_rectangle_t rect;
89 			if (oldval > val) {
90 
91 				if (d->horiz) {
92 					rect.x = 1 + val;
93 					rect.width = 9 + oldval - val;
94 					rect.y = d->rw->widget_scale * d->mark_space + 5;
95 					rect.height = d->w_height - 9 - d->mark_space * d->rw->widget_scale;
96 				} else {
97 					rect.x = 5;
98 					rect.width = d->w_width - 9 - d->mark_space * d->rw->widget_scale;
99 					rect.y = 1 + val;
100 					rect.height = 9 + oldval - val;
101 				}
102 			} else {
103 				if (d->horiz) {
104 					rect.x = 1 + oldval;
105 					rect.width = 9 + val - oldval;
106 					rect.y = d->rw->widget_scale * d->mark_space + 5;
107 					rect.height = d->w_height - 9 - d->mark_space * d->rw->widget_scale;
108 				} else {
109 					rect.x = 5;
110 					rect.width = d->w_width - 9 - d->mark_space * d->rw->widget_scale;
111 					rect.y = 1 + oldval;
112 					rect.height = 9 + val - oldval;
113 				}
114 			}
115 #ifdef GTK_BACKEND
116 			if (1 /* XXX is visible */) {
117 				queue_tiny_area(d->rw, rect.x, rect.y, rect.width, rect.height);
118 			}
119 #else
120 			if (d->rw->cached_position) {
121 				queue_tiny_area(d->rw, rect.x, rect.y, rect.width, rect.height);
122 			}
123 #endif
124 		}
125 	}
126 }
127 
robtk_scale_mousedown(RobWidget * handle,RobTkBtnEvent * event)128 static RobWidget* robtk_scale_mousedown(RobWidget *handle, RobTkBtnEvent *event) {
129 	RobTkScale * d = (RobTkScale *)GET_HANDLE(handle);
130 	if (!d->sensitive) { return NULL; }
131 	if (d->touch_cb) {
132 		d->touch_cb (d->touch_hd, d->touch_id, true);
133 	}
134 	if (event->state & ROBTK_MOD_SHIFT) {
135 		robtk_scale_update_value(d, d->dfl);
136 	} else {
137 		d->drag_x = event->x;
138 		d->drag_y = event->y;
139 		d->drag_c = d->cur;
140 	}
141 	queue_draw(d->rw);
142 	return handle;
143 }
144 
robtk_scale_mouseup(RobWidget * handle,RobTkBtnEvent * event)145 static RobWidget* robtk_scale_mouseup(RobWidget *handle, RobTkBtnEvent *event) {
146 	RobTkScale * d = (RobTkScale *)GET_HANDLE(handle);
147 	if (!d->sensitive) { return NULL; }
148 	d->drag_x = d->drag_y = -1;
149 	if (d->touch_cb) {
150 		d->touch_cb (d->touch_hd, d->touch_id, false);
151 	}
152 	queue_draw(d->rw);
153 	return NULL;
154 }
155 
robtk_scale_mousemove(RobWidget * handle,RobTkBtnEvent * event)156 static RobWidget* robtk_scale_mousemove(RobWidget *handle, RobTkBtnEvent *event) {
157 	RobTkScale * d = (RobTkScale *)GET_HANDLE(handle);
158 	if (d->drag_x < 0 || d->drag_y < 0) return NULL;
159 
160 	if (!d->sensitive) {
161 		d->drag_x = d->drag_y = -1;
162 		queue_draw(d->rw);
163 		return NULL;
164 	}
165 	float len;
166 	float diff;
167 	if (d->horiz) {
168 		len = d->w_width - 8;
169 		diff = (event->x - d->drag_x) / len;
170 	} else {
171 		len = d->w_height - 8;
172 		diff = (d->drag_y - event->y) / len;
173 	}
174 	diff = rint(diff * (d->max - d->min) / d->acc ) * d->acc;
175 	float val = d->drag_c + diff;
176 
177 	/* snap to mark */
178 	const int snc = robtk_scale_round_length(d, val);
179 	// lock ?!
180 	for (int i = 0; i < d->mark_cnt; ++i) {
181 		int sn = robtk_scale_round_length(d, d->mark_val[i]);
182 		if (abs(sn-snc) < 3) {
183 			val = d->mark_val[i];
184 			break;
185 		}
186 	}
187 
188 	robtk_scale_update_value(d, val);
189 	return handle;
190 }
191 
robtk_scale_enter_notify(RobWidget * handle)192 static void robtk_scale_enter_notify(RobWidget *handle) {
193 	RobTkScale * d = (RobTkScale *)GET_HANDLE(handle);
194 	if (!d->prelight) {
195 		d->prelight = TRUE;
196 		queue_draw(d->rw);
197 	}
198 }
199 
robtk_scale_leave_notify(RobWidget * handle)200 static void robtk_scale_leave_notify(RobWidget *handle) {
201 	RobTkScale * d = (RobTkScale *)GET_HANDLE(handle);
202 	if (d->touch_cb && d->touching) {
203 		d->touch_cb (d->touch_hd, d->touch_id, false);
204 		d->touching = FALSE;
205 	}
206 	if (d->prelight) {
207 		d->prelight = FALSE;
208 		queue_draw(d->rw);
209 	}
210 }
211 
212 static void
robtk_scale_size_request(RobWidget * handle,int * w,int * h)213 robtk_scale_size_request(RobWidget* handle, int *w, int *h) {
214 	RobTkScale * d = (RobTkScale *)GET_HANDLE(handle);
215 	int rw, rh;
216 	if (d->horiz) {
217 		rh = GSC_GIRTH + (d->mark_cnt > 0 ? d->mark_space : 0);
218 		rh *= d->rw->widget_scale;
219 		rw = 250;
220 	} else {
221 		rw = GSC_GIRTH + (d->mark_cnt > 0 ? d->mark_space : 0);
222 		rw *= d->rw->widget_scale;
223 		rh = 250;
224 	}
225 
226 	*w = d->w_width  = rw;
227 	*h = d->w_height = rh;
228 }
229 
230 static void
robtk_scale_size_allocate(RobWidget * handle,int w,int h)231 robtk_scale_size_allocate(RobWidget* handle, int w, int h) {
232 	RobTkScale * d = (RobTkScale *)GET_HANDLE(handle);
233 	if (d->horiz) {
234 		d->w_width = w;
235 		d->w_height = d->rw->widget_scale * (GSC_GIRTH * + (d->mark_cnt > 0 ? d->mark_space : 0));
236 		if (d->w_height > h) {
237 			d->w_height = h;
238 		}
239 	} else {
240 		d->w_height = h;
241 		d->w_width = d->rw->widget_scale * (GSC_GIRTH + (d->mark_cnt > 0 ? d->mark_space : 0));
242 		if (d->w_width > w) {
243 			d->w_width = w;
244 		}
245 	}
246 	robwidget_set_size(handle, d->w_width, d->w_height);
247 	if (d->mark_cnt > 0) { d->mark_expose = TRUE; }
248 }
249 
robtk_scale_scroll(RobWidget * handle,RobTkBtnEvent * ev)250 static RobWidget* robtk_scale_scroll(RobWidget *handle, RobTkBtnEvent *ev) {
251 	RobTkScale * d = (RobTkScale *)GET_HANDLE(handle);
252 	if (!d->sensitive) { return NULL; }
253 
254 	if (!(d->drag_x < 0 || d->drag_y < 0)) {
255 		d->drag_x = d->drag_y = -1;
256 	}
257 
258 	float val = d->cur;
259 	switch (ev->direction) {
260 		case ROBTK_SCROLL_RIGHT:
261 		case ROBTK_SCROLL_UP:
262 			val += d->acc;
263 			break;
264 		case ROBTK_SCROLL_LEFT:
265 		case ROBTK_SCROLL_DOWN:
266 			val -= d->acc;
267 			break;
268 		default:
269 			break;
270 	}
271 
272 	if (d->touch_cb && !d->touching) {
273 		d->touch_cb (d->touch_hd, d->touch_id, true);
274 		d->touching = TRUE;
275 	}
276 
277 	robtk_scale_update_value(d, val);
278 	return NULL;
279 }
280 
create_scale_pattern(RobTkScale * d)281 static void create_scale_pattern(RobTkScale * d) {
282 	if (d->horiz) {
283 		d->dpat = cairo_pattern_create_linear (0.0, 0.0, 0.0, GSC_GIRTH);
284 	} else {
285 		d->dpat = cairo_pattern_create_linear (0.0, 0.0, GSC_GIRTH, 0);
286 	}
287 	cairo_pattern_add_color_stop_rgb (d->dpat, 0.0, .3, .3, .33);
288 	cairo_pattern_add_color_stop_rgb (d->dpat, 0.4, .5, .5, .55);
289 	cairo_pattern_add_color_stop_rgb (d->dpat, 1.0, .2, .2, .22);
290 
291 	if (d->horiz) {
292 		d->fpat = cairo_pattern_create_linear (0.0, 0.0, 0.0, GSC_GIRTH);
293 	} else {
294 		d->fpat = cairo_pattern_create_linear (0.0, 0.0, GSC_GIRTH, 0);
295 	}
296 	cairo_pattern_add_color_stop_rgb (d->fpat, 0.0, .0, .0, .0);
297 	cairo_pattern_add_color_stop_rgb (d->fpat, 0.4,  1,  1,  1);
298 	cairo_pattern_add_color_stop_rgb (d->fpat, 1.0, .1, .1, .1);
299 }
300 
301 #define SXX_W(minus) (d->w_width  + minus - d->rw->widget_scale * ((d->bg && !d->horiz) ? d->mark_space : 0))
302 #define SXX_H(minus) (d->w_height + minus - d->rw->widget_scale * ((d->bg &&  d->horiz) ? d->mark_space : 0))
303 #define SXX_T(plus)  (plus + d->rw->widget_scale * ((d->bg && d->horiz) ? d->mark_space : 0))
304 
robtk_scale_render_metrics(RobTkScale * d)305 static void robtk_scale_render_metrics(RobTkScale* d) {
306 	if (d->bg) {
307 		cairo_surface_destroy(d->bg);
308 	}
309 	d->bg = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, d->w_width, d->w_height);
310 	cairo_t *cr = cairo_create (d->bg);
311 	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
312 	cairo_set_source_rgba (cr, .0, .0, .0, 0);
313 	cairo_rectangle (cr, 0, 0, d->w_width, d->w_height);
314 	cairo_fill (cr);
315 	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
316 	cairo_set_source_rgba (cr, .7, .7, .7, 1.0);
317 	cairo_set_line_width (cr, 1.0);
318 
319 	for (int i = 0; i < d->mark_cnt; ++i) {
320 		float v = 4.0 + robtk_scale_round_length(d, d->mark_val[i]);
321 		if (d->horiz) {
322 			if (d->mark_txt[i]) {
323 				cairo_save (cr);
324 				cairo_scale (cr, d->rw->widget_scale, d->rw->widget_scale);
325 				write_text_full(cr, d->mark_txt[i], d->mark_font, v / d->rw->widget_scale, d->rw->widget_scale, -M_PI/2, 1, d->c_txt);
326 				cairo_restore (cr);
327 			}
328 			cairo_move_to(cr, v+.5, SXX_T(1.5));
329 			cairo_line_to(cr, v+.5, SXX_T(0) + SXX_H(-.5));
330 		} else {
331 			if (d->mark_txt[i]) {
332 				cairo_save (cr);
333 				cairo_scale (cr, d->rw->widget_scale, d->rw->widget_scale);
334 				write_text_full(cr, d->mark_txt[i], d->mark_font, (d->w_width -2) / d->rw->widget_scale, v / d->rw->widget_scale, 0, 1, d->c_txt);
335 				cairo_restore (cr);
336 			}
337 			cairo_move_to(cr, 1.5, v+.5);
338 			cairo_line_to(cr, SXX_W(-.5) , v+.5);
339 		}
340 		cairo_stroke(cr);
341 	}
342 	cairo_destroy(cr);
343 }
344 
robtk_scale_expose_event(RobWidget * handle,cairo_t * cr,cairo_rectangle_t * ev)345 static bool robtk_scale_expose_event (RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) {
346 	RobTkScale * d = (RobTkScale *)GET_HANDLE(handle);
347 	cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height);
348 	cairo_clip (cr);
349 
350 	float c[4];
351 	get_color_from_theme(1, c);
352 	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
353 	cairo_set_source_rgb (cr, c[0], c[1], c[2]);
354 	cairo_rectangle (cr, 0, 0, d->w_width, d->w_height);
355 	cairo_fill(cr);
356 
357 	/* prepare tick mark surfaces */
358 	if (d->mark_cnt > 0 && d->mark_expose) {
359 		pthread_mutex_lock (&d->_mutex);
360 		d->mark_expose = FALSE;
361 		robtk_scale_render_metrics(d);
362 		pthread_mutex_unlock (&d->_mutex);
363 	}
364 
365 	/* tick marks */
366 	if (d->bg) {
367 		if (!d->sensitive) {
368 			//cairo_set_operator (cr, CAIRO_OPERATOR_OVERLAY);
369 			cairo_set_operator (cr, CAIRO_OPERATOR_SOFT_LIGHT);
370 		} else {
371 			cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
372 		}
373 		cairo_set_source_surface(cr, d->bg, 0, 0);
374 		cairo_paint (cr);
375 	}
376 
377 	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
378 
379 	/* solid background */
380 	if (d->sensitive) {
381 		cairo_matrix_t matrix;
382 		cairo_matrix_init_translate(&matrix, 0.0, -SXX_T(0));
383 		cairo_pattern_set_matrix (d->dpat, &matrix);
384 		cairo_set_source(cr, d->dpat);
385 	} else {
386 		cairo_set_source_rgba (cr, .5, .5, .5, 1.0);
387 	}
388 	rounded_rectangle(cr, 4.5, SXX_T(4.5), SXX_W(-8), SXX_H(-8), C_RAD);
389 	cairo_fill_preserve(cr);
390 
391 	if (d->sensitive) {
392 		cairo_set_source_rgba (cr, .0, .0, .0, 1.0);
393 	} else {
394 		cairo_set_source_rgba (cr, .5, .5, .5, 1.0);
395 	}
396 	cairo_set_line_width(cr, .75);
397 	cairo_stroke_preserve (cr);
398 	cairo_clip (cr);
399 
400 
401 	float val = robtk_scale_round_length(d, d->cur);
402 
403 	/* red area, left | top */
404 	if (d->sensitive) {
405 		cairo_set_source_rgba (cr, .5, .0, .0, .3);
406 	} else {
407 		cairo_set_source_rgba (cr, .5, .2, .2, .3);
408 	}
409 	if (d->horiz) {
410 		cairo_rectangle(cr, 3.0, SXX_T(5), val, SXX_H(-9));
411 	} else {
412 		cairo_rectangle(cr, 5, SXX_T(3) + val, SXX_W(-9), SXX_H(-7) - val);
413 	}
414 	cairo_fill(cr);
415 
416 	/* green area, botom | right */
417 	if (d->sensitive) {
418 		cairo_set_source_rgba (cr, .0, .5, .0, .3);
419 	} else {
420 		cairo_set_source_rgba (cr, .2, .5, .2, .3);
421 	}
422 	if (d->horiz) {
423 		cairo_rectangle(cr, 3.0 + val, SXX_T(5), SXX_W(-7) - val, SXX_H(-9));
424 	} else {
425 		cairo_rectangle(cr, 5, SXX_T(3), SXX_W(-9), val);
426 	}
427 	cairo_fill(cr);
428 
429 	/* value ring */
430 	if (d->sensitive) {
431 		cairo_matrix_t matrix;
432 		cairo_matrix_init_translate(&matrix, 0, -SXX_T(0));
433 		cairo_pattern_set_matrix (d->fpat, &matrix);
434 		cairo_set_source(cr, d->fpat);
435 	} else {
436 		cairo_set_source_rgba (cr, .7, .7, .7, .7);
437 	}
438 	if (d->horiz) {
439 		cairo_rectangle(cr, 3.0 + val, SXX_T(5), 3, SXX_H(-9));
440 	} else {
441 		cairo_rectangle(cr, 5, SXX_T(3) + val, SXX_W(-9), 3);
442 	}
443 	cairo_fill(cr);
444 
445 
446 	if (d->sensitive && (d->prelight || d->drag_x > 0)) {
447 		cairo_reset_clip (cr);
448 		cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height);
449 		cairo_clip (cr);
450 		cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, .1);
451 		rounded_rectangle(cr, 4.5, SXX_T(4.5), SXX_W(-8), SXX_H(-8), C_RAD);
452 		cairo_fill_preserve(cr);
453 		cairo_set_line_width(cr, .75);
454 		cairo_set_source_rgba (cr, .0, .0, .0, 1.0);
455 		cairo_stroke(cr);
456 	}
457 	return TRUE;
458 }
459 
460 
461 
462 /******************************************************************************
463  * public functions
464  */
465 
robtk_scale_new_with_size(float min,float max,float step,int girth,int length,bool horiz)466 static RobTkScale * robtk_scale_new_with_size(float min, float max, float step,
467 		int girth, int length, bool horiz) {
468 
469 	assert(max > min);
470 	assert(step > 0);
471 	assert( (max - min) / step >= 1.0);
472 
473 	RobTkScale *d = (RobTkScale *) malloc(sizeof(RobTkScale));
474 
475 	d->mark_font = get_font_from_theme();
476 	get_color_from_theme(0, d->c_txt);
477 
478 	pthread_mutex_init (&d->_mutex, 0);
479 	d->mark_space = 0.0; // XXX longest annotation text
480 
481 	d->horiz = horiz;
482 	if (horiz) {
483 		d->w_width = length; d->w_height = girth;
484 	} else {
485 		d->w_width = girth; d->w_height = length;
486 	}
487 
488 	d->rw = robwidget_new(d);
489 	ROBWIDGET_SETNAME(d->rw, "scale");
490 
491 	d->mark_expose = FALSE;
492 	d->cb = NULL;
493 	d->handle = NULL;
494 	d->touch_cb = NULL;
495 	d->touch_hd = NULL;
496 	d->touch_id = 0;
497 	d->touching = FALSE;
498 	d->min = min;
499 	d->max = max;
500 	d->acc = step;
501 	d->cur = min;
502 	d->dfl = min;
503 	d->sensitive = TRUE;
504 	d->prelight = FALSE;
505 	d->drag_x = d->drag_y = -1;
506 	d->bg  = NULL;
507 	create_scale_pattern(d);
508 
509 	d->mark_cnt = 0;
510 	d->mark_val = NULL;
511 	d->mark_txt = NULL;
512 
513 	robwidget_set_size_request(d->rw, robtk_scale_size_request);
514 	robwidget_set_size_allocate(d->rw, robtk_scale_size_allocate);
515 
516 	robwidget_set_expose_event(d->rw, robtk_scale_expose_event);
517 	robwidget_set_mouseup(d->rw, robtk_scale_mouseup);
518 	robwidget_set_mousedown(d->rw, robtk_scale_mousedown);
519 	robwidget_set_mousemove(d->rw, robtk_scale_mousemove);
520 	robwidget_set_mousescroll(d->rw, robtk_scale_scroll);
521 	robwidget_set_enter_notify(d->rw, robtk_scale_enter_notify);
522 	robwidget_set_leave_notify(d->rw, robtk_scale_leave_notify);
523 
524 	return d;
525 }
526 
robtk_scale_new(float min,float max,float step,bool horiz)527 static RobTkScale * robtk_scale_new(float min, float max, float step, bool horiz) {
528 	return robtk_scale_new_with_size(min, max, step, GSC_GIRTH, GSC_LENGTH, horiz);
529 }
530 
robtk_scale_destroy(RobTkScale * d)531 static void robtk_scale_destroy(RobTkScale *d) {
532 	robwidget_destroy(d->rw);
533 	cairo_pattern_destroy(d->dpat);
534 	cairo_pattern_destroy(d->fpat);
535 	pthread_mutex_destroy(&d->_mutex);
536 	for (int i = 0; i < d->mark_cnt; ++i) {
537 		free(d->mark_txt[i]);
538 	}
539 	free(d->mark_txt);
540 	free(d->mark_val);
541 	pango_font_description_free(d->mark_font);
542 	free(d);
543 }
544 
robtk_scale_widget(RobTkScale * d)545 static RobWidget * robtk_scale_widget(RobTkScale *d) {
546 	return d->rw;
547 }
548 
robtk_scale_set_callback(RobTkScale * d,bool (* cb)(RobWidget * w,void * handle),void * handle)549 static void robtk_scale_set_callback(RobTkScale *d, bool (*cb) (RobWidget* w, void* handle), void* handle) {
550 	d->cb = cb;
551 	d->handle = handle;
552 }
553 
robtk_scale_set_touch(RobTkScale * d,void (* cb)(void *,uint32_t,bool),void * handle,uint32_t id)554 static void robtk_scale_set_touch(RobTkScale *d, void (*cb) (void*, uint32_t, bool), void* handle, uint32_t id) {
555 	d->touch_cb = cb;
556 	d->touch_hd = handle;
557 	d->touch_id = id;
558 }
559 
robtk_scale_set_value(RobTkScale * d,float v)560 static void robtk_scale_set_value(RobTkScale *d, float v) {
561 	v = d->min + rint((v-d->min) / d->acc ) * d->acc;
562 	robtk_scale_update_value(d, v);
563 }
564 
robtk_scale_set_sensitive(RobTkScale * d,bool s)565 static void robtk_scale_set_sensitive(RobTkScale *d, bool s) {
566 	if (d->sensitive != s) {
567 		d->sensitive = s;
568 		queue_draw(d->rw);
569 	}
570 }
571 
robtk_scale_get_value(RobTkScale * d)572 static float robtk_scale_get_value(RobTkScale *d) {
573 	return (d->cur);
574 }
575 
robtk_scale_set_default(RobTkScale * d,float v)576 static void robtk_scale_set_default(RobTkScale *d, float v) {
577 	assert(v >= d->min);
578 	assert(v <= d->max);
579 	d->dfl = v;
580 }
581 
robtk_scale_add_mark(RobTkScale * d,float v,const char * txt)582 static void robtk_scale_add_mark(RobTkScale *d, float v, const char *txt) {
583 	int tw = 0;
584 	int th = 0;
585 	if (txt && strlen(txt)) {
586 		get_text_geometry(txt, d->mark_font, &tw, &th);
587 	}
588 	pthread_mutex_lock (&d->_mutex);
589 	if ((tw + 3) > d->mark_space) {
590 		d->mark_space = tw + 3;
591 	}
592 	d->mark_val = (float *) realloc(d->mark_val, sizeof(float) * (d->mark_cnt+1));
593 	d->mark_txt = (char **) realloc(d->mark_txt, sizeof(char*) * (d->mark_cnt+1));
594 	d->mark_val[d->mark_cnt] = v;
595 	d->mark_txt[d->mark_cnt] = txt ? strdup(txt): NULL;
596 	d->mark_cnt++;
597 	d->mark_expose = TRUE;
598 	pthread_mutex_unlock (&d->_mutex);
599 }
600 #endif
601