Finally - Knobs type 3!
[calf.git] / src / ctl_knob.cpp
blobd685aa14a3f6301a35f1d7bea3a11f2218aece3a
1 /* Calf DSP Library
2 * Knob control.
3 * Copyright (C) 2007-2010 Krzysztof Foltman, Torben Hohn, Markus Schmidt
4 * and others
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General
17 * Public License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 #include "config.h"
22 #include <calf/ctl_knob.h>
23 #include <calf/drawingutils.h>
24 #include <gdk/gdkkeysyms.h>
25 #include <cairo/cairo.h>
26 #if !defined(__APPLE__)
27 #include <malloc.h>
28 #endif
29 #include <math.h>
30 #include <stdint.h>
31 #include <stdlib.h>
32 #include <gdk/gdk.h>
33 #include <algorithm>
34 #include <stdlib.h>
36 #define range01(tick) std::min(1., std::max(0., tick))
38 ///////////////////////////////////////// knob ///////////////////////////////////////////////
40 static float
41 calf_knob_get_color (CalfKnob *self, float deg, float phase, float start, float last, float tickw)
43 double on = 1.0;
44 double off = 0.22;
45 //printf ("get color: phase %.2f deg %.2f\n", phase, deg);
46 if (self->type == 0) {
47 // normal
48 if (deg > phase or phase == start)
49 return off;
50 else return on;
52 if (self->type == 1) {
53 // centered
54 if (deg > 270 and deg <= phase and phase > 270)
55 return on;
56 if (deg <= 270 and deg > phase and phase < 270)
57 return on;
58 if ((deg == start and phase == start)
59 or (deg == 270. and phase > 270.))
60 return on;
61 return off;
63 if (self->type == 2) {
64 // reverse
65 if (deg > phase or phase == start)
66 return on;
67 else return off;
69 if (self->type == 3) {
70 for (unsigned j = 0; j < self->ticks.size(); j++) {
71 float tp = fmod((start + range01(self->ticks[j]) * 360.) - phase + 360, 360);
72 if (tp > 360 - tickw or tp < tickw) {
73 return on;
76 if (deg > phase and deg > last + tickw and last < phase)
77 return on;
80 return off;
86 static gboolean
87 calf_knob_expose (GtkWidget *widget, GdkEventExpose *event)
89 g_assert(CALF_IS_KNOB(widget));
90 CalfKnob *self = CALF_KNOB(widget);
91 CalfKnobClass *cls = CALF_KNOB_CLASS(GTK_OBJECT_GET_CLASS(widget));
92 GdkPixbuf *pixbuf = cls->knob_image[self->size - 1];
93 gint iw = gdk_pixbuf_get_width(pixbuf);
94 gint ih = gdk_pixbuf_get_height(pixbuf);
96 float widths[6] = {0, 2.5, 3.5, 3.5, 4.2, 5.5};
97 float margins[6] = {0, 2.2, 3.5, 3.8, 4.2, 4.5};
98 float pins_m[6] = {0, 6, 10, 10, 11, 13};
99 float pins_s[6] = {0, 3, 4, 4, 4, 4};
101 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
102 cairo_t *ctx = gdk_cairo_create(GDK_DRAWABLE(widget->window));
104 float r, g, b;
105 get_fg_color(widget, NULL, &r, &g, &b);
107 double ox = widget->allocation.x + (widget->allocation.width - iw) / 2;
108 double oy = widget->allocation.y + (widget->allocation.height - ih) / 2;
109 double size = iw;
110 float rad = size / 2;
111 double xc = ox + rad;
112 double yc = oy + rad;
114 unsigned int tick;
115 double phase;
116 double base;
117 double deg;
118 double end;
119 double last;
120 double start;
121 double nend;
122 double zero;
123 double opac;
125 double lwidth = widths[self->size];
126 double lmarg = margins[self->size];
127 double perim = (rad - lmarg) * 2 * M_PI;
128 double tickw = 2. / perim * 360.;
129 double tickw2 = tickw / 2.;
131 const unsigned int debug = 0;
133 cairo_rectangle(ctx, ox, oy, size + size / 2, size + size / 2);
134 cairo_clip(ctx);
136 // draw background
137 gdk_draw_pixbuf(GDK_DRAWABLE(widget->window), widget->style->fg_gc[0], pixbuf,
138 0, 0, ox, oy, iw, ih, GDK_RGB_DITHER_NORMAL, 0, 0);
140 switch (self->type) {
141 default:
142 case 0:
143 // normal knob
144 start = 135.;
145 end = 405.;
146 base = 270.;
147 zero = 135.;
148 case 1:
149 // centered @ 270°
150 start = 135.;
151 end = 405.;
152 base = 270.;
153 zero = 270.;
154 case 2:
155 // reversed
156 start = 135.;
157 end = 405.;
158 base = 270.;
159 zero = 135.;
160 break;
161 case 3:
162 // 360°
163 start = -90.;
164 end = 270.;
165 base = 360.;
166 zero = -90.;
167 break;
169 tick = 0;
170 nend = 0.;
171 deg = last = start;
172 phase = (adj->value - adj->lower) * base / (adj->upper - adj->lower) + start;
174 // draw pin
175 float x1 = ox + rad + (rad - pins_m[self->size]) * cos(phase * (M_PI / 180.));
176 float y1 = oy + rad + (rad - pins_m[self->size]) * sin(phase * (M_PI / 180.));
177 float x2 = ox + rad + (rad - pins_s[self->size] - pins_m[self->size]) * cos(phase * (M_PI / 180.));
178 float y2 = oy + rad + (rad - pins_s[self->size] - pins_m[self->size]) * sin(phase * (M_PI / 180.));
179 cairo_move_to(ctx, x1, y1);
180 cairo_line_to(ctx, x2, y2);
181 cairo_set_source_rgba(ctx, r, g, b, 0.99);
182 cairo_set_line_width(ctx, lwidth / 2.);
183 cairo_stroke(ctx);
185 cairo_set_line_width(ctx, lwidth);
187 // draw ticks and rings
188 unsigned int evsize = 4;
189 double events[evsize] = { start, zero, end, phase };
190 if (self->type == 3)
191 evsize = 3;
192 std::sort(events, events + evsize);
193 if (debug) {
194 printf("start %.2f end %.2f last %.2f deg %.2f tick %d ticks %d phase %.2f base %.2f nend %.2f\n", start, end, last, deg, tick, int(self->ticks.size()), phase, base, nend);
195 for (unsigned int i = 0; i < self->ticks.size(); i++) {
196 printf("tick %d %.2f\n", i, self->ticks[i]);
199 while (deg <= end) {
200 if (debug) printf("tick %d deg %.2f last %.2f end %.2f\n", tick, deg, last, end);
201 if (self->ticks.size() and deg == start + range01(self->ticks[tick]) * base) {
202 // seems we want to draw a tick on this angle.
203 // so we have to fill the void between the last set angle
204 // and the point directly before the tick first.
205 // (draw from last known angle to tickw2 + tickw before actual deg)
206 if (last < deg - tickw - tickw2) {
207 opac = calf_knob_get_color(self, (deg - tickw - tickw2), phase, start, last, tickw + tickw2);
208 cairo_set_source_rgba(ctx, r, g, b, opac);
209 cairo_arc(ctx, xc, yc, rad - lmarg, last * (M_PI / 180.), std::max(last, std::min(nend, (deg - tickw - tickw2))) * (M_PI / 180.));
210 cairo_stroke(ctx);
211 if (debug) printf("fill from %.2f to %.2f @ %.2f\n", last, (deg - tickw - tickw2), opac);
213 // draw the tick itself
214 opac = calf_knob_get_color(self, deg, phase, start, end, tickw + tickw2);
215 cairo_set_source_rgba(ctx, r, g, b, opac);
216 cairo_arc(ctx, xc, yc, rad - lmarg, (deg - tickw2) * (M_PI / 180.), (deg + tickw2) * (M_PI / 180.));
217 cairo_stroke(ctx);
218 if (debug) printf("tick from %.2f to %.2f @ %.2f\n", (deg - tickw2), (deg + tickw2), opac);
219 // set last known angle to deg plus tickw + tickw2
220 last = deg + tickw + tickw2;
221 // and count up tick
222 tick ++;
223 // remember the next ticks void end
224 if (tick < self->ticks.size())
225 nend = range01(self->ticks[tick]) * base + start - tickw - tickw2;
226 else
227 nend = end;
228 } else {
229 // seems we want to fill a gap between the last event and
230 // the actual one, while the actual one isn't a tick (but a
231 // knobs position or a center)
232 if ((last < deg)) {
233 opac = calf_knob_get_color(self, deg, phase, start, last, tickw + tickw2);
234 cairo_set_source_rgba(ctx, r, g, b, opac);
235 cairo_arc(ctx, xc, yc, rad - lmarg, last * (M_PI / 180.), std::min(nend, std::max(last, deg)) * (M_PI / 180.));
236 cairo_stroke(ctx);
237 if (debug) printf("void from %.2f to %.2f @ %.2f\n", last, std::min(nend, std::max(last, deg)), opac);
239 last = deg;
241 if (deg >= end)
242 break;
243 // set deg to next event
244 for (unsigned int i = 0; i < evsize; i++) {
245 if (debug > 1) printf("checking %.2f (start %.2f zero %.2f phase %.2f end %.2f)\n", events[i], start, zero, phase, end);
246 if (events[i] > deg) {
247 deg = events[i];
248 if (debug > 1) printf("taken.\n");
249 break;
252 if (tick < self->ticks.size()) {
253 deg = std::min(deg, start + range01(self->ticks[tick]) * base);
254 if (debug > 1) printf("checking tick %d %.2f\n", tick, start + range01(self->ticks[tick]) * base);
256 //deg = std::max(last, deg);
257 if (debug > 1) printf("finally! deg %.2f\n", deg);
259 if (debug) printf("\n");
260 cairo_destroy(ctx);
261 return TRUE;
264 static void
265 calf_knob_size_request (GtkWidget *widget,
266 GtkRequisition *requisition)
268 g_assert(CALF_IS_KNOB(widget));
270 CalfKnob *self = CALF_KNOB(widget);
272 CalfKnobClass * cls = CALF_KNOB_CLASS(GTK_OBJECT_GET_CLASS(widget));
273 requisition->width = gdk_pixbuf_get_width(cls->knob_image[self->size - 1]);
274 requisition->height = gdk_pixbuf_get_height(cls->knob_image[self->size - 1]);
277 static gboolean calf_knob_enter (GtkWidget *widget, GdkEventCrossing* ev)
279 if (gtk_widget_get_state(widget) == GTK_STATE_NORMAL) {
280 gtk_widget_set_state(widget, GTK_STATE_PRELIGHT);
281 gtk_widget_queue_draw(widget);
283 return TRUE;
286 static gboolean calf_knob_leave (GtkWidget *widget, GdkEventCrossing *ev)
288 if (gtk_widget_get_state(widget) == GTK_STATE_PRELIGHT) {
289 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
290 gtk_widget_queue_draw(widget);
292 return TRUE;
295 static void
296 calf_knob_incr (GtkWidget *widget, int dir_down)
298 g_assert(CALF_IS_KNOB(widget));
299 CalfKnob *self = CALF_KNOB(widget);
300 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
302 int oldstep = (int)(0.5f + (adj->value - adj->lower) / adj->step_increment);
303 int step;
304 int nsteps = (int)(0.5f + (adj->upper - adj->lower) / adj->step_increment); // less 1 actually
305 if (dir_down)
306 step = oldstep - 1;
307 else
308 step = oldstep + 1;
309 if (self->type == 3 && step >= nsteps)
310 step %= nsteps;
311 if (self->type == 3 && step < 0)
312 step = nsteps - (nsteps - step) % nsteps;
314 // trying to reduce error cumulation here, by counting from lowest or from highest
315 float value = adj->lower + step * double(adj->upper - adj->lower) / nsteps;
316 gtk_range_set_value(GTK_RANGE(widget), value);
317 // printf("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
320 static gboolean
321 calf_knob_key_press (GtkWidget *widget, GdkEventKey *event)
323 g_assert(CALF_IS_KNOB(widget));
324 CalfKnob *self = CALF_KNOB(widget);
325 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
326 gtk_widget_set_state(widget, GTK_STATE_ACTIVE);
327 gtk_widget_queue_draw(widget);
328 switch(event->keyval)
330 case GDK_Home:
331 gtk_range_set_value(GTK_RANGE(widget), adj->lower);
332 return TRUE;
334 case GDK_End:
335 gtk_range_set_value(GTK_RANGE(widget), adj->upper);
336 return TRUE;
338 case GDK_Up:
339 calf_knob_incr(widget, 0);
340 return TRUE;
342 case GDK_Down:
343 calf_knob_incr(widget, 1);
344 return TRUE;
346 case GDK_Shift_L:
347 case GDK_Shift_R:
348 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
349 self->start_y = self->last_y;
350 return TRUE;
353 return FALSE;
356 static gboolean
357 calf_knob_key_release (GtkWidget *widget, GdkEventKey *event)
359 g_assert(CALF_IS_KNOB(widget));
360 CalfKnob *self = CALF_KNOB(widget);
362 if(event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R)
364 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
365 self->start_y = self->last_y;
366 return TRUE;
368 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
369 gtk_widget_queue_draw(widget);
370 return FALSE;
373 static gboolean
374 calf_knob_button_press (GtkWidget *widget, GdkEventButton *event)
376 g_assert(CALF_IS_KNOB(widget));
377 CalfKnob *self = CALF_KNOB(widget);
379 if (event->type == GDK_2BUTTON_PRESS) {
380 gtk_range_set_value(GTK_RANGE(widget), self->default_value);
383 // CalfKnob *lg = CALF_KNOB(widget);
384 gtk_widget_grab_focus(widget);
385 gtk_grab_add(widget);
386 self->start_x = event->x;
387 self->last_y = self->start_y = event->y;
388 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
389 gtk_widget_set_state(widget, GTK_STATE_ACTIVE);
390 gtk_widget_queue_draw(widget);
391 return TRUE;
394 static gboolean
395 calf_knob_button_release (GtkWidget *widget, GdkEventButton *event)
397 g_assert(CALF_IS_KNOB(widget));
399 if (GTK_WIDGET_HAS_GRAB(widget))
400 gtk_grab_remove(widget);
401 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
402 gtk_widget_queue_draw(widget);
403 return FALSE;
406 static inline float endless(float value)
408 if (value >= 0)
409 return fmod(value, 1.f);
410 else
411 return fmod(1.f - fmod(1.f - value, 1.f), 1.f);
414 static inline float deadzone(GtkWidget *widget, float value, float incr)
416 // map to dead zone
417 float ov = value;
418 if (ov > 0.5)
419 ov = 0.1 + ov;
420 if (ov < 0.5)
421 ov = ov - 0.1;
423 float nv = ov + incr;
425 if (nv > 0.6)
426 return nv - 0.1;
427 if (nv < 0.4)
428 return nv + 0.1;
429 return 0.5;
432 static gboolean
433 calf_knob_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
435 g_assert(CALF_IS_KNOB(widget));
436 CalfKnob *self = CALF_KNOB(widget);
438 float scale = (event->state & GDK_SHIFT_MASK) ? 2500 : 250;
439 gboolean moved = FALSE;
441 if (GTK_WIDGET_HAS_GRAB(widget))
443 if (self->type == 3)
445 gtk_range_set_value(GTK_RANGE(widget), endless(self->start_value - (event->y - self->start_y) / scale));
447 else
448 if (self->type == 1)
450 gtk_range_set_value(GTK_RANGE(widget), deadzone(GTK_WIDGET(widget), self->start_value, -(event->y - self->start_y) / scale));
452 else
454 gtk_range_set_value(GTK_RANGE(widget), self->start_value - (event->y - self->start_y) / scale);
456 moved = TRUE;
458 self->last_y = event->y;
459 return moved;
462 static gboolean
463 calf_knob_scroll (GtkWidget *widget, GdkEventScroll *event)
465 calf_knob_incr(widget, event->direction);
466 return TRUE;
469 static void
470 calf_knob_class_init (CalfKnobClass *klass)
472 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
473 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
474 widget_class->expose_event = calf_knob_expose;
475 widget_class->size_request = calf_knob_size_request;
476 widget_class->enter_notify_event = calf_knob_enter;
477 widget_class->leave_notify_event = calf_knob_leave;
478 widget_class->button_press_event = calf_knob_button_press;
479 widget_class->button_release_event = calf_knob_button_release;
480 widget_class->motion_notify_event = calf_knob_pointer_motion;
481 widget_class->key_press_event = calf_knob_key_press;
482 widget_class->key_release_event = calf_knob_key_release;
483 widget_class->scroll_event = calf_knob_scroll;
484 GError *error = NULL;
485 klass->knob_image[0] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob1.png", &error);
486 klass->knob_image[1] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob2.png", &error);
487 klass->knob_image[2] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob3.png", &error);
488 klass->knob_image[3] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob4.png", &error);
489 klass->knob_image[4] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob5.png", &error);
490 g_assert(klass->knob_image != NULL);
493 static void
494 calf_knob_init (CalfKnob *self)
496 GtkWidget *widget = GTK_WIDGET(self);
497 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
498 widget->requisition.width = 40;
499 widget->requisition.height = 40;
502 GtkWidget *
503 calf_knob_new()
505 GtkAdjustment *adj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 1, 0.01, 0.5, 0);
506 return calf_knob_new_with_adjustment(adj);
509 static gboolean calf_knob_value_changed(gpointer obj)
511 GtkWidget *widget = (GtkWidget *)obj;
512 gtk_widget_queue_draw(widget);
513 return FALSE;
516 GtkWidget *calf_knob_new_with_adjustment(GtkAdjustment *_adjustment)
518 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_KNOB, NULL ));
519 if (widget) {
520 gtk_range_set_adjustment(GTK_RANGE(widget), _adjustment);
521 g_signal_connect(GTK_OBJECT(widget), "value-changed", G_CALLBACK(calf_knob_value_changed), widget);
523 return widget;
526 GType
527 calf_knob_get_type (void)
529 static GType type = 0;
530 if (!type) {
532 static const GTypeInfo type_info = {
533 sizeof(CalfKnobClass),
534 NULL, /* base_init */
535 NULL, /* base_finalize */
536 (GClassInitFunc)calf_knob_class_init,
537 NULL, /* class_finalize */
538 NULL, /* class_data */
539 sizeof(CalfKnob),
540 0, /* n_preallocs */
541 (GInstanceInitFunc)calf_knob_init
544 for (int i = 0; ; i++) {
545 char *name = g_strdup_printf("CalfKnob%u%d",
546 ((unsigned int)(intptr_t)calf_knob_class_init) >> 16, i);
547 if (g_type_from_name(name)) {
548 free(name);
549 continue;
551 type = g_type_register_static(GTK_TYPE_RANGE,
552 name,
553 &type_info,
554 (GTypeFlags)0);
555 free(name);
556 break;
559 return type;