Update classkeys to match new total LuaSignal count (windows only)
[ardour.git] / libs / surfaces / push2 / knob.cc
1 /*
2   Copyright (C) 2016 Paul Davis
3
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation; either version 2 of the License, or
7   (at your option) any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12   GNU General Public License for more details.
13
14   You should have received a copy of the GNU General Public License
15   along with this program; if not, write to the Free Software
16   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include <cmath>
20
21 #include <cairomm/context.h>
22 #include <cairomm/pattern.h>
23
24 #include "ardour/automation_control.h"
25 #include "ardour/dB.h"
26 #include "ardour/utils.h"
27
28 #include "gtkmm2ext/gui_thread.h"
29 #include "gtkmm2ext/rgb_macros.h"
30
31 #include "gtkmm2ext/colors.h"
32 #include "canvas/text.h"
33
34 #include "knob.h"
35 #include "push2.h"
36 #include "utils.h"
37
38 #include "pbd/i18n.h"
39
40 #ifdef __APPLE__
41 #define Rect ArdourCanvas::Rect
42 #endif
43
44 using namespace PBD;
45 using namespace ARDOUR;
46 using namespace ArdourSurface;
47 using namespace ArdourCanvas;
48
49 Push2Knob::Element Push2Knob::default_elements = Push2Knob::Element (Push2Knob::Arc);
50
51 Push2Knob::Push2Knob (Push2& p, Item* parent, Element e, Flags flags)
52         : Container (parent)
53         , p2 (p)
54         , _elements (e)
55         , _flags (flags)
56         , _r (0)
57         , _val (0)
58         , _normal (0)
59 {
60         Pango::FontDescription fd ("Sans 10");
61
62         text = new Text (this);
63         text->set_font_description (fd);
64         text->set_position (Duple (0, -20)); /* changed when radius changes */
65
66         /* typically over-ridden */
67
68         text_color = p2.get_color (Push2::ParameterName);
69         arc_start_color = p2.get_color (Push2::KnobArcStart);
70         arc_end_color = p2.get_color (Push2::KnobArcEnd);
71 }
72
73 Push2Knob::~Push2Knob ()
74 {
75 }
76
77 void
78 Push2Knob::set_text_color (Gtkmm2ext::Color c)
79 {
80         text->set_color (c);
81 }
82
83 void
84 Push2Knob::set_radius (double r)
85 {
86         _r = r;
87         text->set_position (Duple (-_r, -_r - 20));
88         _bounding_box_dirty = true;
89         redraw ();
90 }
91
92 void
93 Push2Knob::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
94 {
95         if (!_controllable) {
96                 /* no controllable, nothing to draw */
97                 return;
98         }
99
100         const float scale = 2.0 * _r;
101         const float pointer_thickness = 3.0 * (scale/80);  //(if the knob is 80 pixels wide, we want a 3-pix line on it)
102
103         const float start_angle = ((180 - 65) * G_PI) / 180;
104         const float end_angle = ((360 + 65) * G_PI) / 180;
105
106         float zero = 0;
107
108         if (_flags & ArcToZero) {
109                 zero = _normal;
110         }
111
112         const float value_angle = start_angle + (_val * (end_angle - start_angle));
113         const float zero_angle = start_angle + (zero * (end_angle - start_angle));
114
115         float value_x = cos (value_angle);
116         float value_y = sin (value_angle);
117
118         /* translate so that all coordinates are based on the center of the
119          * knob (which is also its position()
120          */
121         Duple origin = item_to_window (Duple (0, 0));
122         context->translate (origin.x, origin.y);
123         context->begin_new_path ();
124
125         float center_radius = 0.48*scale;
126         float border_width = 0.8;
127
128         const bool arc = (_elements & Arc)==Arc;
129         const bool flat = false;
130
131         if (arc) {
132                 center_radius = scale*0.33;
133
134                 float inner_progress_radius = scale*0.38;
135                 float outer_progress_radius = scale*0.48;
136                 float progress_width = (outer_progress_radius-inner_progress_radius);
137                 float progress_radius = inner_progress_radius + progress_width/2.0;
138
139                 //dark arc background
140                 set_source_rgb (context, p2.get_color (Push2::KnobArcBackground));
141                 context->set_line_width (progress_width);
142                 context->arc (0, 0, progress_radius, start_angle, end_angle);
143                 context->stroke ();
144
145                 double red_start, green_start, blue_start, astart;
146                 double red_end, green_end, blue_end, aend;
147
148                 Gtkmm2ext::color_to_rgba (arc_start_color, red_start, green_start, blue_start, astart);
149                 Gtkmm2ext::color_to_rgba (arc_end_color, red_end, green_end, blue_end, aend);
150
151                 //vary the arc color over the travel of the knob
152                 float intensity = fabsf (_val - zero) / std::max(zero, (1.f - zero));
153                 const float intensity_inv = 1.0 - intensity;
154                 float r = intensity_inv * red_end   + intensity * red_start;
155                 float g = intensity_inv * green_end + intensity * green_start;
156                 float b = intensity_inv * blue_end  + intensity * blue_start;
157
158                 //draw the arc
159                 context->set_source_rgb (r,g,b);
160                 context->set_line_width (progress_width);
161                 if (zero_angle > value_angle) {
162                         context->arc (0, 0, progress_radius, value_angle, zero_angle);
163                 } else {
164                         context->arc (0, 0, progress_radius, zero_angle, value_angle);
165                 }
166                 context->stroke ();
167
168                 //shade the arc
169                 if (!flat) {
170                         //note we have to offset the pattern from our centerpoint
171                         Cairo::RefPtr<Cairo::LinearGradient> pattern = Cairo::LinearGradient::create (0.0, -_position.y, 0.0, _position.y);
172                         pattern->add_color_stop_rgba (0.0, 1,1,1, 0.15);
173                         pattern->add_color_stop_rgba (0.5, 1,1,1, 0.0);
174                         pattern->add_color_stop_rgba (1.0, 1,1,1, 0.0);
175                         context->set_source (pattern);
176                         context->arc (0, 0, outer_progress_radius-1, 0, 2.0*G_PI);
177                         context->fill ();
178                 }
179         }
180
181         if (!flat) {
182                 //knob shadow
183                 context->save();
184                 context->translate(pointer_thickness+1, pointer_thickness+1 );
185                 set_source_rgba (context, p2.get_color (Push2::KnobShadow));
186                 context->arc (0, 0, center_radius-1, 0, 2.0*G_PI);
187                 context->fill ();
188                 context->restore();
189
190                 //inner circle
191                 set_source_rgb (context, p2.get_color (Push2::KnobForeground));
192                 context->arc (0, 0, center_radius, 0, 2.0*G_PI);
193                 context->fill ();
194
195                 //radial gradient as a lightness shade
196                 Cairo::RefPtr<Cairo::RadialGradient> pattern = Cairo::RadialGradient::create (-center_radius, -center_radius, 1, -center_radius, -center_radius, center_radius*2.5  );  //note we have to offset the gradient from our centerpoint
197                 pattern->add_color_stop_rgba (0.0, 0, 0, 0, 0.2);
198                 pattern->add_color_stop_rgba (1.0, 1, 1, 1, 0.3);
199                 context->set_source (pattern);
200                 context->arc (0, 0, center_radius, 0, 2.0*G_PI);
201                 context->fill ();
202
203         }
204
205         //black knob border
206         context->set_line_width (border_width);
207         set_source_rgba (context, p2.get_color (Push2::KnobBorder));
208         context->set_source_rgba (0, 0, 0, 1 );
209         context->arc (0, 0, center_radius, 0, 2.0*G_PI);
210         context->stroke ();
211
212         //line shadow
213         if (!flat) {
214                 context->save();
215                 context->translate(1, 1 );
216                 set_source_rgba (context, p2.get_color (Push2::KnobLineShadow));
217                 context->set_line_cap (Cairo::LINE_CAP_ROUND);
218                 context->set_line_width (pointer_thickness);
219                 context->move_to ((center_radius * value_x), (center_radius * value_y));
220                 context->line_to (((center_radius*0.4) * value_x), ((center_radius*0.4) * value_y));
221                 context->stroke ();
222                 context->restore();
223         }
224
225         //line
226         set_source_rgba (context, p2.get_color (Push2::KnobLine));
227         context->set_line_cap (Cairo::LINE_CAP_ROUND);
228         context->set_line_width (pointer_thickness);
229         context->move_to ((center_radius * value_x), (center_radius * value_y));
230         context->line_to (((center_radius*0.4) * value_x), ((center_radius*0.4) * value_y));
231         context->stroke ();
232
233         /* reset all translations, scaling etc. */
234         context->set_identity_matrix();
235
236         render_children (area, context);
237 }
238
239  void
240 Push2Knob::compute_bounding_box () const
241 {
242         if (!_canvas || _r == 0) {
243                 _bounding_box = Rect ();
244                 _bounding_box_dirty = false;
245                 return;
246         }
247
248         if (_bounding_box_dirty) {
249                 Rect r = Rect (_position.x - _r, _position.y - _r, _position.x + _r, _position.y + _r);
250                 _bounding_box = r;
251                 _bounding_box_dirty = false;
252         }
253
254         add_child_bounding_boxes ();
255 }
256
257 void
258 Push2Knob::set_controllable (boost::shared_ptr<AutomationControl> c)
259 {
260         watch_connection.disconnect ();  //stop watching the old controllable
261
262         if (!c) {
263                 _controllable.reset ();
264                 return;
265         }
266
267         _controllable = c;
268         _controllable->Changed.connect (watch_connection, invalidator(*this), boost::bind (&Push2Knob::controllable_changed, this), &p2);
269
270         controllable_changed ();
271 }
272
273 void
274 Push2Knob::set_pan_azimuth_text (double pos)
275 {
276         /* We show the position of the center of the image relative to the left & right.
277            This is expressed as a pair of percentage values that ranges from (100,0)
278            (hard left) through (50,50) (hard center) to (0,100) (hard right).
279
280            This is pretty wierd, but its the way audio engineers expect it. Just remember that
281            the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
282         */
283
284         char buf[64];
285         snprintf (buf, sizeof (buf), _("L:%3d R:%3d"), (int) rint (100.0 * (1.0 - pos)), (int) rint (100.0 * pos));
286         text->set (buf);
287 }
288
289 void
290 Push2Knob::set_pan_width_text (double val)
291 {
292         char buf[16];
293         snprintf (buf, sizeof (buf), "%d%%", (int) floor (val*100));
294         text->set (buf);
295 }
296
297 void
298 Push2Knob::set_gain_text (double)
299 {
300         char buf[16];
301
302         /* need to ignore argument, because it has already been converted into
303            the "interface" (0..1) range.
304         */
305
306         snprintf (buf, sizeof (buf), "%.1f dB", accurate_coefficient_to_dB (_controllable->get_value()));
307         text->set (buf);
308 }
309
310 void
311 Push2Knob::controllable_changed ()
312 {
313         if (_controllable) {
314                 _normal = _controllable->internal_to_interface (_controllable->normal());
315                 _val = _controllable->internal_to_interface (_controllable->get_value());
316
317                 switch (_controllable->parameter().type()) {
318                 case ARDOUR::PanAzimuthAutomation:
319                         set_pan_azimuth_text (_val);
320                         break;
321
322                 case ARDOUR::PanWidthAutomation:
323                         set_pan_width_text (_val);
324                         break;
325
326                 case ARDOUR::GainAutomation:
327                 case ARDOUR::BusSendLevel:
328                 case ARDOUR::TrimAutomation:
329                         set_gain_text (_val);
330                         break;
331
332                 default:
333                         text->set (std::string());
334                 }
335         }
336
337         redraw ();
338 }
339
340 void
341 Push2Knob::add_flag (Flags f)
342 {
343         _flags = Flags (_flags | f);
344         redraw ();
345 }
346
347 void
348 Push2Knob::remove_flag (Flags f)
349 {
350         _flags = Flags (_flags & ~f);
351         redraw ();
352 }
353
354 void
355 Push2Knob::set_arc_start_color (uint32_t c)
356 {
357         arc_start_color = c;
358         redraw ();
359 }
360
361 void
362 Push2Knob::set_arc_end_color (uint32_t c)
363 {
364         arc_end_color = c;
365         redraw ();
366 }