Line Graph: fix cairo radial gradient LED sim in portrait orientation
[calf.git] / src / ctl_knob.cpp
blobc51978989469fb8fef4068000c91f1ed9860e6c5
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 <gdk/gdkkeysyms.h>
24 #include <cairo/cairo.h>
25 #if !defined(__APPLE__)
26 #include <malloc.h>
27 #endif
28 #include <math.h>
29 #include <stdint.h>
30 #include <stdlib.h>
31 #include <gdk/gdk.h>
33 ///////////////////////////////////////// knob ///////////////////////////////////////////////
35 static gboolean
36 calf_knob_expose (GtkWidget *widget, GdkEventExpose *event)
38 g_assert(CALF_IS_KNOB(widget));
40 float widths[6] = {0, 2, 4, 4.5, 4.5, 5.5};
41 float margins[6] = {0, 2, 3, 5.5, 5, 7.5};
42 float pins_m[6] = {0, 4, 8, 12, 11, 21};
43 float pins_s[6] = {0, 3, 4, 4, 4, 5};
45 CalfKnob *self = CALF_KNOB(widget);
46 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
47 cairo_t *ctx = gdk_cairo_create(GDK_DRAWABLE(widget->window));
49 int ox = widget->allocation.x, oy = widget->allocation.y;
50 ox += (widget->allocation.width - self->knob_size * 20) / 2;
51 oy += (widget->allocation.height - self->knob_size * 20) / 2;
52 int size = self->knob_size * 20;
53 int rad = size / 2;
54 int from = self->knob_type == 3 ? 270 : 135;
55 int to = self->knob_type == 3 ? -90 : 45;
56 int phase = (adj->value - adj->lower) * 270 / (adj->upper - adj->lower) + 135;
57 int start;
58 int neg_b = 0;
59 int neg_l = 0;
61 cairo_rectangle(ctx, ox, oy, size + size / 2, size + size / 2);
62 cairo_clip(ctx);
64 switch (self->knob_type) {
65 case 0:
66 default:
67 // normal knob
68 start = 135;
69 break;
70 case 1:
71 // centered @ 270°
72 if (adj->value < 0.5) {
73 neg_l = 1;
74 } else {
75 phase = (adj->value - adj->lower) * 270 / (adj->upper - adj->lower) -225;
77 start = -90;
78 break;
79 case 2:
80 // reversed
81 neg_l = 1;
82 start = 45;
83 break;
84 case 3:
85 // 360°
86 neg_l = 1;
87 neg_b = 1;
88 phase = (adj->value - adj->lower) * 360 / (adj->upper - adj->lower) + -90;
89 start = phase;
90 break;
93 if (self->knob_type == 1 && phase == 270) {
94 double pt = (adj->value - adj->lower) * 2.0 / (adj->upper - adj->lower) - 1.0;
95 if (pt < 0)
96 phase = 269;
97 if (pt > 0)
98 phase = 273;
101 static const double dash[] = {2, 1};
102 cairo_set_dash(ctx, dash, 2, 0);
103 cairo_set_line_width(ctx, widths[self->knob_size]);
105 // draw background
106 gdk_draw_pixbuf(GDK_DRAWABLE(widget->window),
107 widget->style->fg_gc[0],
108 CALF_KNOB_CLASS(GTK_OBJECT_GET_CLASS(widget))->knob_image[self->knob_size - 1],
109 0, 0, ox, oy,
110 gdk_pixbuf_get_width(CALF_KNOB_CLASS(GTK_OBJECT_GET_CLASS(widget))->knob_image[self->knob_size - 1]),
111 gdk_pixbuf_get_height(CALF_KNOB_CLASS(GTK_OBJECT_GET_CLASS(widget))->knob_image[self->knob_size - 1]),
112 GDK_RGB_DITHER_NORMAL, 0, 0);
114 // draw unlit
115 if (neg_b)
116 cairo_arc_negative (ctx, ox + rad, oy + rad, rad - margins[self->knob_size], from * (M_PI / 180.), to * (M_PI / 180.));
117 else
118 cairo_arc (ctx, ox + rad, oy + rad, rad - margins[self->knob_size], from * (M_PI / 180.), to * (M_PI / 180.));
119 cairo_set_source_rgb(ctx, 0, 0.1, 0.1);
120 cairo_stroke(ctx);
122 // draw lit
123 float pos1 = (rad - margins[self->knob_size] + widths[self->knob_size] / 2.) / rad;
124 float pos2 = (rad - margins[self->knob_size]) / rad;
125 float pos3 = (rad - margins[self->knob_size] - widths[self->knob_size] / 2.) / rad;
126 cairo_pattern_t *pat = cairo_pattern_create_radial(ox + rad, oy + rad, 0, ox + rad, oy + rad, rad);
127 cairo_pattern_add_color_stop_rgba(pat, pos1, 0, 0.9, 1, 0.75);
128 cairo_pattern_add_color_stop_rgba(pat, pos2, 0, 1, 1, 1.);
129 cairo_pattern_add_color_stop_rgba(pat, pos3, 0, 0.9, 1, 0.75);
130 cairo_set_source(ctx, pat);
131 if (neg_l)
132 cairo_arc_negative (ctx, ox + rad, oy + rad, rad - margins[self->knob_size], start * (M_PI / 180.), phase * (M_PI / 180.));
133 else
134 cairo_arc (ctx, ox + rad, oy + rad, rad - margins[self->knob_size], start * (M_PI / 180.), phase * (M_PI / 180.));
135 cairo_stroke(ctx);
137 // draw light
138 float x = ox + rad + (rad - margins[self->knob_size]) * cos(phase * (M_PI / 180.));
139 float y = oy + rad + (rad - margins[self->knob_size]) * sin(phase * (M_PI / 180.));
140 if (neg_b)
141 cairo_arc_negative (ctx, ox + rad, oy + rad, rad - margins[self->knob_size], from * (M_PI / 180.), to * (M_PI / 180.));
142 else
143 cairo_arc (ctx, ox + rad, oy + rad, rad - margins[self->knob_size], from * (M_PI / 180.), to * (M_PI / 180.));
144 pat = cairo_pattern_create_radial(x, y, widths[self->knob_size] / 2, x, y, widths[self->knob_size]);
145 cairo_pattern_add_color_stop_rgba(pat, 0, 1, 1, 1, 1);
146 cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0.5, 0.8, 0.);
147 cairo_set_source(ctx, pat);
148 cairo_stroke(ctx);
150 // draw shine
151 cairo_rectangle(ctx, ox, oy, size, size);
152 pat = cairo_pattern_create_radial(x, y, 0, x, y, widths[self->knob_size] * 1.5);
153 cairo_pattern_add_color_stop_rgba(pat, 0, 0.8, 1, 1, 0.7);
154 cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0.75, 1, 0.);
155 cairo_set_source(ctx, pat);
156 cairo_fill(ctx);
158 // draw other shine
159 if (neg_l)
160 cairo_arc_negative (ctx, ox + rad, oy + rad, rad - margins[self->knob_size] + widths[self->knob_size], start * (M_PI / 180.), phase * (M_PI / 180.));
161 else
162 cairo_arc (ctx, ox + rad, oy + rad, rad - margins[self->knob_size], start * (M_PI / 180.), phase * (M_PI / 180.));
163 pos1 = (rad - margins[self->knob_size] + widths[self->knob_size]) / rad;
164 pos2 = (rad - margins[self->knob_size]) / rad;
165 pos3 = (rad - margins[self->knob_size] - widths[self->knob_size]) / rad;
166 pat = cairo_pattern_create_radial(ox + rad, oy + rad, 0, ox + rad, oy + rad, rad);
167 cairo_pattern_add_color_stop_rgba(pat, pos1, 0, 1, 1, 0.);
168 cairo_pattern_add_color_stop_rgba(pat, pos2, 0.8, 1, 1, 0.6);
169 cairo_pattern_add_color_stop_rgba(pat, pos3, 0, 1, 1, 0.);
170 cairo_set_source(ctx, pat);
171 cairo_set_line_width(ctx, widths[self->knob_size] * 2.);
172 cairo_stroke(ctx);
175 cairo_pattern_destroy(pat);
177 // draw pin
178 float x1 = ox + rad + (rad - pins_m[self->knob_size]) * cos(phase * (M_PI / 180.));
179 float y1 = oy + rad + (rad - pins_m[self->knob_size]) * sin(phase * (M_PI / 180.));
180 float x2 = ox + rad + (rad - pins_s[self->knob_size] - pins_m[self->knob_size]) * cos(phase * (M_PI / 180.));
181 float y2 = oy + rad + (rad - pins_s[self->knob_size] - pins_m[self->knob_size]) * sin(phase * (M_PI / 180.));
182 cairo_move_to(ctx, x1, y1);
183 cairo_line_to(ctx, x2, y2);
184 cairo_set_dash(ctx, dash, 0, 0);
185 float col = 0;
186 cairo_set_source_rgba(ctx, col, col, col,0.5);
187 cairo_set_line_width(ctx, widths[self->knob_size] / 2.);
188 cairo_stroke(ctx);
189 cairo_destroy(ctx);
191 return TRUE;
194 static void
195 calf_knob_size_request (GtkWidget *widget,
196 GtkRequisition *requisition)
198 g_assert(CALF_IS_KNOB(widget));
200 CalfKnob *self = CALF_KNOB(widget);
202 // width/height is hardwired at 40px now
203 // is now chooseable by "size" value in XML (1-4)
204 requisition->width = 20 * self->knob_size;
205 requisition->height = 20 * self->knob_size;
208 static void
209 calf_knob_incr (GtkWidget *widget, int dir_down)
211 g_assert(CALF_IS_KNOB(widget));
212 CalfKnob *self = CALF_KNOB(widget);
213 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
215 int oldstep = (int)(0.5f + (adj->value - adj->lower) / adj->step_increment);
216 int step;
217 int nsteps = (int)(0.5f + (adj->upper - adj->lower) / adj->step_increment); // less 1 actually
218 if (dir_down)
219 step = oldstep - 1;
220 else
221 step = oldstep + 1;
222 if (self->knob_type == 3 && step >= nsteps)
223 step %= nsteps;
224 if (self->knob_type == 3 && step < 0)
225 step = nsteps - (nsteps - step) % nsteps;
227 // trying to reduce error cumulation here, by counting from lowest or from highest
228 float value = adj->lower + step * double(adj->upper - adj->lower) / nsteps;
229 gtk_range_set_value(GTK_RANGE(widget), value);
230 // printf("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
233 static gboolean
234 calf_knob_key_press (GtkWidget *widget, GdkEventKey *event)
236 g_assert(CALF_IS_KNOB(widget));
237 CalfKnob *self = CALF_KNOB(widget);
238 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
240 switch(event->keyval)
242 case GDK_Home:
243 gtk_range_set_value(GTK_RANGE(widget), adj->lower);
244 return TRUE;
246 case GDK_End:
247 gtk_range_set_value(GTK_RANGE(widget), adj->upper);
248 return TRUE;
250 case GDK_Up:
251 calf_knob_incr(widget, 0);
252 return TRUE;
254 case GDK_Down:
255 calf_knob_incr(widget, 1);
256 return TRUE;
258 case GDK_Shift_L:
259 case GDK_Shift_R:
260 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
261 self->start_y = self->last_y;
262 return TRUE;
265 return FALSE;
268 static gboolean
269 calf_knob_key_release (GtkWidget *widget, GdkEventKey *event)
271 g_assert(CALF_IS_KNOB(widget));
272 CalfKnob *self = CALF_KNOB(widget);
274 if(event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R)
276 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
277 self->start_y = self->last_y;
278 return TRUE;
281 return FALSE;
284 static gboolean
285 calf_knob_button_press (GtkWidget *widget, GdkEventButton *event)
287 g_assert(CALF_IS_KNOB(widget));
288 CalfKnob *self = CALF_KNOB(widget);
290 if (event->type == GDK_2BUTTON_PRESS) {
291 gtk_range_set_value(GTK_RANGE(widget), self->default_value);
294 // CalfKnob *lg = CALF_KNOB(widget);
295 gtk_widget_grab_focus(widget);
296 gtk_grab_add(widget);
297 self->start_x = event->x;
298 self->last_y = self->start_y = event->y;
299 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
301 return TRUE;
304 static gboolean
305 calf_knob_button_release (GtkWidget *widget, GdkEventButton *event)
307 g_assert(CALF_IS_KNOB(widget));
309 if (GTK_WIDGET_HAS_GRAB(widget))
310 gtk_grab_remove(widget);
311 return FALSE;
314 static inline float endless(float value)
316 if (value >= 0)
317 return fmod(value, 1.f);
318 else
319 return fmod(1.f - fmod(1.f - value, 1.f), 1.f);
322 static inline float deadzone(GtkWidget *widget, float value, float incr)
324 // map to dead zone
325 float ov = value;
326 if (ov > 0.5)
327 ov = 0.1 + ov;
328 if (ov < 0.5)
329 ov = ov - 0.1;
331 float nv = ov + incr;
333 if (nv > 0.6)
334 return nv - 0.1;
335 if (nv < 0.4)
336 return nv + 0.1;
337 return 0.5;
340 static gboolean
341 calf_knob_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
343 g_assert(CALF_IS_KNOB(widget));
344 CalfKnob *self = CALF_KNOB(widget);
346 float scale = (event->state & GDK_SHIFT_MASK) ? 2500 : 250;
347 gboolean moved = FALSE;
349 if (GTK_WIDGET_HAS_GRAB(widget))
351 if (self->knob_type == 3)
353 gtk_range_set_value(GTK_RANGE(widget), endless(self->start_value - (event->y - self->start_y) / scale));
355 else
356 if (self->knob_type == 1)
358 gtk_range_set_value(GTK_RANGE(widget), deadzone(GTK_WIDGET(widget), self->start_value, -(event->y - self->start_y) / scale));
360 else
362 gtk_range_set_value(GTK_RANGE(widget), self->start_value - (event->y - self->start_y) / scale);
364 moved = TRUE;
366 self->last_y = event->y;
367 return moved;
370 static gboolean
371 calf_knob_scroll (GtkWidget *widget, GdkEventScroll *event)
373 calf_knob_incr(widget, event->direction);
374 return TRUE;
377 static void
378 calf_knob_class_init (CalfKnobClass *klass)
380 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
381 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
382 widget_class->expose_event = calf_knob_expose;
383 widget_class->size_request = calf_knob_size_request;
384 widget_class->button_press_event = calf_knob_button_press;
385 widget_class->button_release_event = calf_knob_button_release;
386 widget_class->motion_notify_event = calf_knob_pointer_motion;
387 widget_class->key_press_event = calf_knob_key_press;
388 widget_class->key_release_event = calf_knob_key_release;
389 widget_class->scroll_event = calf_knob_scroll;
390 GError *error = NULL;
391 klass->knob_image[0] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob1.png", &error);
392 klass->knob_image[1] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob2.png", &error);
393 klass->knob_image[2] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob3.png", &error);
394 klass->knob_image[3] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob4.png", &error);
395 klass->knob_image[4] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob5.png", &error);
396 g_assert(klass->knob_image != NULL);
399 static void
400 calf_knob_init (CalfKnob *self)
402 GtkWidget *widget = GTK_WIDGET(self);
403 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
404 widget->requisition.width = 40;
405 widget->requisition.height = 40;
408 GtkWidget *
409 calf_knob_new()
411 GtkAdjustment *adj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 1, 0.01, 0.5, 0);
412 return calf_knob_new_with_adjustment(adj);
415 static gboolean calf_knob_value_changed(gpointer obj)
417 GtkWidget *widget = (GtkWidget *)obj;
418 gtk_widget_queue_draw(widget);
419 return FALSE;
422 GtkWidget *calf_knob_new_with_adjustment(GtkAdjustment *_adjustment)
424 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_KNOB, NULL ));
425 if (widget) {
426 gtk_range_set_adjustment(GTK_RANGE(widget), _adjustment);
427 g_signal_connect(GTK_OBJECT(widget), "value-changed", G_CALLBACK(calf_knob_value_changed), widget);
429 return widget;
432 GType
433 calf_knob_get_type (void)
435 static GType type = 0;
436 if (!type) {
438 static const GTypeInfo type_info = {
439 sizeof(CalfKnobClass),
440 NULL, /* base_init */
441 NULL, /* base_finalize */
442 (GClassInitFunc)calf_knob_class_init,
443 NULL, /* class_finalize */
444 NULL, /* class_data */
445 sizeof(CalfKnob),
446 0, /* n_preallocs */
447 (GInstanceInitFunc)calf_knob_init
450 for (int i = 0; ; i++) {
451 char *name = g_strdup_printf("CalfKnob%u%d",
452 ((unsigned int)(intptr_t)calf_knob_class_init) >> 16, i);
453 if (g_type_from_name(name)) {
454 free(name);
455 continue;
457 type = g_type_register_static(GTK_TYPE_RANGE,
458 name,
459 &type_info,
460 (GTypeFlags)0);
461 free(name);
462 break;
465 return type;