1 /* dial widget with custom display
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19 
20 #ifndef _ROB_TK_CNOB_H_
21 #define _ROB_TK_CNOB_H_
22 
23 typedef struct _RobTkCnob {
24 	RobWidget *rw;
25 
26 	float min;
27 	float max;
28 	float acc;
29 	float cur;
30 	float dfl;
31 	float alt;
32 	float base_mult;
33 	float scroll_mult;
34 	float dead_zone_delta;
35 	int   n_detents;
36 	float *detent;
37 
38 	float drag_x, drag_y, drag_c;
39 	bool dragging;
40 	bool sensitive;
41 	bool prelight;
42 
43 	bool (*cb) (RobWidget* w, void* handle);
44 	void* handle;
45 
46 	void (*draw) (struct _RobTkCnob* d, cairo_t *cr, void* handle);
47 	void* draw_handle;
48 
49 
50 	cairo_pattern_t* dpat;
51 	cairo_surface_t* bg;
52 	float bg_scale;
53 
54 	float w_width, w_height;
55 
56 	float dcol[4][4];
57 } RobTkCnob;
58 
robtk_cnob_expose_event(RobWidget * handle,cairo_t * cr,cairo_rectangle_t * ev)59 static bool robtk_cnob_expose_event (RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) {
60 	RobTkCnob* d = (RobTkCnob *)GET_HANDLE(handle);
61 	cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height);
62 	cairo_clip (cr);
63 	cairo_scale (cr, d->rw->widget_scale, d->rw->widget_scale);
64 
65 	float c[4];
66 	get_color_from_theme(1, c);
67 	cairo_set_source_rgb (cr, c[0], c[1], c[2]);
68 	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
69 	cairo_rectangle (cr, 0, 0, d->w_width, d->w_height);
70 	cairo_fill(cr);
71 	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
72 
73 	cairo_save (cr);
74 	if (d->draw) {
75 		d->draw (d, cr, d->draw_handle);
76 	}
77 
78 	if (d->prelight && d->sensitive) {
79 		cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
80 		cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, .1);
81 		cairo_rectangle (cr, 0, 0, d->w_width, d->w_height);
82 		cairo_fill(cr);
83 	}
84 	cairo_restore (cr);
85 	return TRUE;
86 }
87 
robtk_cnob_update_value(RobTkCnob * d,float val)88 static void robtk_cnob_update_value(RobTkCnob * d, float val) {
89 	if (val < d->min) val = d->min;
90 	if (val > d->max) val = d->max;
91 	val = d->min + rintf((val - d->min) / d->acc) * d->acc;
92 	if (val != d->cur) {
93 		d->cur = val;
94 		if (d->cb) d->cb(d->rw, d->handle);
95 		queue_draw(d->rw);
96 	}
97 }
98 
robtk_cnob_mousedown(RobWidget * handle,RobTkBtnEvent * ev)99 static RobWidget* robtk_cnob_mousedown(RobWidget* handle, RobTkBtnEvent *ev) {
100 	RobTkCnob * d = (RobTkCnob *)GET_HANDLE(handle);
101 	if (!d->sensitive) { return NULL; }
102 	if (ev->state & ROBTK_MOD_SHIFT) {
103 		robtk_cnob_update_value(d, d->dfl);
104 	} else if (ev->button == 3) {
105 		if (d->cur == d->dfl) {
106 			robtk_cnob_update_value(d, d->alt);
107 		} else {
108 			d->alt = d->cur;
109 			robtk_cnob_update_value(d, d->dfl);
110 		}
111 	} else if (ev->button == 1) {
112 		d->dragging = TRUE;
113 		d->drag_x = ev->x;
114 		d->drag_y = ev->y;
115 		d->drag_c = d->cur;
116 	}
117 	queue_draw(d->rw);
118 	return handle;
119 }
120 
robtk_cnob_mouseup(RobWidget * handle,RobTkBtnEvent * ev)121 static RobWidget* robtk_cnob_mouseup(RobWidget* handle, RobTkBtnEvent *ev) {
122 	RobTkCnob * d = (RobTkCnob *)GET_HANDLE(handle);
123 	if (!d->sensitive) {
124 		d->dragging = FALSE;
125 		return NULL;
126 	}
127 	d->dragging = FALSE;
128 	queue_draw(d->rw);
129 	return NULL;
130 }
131 
robtk_cnob_mousemove(RobWidget * handle,RobTkBtnEvent * ev)132 static RobWidget* robtk_cnob_mousemove(RobWidget* handle, RobTkBtnEvent *ev) {
133 	RobTkCnob * d = (RobTkCnob *)GET_HANDLE(handle);
134 	if (!d->dragging) return NULL;
135 
136 	if (!d->sensitive) {
137 		d->dragging = FALSE;
138 		queue_draw(d->rw);
139 		return NULL;
140 	}
141 
142 	const float mult = (ev->state & ROBTK_MOD_CTRL) ? (d->base_mult * 0.1) : d->base_mult;
143 	float diff = ((ev->x - d->drag_x) - (ev->y - d->drag_y));
144 	if (diff == 0) {
145 		return handle;
146 	}
147 
148 	for (int i = 0; i < d->n_detents; ++i) {
149 		const float px_deadzone = 34.f - d->n_detents; // px
150 		if ((d->cur - d->detent[i]) * (d->cur - d->detent[i] + diff * mult) < 0) {
151 			/* detent */
152 			const int tozero = (d->cur - d->detent[i]) * mult;
153 			int remain = diff - tozero;
154 			if (abs (remain) > px_deadzone) {
155 				/* slow down passing the default value */
156 				remain += (remain > 0) ? px_deadzone * -.5 : px_deadzone * .5;
157 				diff = tozero + remain;
158 				d->dead_zone_delta = 0;
159 			} else {
160 				robtk_cnob_update_value(d, d->detent[i]);
161 				d->dead_zone_delta = remain / px_deadzone;
162 				d->drag_x = ev->x;
163 				d->drag_y = ev->y;
164 				goto out;
165 			}
166 		}
167 
168 		if (fabsf (rintf((d->cur - d->detent[i]) / mult) + d->dead_zone_delta) < 1) {
169 			robtk_cnob_update_value(d, d->detent[i]);
170 			d->dead_zone_delta += diff / px_deadzone;
171 			d->drag_x = ev->x;
172 			d->drag_y = ev->y;
173 			goto out;
174 		}
175 	}
176 
177 	diff = rintf(diff * mult * (d->max - d->min) / d->acc ) * d->acc;
178 
179 	if (diff != 0) {
180 		d->dead_zone_delta = 0;
181 	}
182 	robtk_cnob_update_value(d, d->drag_c + diff);
183 
184 out:
185 	if (d->drag_c != d->cur) {
186 		d->drag_x = ev->x;
187 		d->drag_y = ev->y;
188 		d->drag_c = d->cur;
189 	}
190 
191 	return handle;
192 }
193 
robtk_cnob_enter_notify(RobWidget * handle)194 static void robtk_cnob_enter_notify(RobWidget *handle) {
195 	RobTkCnob * d = (RobTkCnob *)GET_HANDLE(handle);
196 	if (!d->prelight) {
197 		d->prelight = TRUE;
198 		queue_draw(d->rw);
199 	}
200 }
201 
robtk_cnob_leave_notify(RobWidget * handle)202 static void robtk_cnob_leave_notify(RobWidget *handle) {
203 	RobTkCnob * d = (RobTkCnob *)GET_HANDLE(handle);
204 	if (d->prelight) {
205 		d->prelight = FALSE;
206 		queue_draw(d->rw);
207 	}
208 }
209 
210 
robtk_cnob_scroll(RobWidget * handle,RobTkBtnEvent * ev)211 static RobWidget* robtk_cnob_scroll(RobWidget* handle, RobTkBtnEvent *ev) {
212 	RobTkCnob * d = (RobTkCnob *)GET_HANDLE(handle);
213 	if (!d->sensitive) { return NULL; }
214 	if (d->dragging) { d->dragging = FALSE; }
215 
216 	const float delta = (ev->state & ROBTK_MOD_CTRL) ? d->acc : d->scroll_mult * d->acc;
217 
218 	float val = d->cur;
219 	switch (ev->direction) {
220 		case ROBTK_SCROLL_RIGHT:
221 		case ROBTK_SCROLL_UP:
222 			val += delta;
223 			break;
224 		case ROBTK_SCROLL_LEFT:
225 		case ROBTK_SCROLL_DOWN:
226 			val -= delta;
227 			break;
228 		default:
229 			break;
230 	}
231 	robtk_cnob_update_value(d, val);
232 	return NULL;
233 }
234 
create_cnob_pattern(RobTkCnob * d,const float c_bg[4])235 static void create_cnob_pattern(RobTkCnob * d, const float c_bg[4]) {
236 	if (d->dpat) {
237 		cairo_pattern_destroy(d->dpat);
238 	}
239 
240 	cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, d->w_height);
241 	d->dpat = pat;
242 }
243 
244 /******************************************************************************
245  * RobWidget stuff
246  */
247 
248 static void
robtk_cnob_size_request(RobWidget * handle,int * w,int * h)249 robtk_cnob_size_request(RobWidget* handle, int *w, int *h) {
250 	RobTkCnob * d = (RobTkCnob *)GET_HANDLE(handle);
251 	*w = d->w_width * d->rw->widget_scale;
252 	*h = d->w_height * d->rw->widget_scale;
253 }
254 
255 /******************************************************************************
256  * public functions
257  */
258 
259 static RobTkCnob*
robtk_cnob_new(float min,float max,float step,int width,int height)260 robtk_cnob_new (float min, float max, float step, int width, int height)
261 {
262 
263 	assert(max > min);
264 	assert(step > 0);
265 	assert( (max - min) / step >= 1.0);
266 
267 	RobTkCnob* d = (RobTkCnob *) calloc (1, sizeof (RobTkCnob));
268 
269 	d->w_width = width;
270 	d->w_height = height;
271 
272 	d->rw = robwidget_new(d);
273 	ROBWIDGET_SETNAME(d->rw, "dial");
274 	robwidget_set_expose_event(d->rw, robtk_cnob_expose_event);
275 	robwidget_set_size_request(d->rw, robtk_cnob_size_request);
276 	robwidget_set_mouseup(d->rw, robtk_cnob_mouseup);
277 	robwidget_set_mousedown(d->rw, robtk_cnob_mousedown);
278 	robwidget_set_mousemove(d->rw, robtk_cnob_mousemove);
279 	robwidget_set_mousescroll(d->rw, robtk_cnob_scroll);
280 	robwidget_set_enter_notify(d->rw, robtk_cnob_enter_notify);
281 	robwidget_set_leave_notify(d->rw, robtk_cnob_leave_notify);
282 
283 	d->min = min;
284 	d->max = max;
285 	d->acc = step;
286 	d->cur = min;
287 	d->dfl = min;
288 	d->alt = min;
289 	d->n_detents = 0;
290 	d->dead_zone_delta = 0;
291 	d->sensitive = TRUE;
292 	d->prelight = FALSE;
293 	d->dragging = FALSE;
294 	d->drag_x = d->drag_y = 0;
295 	d->base_mult = (((d->max - d->min) / d->acc) < 12) ? (d->acc * 12.0 / (d->max - d->min)) : 1.0;
296 	d->base_mult *= 0.004; // 250px
297 	d->scroll_mult = 1.0;
298 	d->bg_scale = 1.0;
299 	return d;
300 }
301 
robtk_cnob_destroy(RobTkCnob * d)302 static void robtk_cnob_destroy(RobTkCnob *d) {
303 	robwidget_destroy(d->rw);
304 	cairo_pattern_destroy(d->dpat);
305 	free(d->detent);
306 	free(d);
307 }
308 
robtk_cnob_set_alignment(RobTkCnob * d,float x,float y)309 static void robtk_cnob_set_alignment(RobTkCnob *d, float x, float y) {
310 	robwidget_set_alignment(d->rw, x, y);
311 }
312 
robtk_cnob_widget(RobTkCnob * d)313 static RobWidget* robtk_cnob_widget(RobTkCnob *d) {
314 	return d->rw;
315 }
316 
robtk_cnob_set_callback(RobTkCnob * d,bool (* cb)(RobWidget * w,void * handle),void * handle)317 static void robtk_cnob_set_callback(RobTkCnob *d, bool (*cb) (RobWidget* w, void* handle), void* handle) {
318 	d->cb = cb;
319 	d->handle = handle;
320 }
321 
robtk_cnob_expose_callback(RobTkCnob * d,void (* cb)(RobTkCnob * d,cairo_t * cr,void * handle),void * handle)322 static void robtk_cnob_expose_callback (RobTkCnob *d, void (*cb) (RobTkCnob* d, cairo_t *cr, void* handle), void* handle) {
323 	d->draw = cb;
324 	d->draw_handle = handle;
325 }
326 
robtk_cnob_set_default(RobTkCnob * d,float v)327 static void robtk_cnob_set_default(RobTkCnob *d, float v) {
328 	v = d->min + rintf((v-d->min) / d->acc ) * d->acc;
329 	assert(v >= d->min);
330 	assert(v <= d->max);
331 	d->dfl = v;
332 	d->alt = v;
333 }
334 
robtk_cnob_set_value(RobTkCnob * d,float v)335 static void robtk_cnob_set_value(RobTkCnob *d, float v) {
336 	robtk_cnob_update_value(d, v);
337 }
338 
robtk_cnob_set_sensitive(RobTkCnob * d,bool s)339 static void robtk_cnob_set_sensitive(RobTkCnob *d, bool s) {
340 	if (d->sensitive != s) {
341 		d->sensitive = s;
342 		queue_draw(d->rw);
343 	}
344 }
345 
robtk_cnob_get_value(RobTkCnob * d)346 static float robtk_cnob_get_value(RobTkCnob *d) {
347 	return (d->cur);
348 }
349 
robtk_cnob_set_scroll_mult(RobTkCnob * d,float v)350 static void robtk_cnob_set_scroll_mult(RobTkCnob *d, float v) {
351 	d->scroll_mult = v;
352 }
353 
robtk_cnob_set_detents(RobTkCnob * d,const int n,const float * p)354 static void robtk_cnob_set_detents(RobTkCnob *d, const int n, const float *p) {
355 	free(d->detent);
356 	assert (n < 15); // XXX
357 	d->n_detents = n;
358 	d->detent = (float*) malloc(n * sizeof(float));
359 	memcpy(d->detent, p, n * sizeof(float));
360 }
361 
robtk_cnob_set_detent_default(RobTkCnob * d,bool v)362 static void robtk_cnob_set_detent_default(RobTkCnob *d, bool v) {
363 	free(d->detent);
364 	d->n_detents = 1;
365 	d->detent = (float*) malloc(sizeof(float));
366 	d->detent[0] = d->dfl;
367 }
368 #endif
369