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