1 /*
2 * Copyright (C) 2016-2017 Paul Davis <paul@linuxaudiosystems.com>
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 along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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
Push2Knob(Push2 & p,Item * parent,Element e,Flags flags)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
~Push2Knob()73 Push2Knob::~Push2Knob ()
74 {
75 }
76
77 void
set_text_color(Gtkmm2ext::Color c)78 Push2Knob::set_text_color (Gtkmm2ext::Color c)
79 {
80 text->set_color (c);
81 }
82
83 void
set_radius(double r)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
render(Rect const & area,Cairo::RefPtr<Cairo::Context> context) const93 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
compute_bounding_box() const240 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
set_controllable(boost::shared_ptr<AutomationControl> c)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
set_pan_azimuth_text(double pos)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
set_pan_width_text(double val)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
set_gain_text(double)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
controllable_changed()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
add_flag(Flags f)341 Push2Knob::add_flag (Flags f)
342 {
343 _flags = Flags (_flags | f);
344 redraw ();
345 }
346
347 void
remove_flag(Flags f)348 Push2Knob::remove_flag (Flags f)
349 {
350 _flags = Flags (_flags & ~f);
351 redraw ();
352 }
353
354 void
set_arc_start_color(uint32_t c)355 Push2Knob::set_arc_start_color (uint32_t c)
356 {
357 arc_start_color = c;
358 redraw ();
359 }
360
361 void
set_arc_end_color(uint32_t c)362 Push2Knob::set_arc_end_color (uint32_t c)
363 {
364 arc_end_color = c;
365 redraw ();
366 }
367