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