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