first compiling and theoretically correct version of Push2 canvas display.
[ardour.git] / libs / surfaces / push2 / knob.cc
diff --git a/libs/surfaces/push2/knob.cc b/libs/surfaces/push2/knob.cc
new file mode 100644 (file)
index 0000000..71edaf5
--- /dev/null
@@ -0,0 +1,354 @@
+/*
+  Copyright (C) 2016 Paul Davis
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <cmath>
+
+#include <cairomm/context.h>
+#include <cairomm/pattern.h>
+
+#include "ardour/automation_control.h"
+#include "ardour/dB.h"
+
+#include "gtkmm2ext/gui_thread.h"
+#include "gtkmm2ext/rgb_macros.h"
+
+#include "canvas/colors.h"
+
+#include "knob.h"
+#include "push2.h"
+#include "utils.h"
+
+#include "pbd/i18n.h"
+
+using namespace PBD;
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace ArdourCanvas;
+
+Push2Knob::Element Push2Knob::default_elements = Push2Knob::Element (Push2Knob::Arc);
+
+Push2Knob::Push2Knob (Push2& p, Item* parent, Element e, Flags flags)
+       : Item (parent)
+       , p2 (p)
+       , _elements (e)
+       , _flags (flags)
+       , _r (0)
+       , _val (0)
+       , _normal (0)
+       , text (this)
+{
+       Pango::FontDescription fd ("Sans 10");
+       text.set_font_description (fd);
+       text.set_position (Duple (0, -20)); /* changed when radius changes */
+
+       /* typically over-ridden */
+
+       text_color = p2.get_color (Push2::ParameterName);
+       arc_start_color = p2.get_color (Push2::KnobArcStart);
+       arc_end_color = p2.get_color (Push2::KnobArcEnd);
+}
+
+Push2Knob::~Push2Knob ()
+{
+}
+
+void
+Push2Knob::set_text_color (Color c)
+{
+       text.set_color (c);
+}
+
+void
+Push2Knob::set_radius (double r)
+{
+       _r = r;
+       text.set_position (Duple (-_r, -_r - 20));
+       redraw ();
+}
+
+void
+Push2Knob::compute_bounding_box () const
+{
+       if (!_canvas || _r == 0) {
+               _bounding_box = boost::optional<Rect> ();
+               _bounding_box_dirty = false;
+               return;
+       }
+
+       if (_bounding_box_dirty) {
+               Rect r = Rect (0, 0, _r * 2.0, _r * 2.0);
+               _bounding_box = r;
+               _bounding_box_dirty = false;
+       }
+
+       add_child_bounding_boxes ();
+}
+
+void
+Push2Knob::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
+{
+       if (!_controllable) {
+               /* no controllable, nothing to draw */
+               return;
+       }
+
+       const float scale = 2.0 * _r;
+       const float pointer_thickness = 3.0 * (scale/80);  //(if the knob is 80 pixels wide, we want a 3-pix line on it)
+
+       const float start_angle = ((180 - 65) * G_PI) / 180;
+       const float end_angle = ((360 + 65) * G_PI) / 180;
+
+       float zero = 0;
+
+       if (_flags & ArcToZero) {
+               zero = _normal;
+       }
+
+       const float value_angle = start_angle + (_val * (end_angle - start_angle));
+       const float zero_angle = start_angle + (zero * (end_angle - start_angle));
+
+       float value_x = cos (value_angle);
+       float value_y = sin (value_angle);
+
+       context->translate (_position.x, _position.y);  //after this, everything is based on the center of the knob
+       context->begin_new_path ();
+
+       float center_radius = 0.48*scale;
+       float border_width = 0.8;
+
+       const bool arc = (_elements & Arc)==Arc;
+       const bool flat = false;
+
+       if (arc) {
+               center_radius = scale*0.33;
+
+               float inner_progress_radius = scale*0.38;
+               float outer_progress_radius = scale*0.48;
+               float progress_width = (outer_progress_radius-inner_progress_radius);
+               float progress_radius = inner_progress_radius + progress_width/2.0;
+
+               //dark arc background
+               set_source_rgb (context, p2.get_color (Push2::KnobArcBackground));
+               context->set_line_width (progress_width);
+               context->arc (0, 0, progress_radius, start_angle, end_angle);
+               context->stroke ();
+
+
+               double red_start, green_start, blue_start, astart;
+               double red_end, green_end, blue_end, aend;
+
+               ArdourCanvas::color_to_rgba (arc_start_color, red_start, green_start, blue_start, astart);
+               ArdourCanvas::color_to_rgba (arc_end_color, red_end, green_end, blue_end, aend);
+
+               //vary the arc color over the travel of the knob
+               float intensity = fabsf (_val - zero) / std::max(zero, (1.f - zero));
+               const float intensity_inv = 1.0 - intensity;
+               float r = intensity_inv * red_end   + intensity * red_start;
+               float g = intensity_inv * green_end + intensity * green_start;
+               float b = intensity_inv * blue_end  + intensity * blue_start;
+
+               //draw the arc
+               context->set_source_rgb (r,g,b);
+               context->set_line_width (progress_width);
+               if (zero_angle > value_angle) {
+                       context->arc (0, 0, progress_radius, value_angle, zero_angle);
+               } else {
+                       context->arc (0, 0, progress_radius, zero_angle, value_angle);
+               }
+               context->stroke ();
+
+               //shade the arc
+               if (!flat) {
+                       //note we have to offset the pattern from our centerpoint
+                       Cairo::RefPtr<Cairo::LinearGradient> pattern = Cairo::LinearGradient::create (0.0, -_position.y, 0.0, _position.y);
+                       pattern->add_color_stop_rgba (0.0, 1,1,1, 0.15);
+                       pattern->add_color_stop_rgba (0.5, 1,1,1, 0.0);
+                       pattern->add_color_stop_rgba (1.0, 1,1,1, 0.0);
+                       context->set_source (pattern);
+                       context->arc (0, 0, outer_progress_radius-1, 0, 2.0*G_PI);
+                       context->fill ();
+               }
+       }
+
+       if (!flat) {
+               //knob shadow
+               context->save();
+               context->translate(pointer_thickness+1, pointer_thickness+1 );
+               set_source_rgba (context, p2.get_color (Push2::KnobShadow));
+               context->arc (0, 0, center_radius-1, 0, 2.0*G_PI);
+               context->fill ();
+               context->restore();
+
+               //inner circle
+               set_source_rgb (context, p2.get_color (Push2::KnobForeground));
+               context->arc (0, 0, center_radius, 0, 2.0*G_PI);
+               context->fill ();
+
+               //radial gradient as a lightness shade
+               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
+               pattern->add_color_stop_rgba (0.0, 0, 0, 0, 0.2);
+               pattern->add_color_stop_rgba (1.0, 1, 1, 1, 0.3);
+               context->set_source (pattern);
+               context->arc (0, 0, center_radius, 0, 2.0*G_PI);
+               context->fill ();
+
+       }
+
+       //black knob border
+       context->set_line_width (border_width);
+       set_source_rgba (context, p2.get_color (Push2::KnobBorder));
+       context->set_source_rgba (0, 0, 0, 1 );
+       context->arc (0, 0, center_radius, 0, 2.0*G_PI);
+       context->stroke ();
+
+       //line shadow
+       if (!flat) {
+               context->save();
+               context->translate(1, 1 );
+               set_source_rgba (context, p2.get_color (Push2::KnobLineShadow));
+               context->set_line_cap (Cairo::LINE_CAP_ROUND);
+               context->set_line_width (pointer_thickness);
+               context->move_to ((center_radius * value_x), (center_radius * value_y));
+               context->line_to (((center_radius*0.4) * value_x), ((center_radius*0.4) * value_y));
+               context->stroke ();
+               context->restore();
+       }
+
+       //line
+       set_source_rgba (context, p2.get_color (Push2::KnobLine));
+       context->set_line_cap (Cairo::LINE_CAP_ROUND);
+       context->set_line_width (pointer_thickness);
+       context->move_to ((center_radius * value_x), (center_radius * value_y));
+       context->line_to (((center_radius*0.4) * value_x), ((center_radius*0.4) * value_y));
+       context->stroke ();
+
+       /* reset all translations, scaling etc. */
+       context->set_identity_matrix();
+
+       render_children (area, context);
+}
+
+void
+Push2Knob::set_controllable (boost::shared_ptr<AutomationControl> c)
+{
+       watch_connection.disconnect ();  //stop watching the old controllable
+
+       if (!c) {
+               _controllable.reset ();
+               return;
+       }
+
+       _controllable = c;
+       _controllable->Changed.connect (watch_connection, invalidator(*this), boost::bind (&Push2Knob::controllable_changed, this), &p2);
+
+       controllable_changed ();
+}
+
+void
+Push2Knob::set_pan_azimuth_text (double pos)
+{
+       /* We show the position of the center of the image relative to the left & right.
+          This is expressed as a pair of percentage values that ranges from (100,0)
+          (hard left) through (50,50) (hard center) to (0,100) (hard right).
+
+          This is pretty wierd, but its the way audio engineers expect it. Just remember that
+          the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
+       */
+
+       char buf[64];
+       snprintf (buf, sizeof (buf), _("L:%3d R:%3d"), (int) rint (100.0 * (1.0 - pos)), (int) rint (100.0 * pos));
+       text.set (buf);
+}
+
+void
+Push2Knob::set_pan_width_text (double val)
+{
+       char buf[16];
+       snprintf (buf, sizeof (buf), "%d%%", (int) floor (val*100));
+       text.set (buf);
+}
+
+void
+Push2Knob::set_gain_text (double)
+{
+       char buf[16];
+
+       /* need to ignore argument, because it has already been converted into
+          the "interface" (0..1) range.
+       */
+
+       snprintf (buf, sizeof (buf), "%.1f dB", accurate_coefficient_to_dB (_controllable->get_value()));
+       text.set (buf);
+}
+
+void
+Push2Knob::controllable_changed ()
+{
+       if (_controllable) {
+               _normal = _controllable->internal_to_interface (_controllable->normal());
+               _val = _controllable->internal_to_interface (_controllable->get_value());
+
+               switch (_controllable->parameter().type()) {
+               case ARDOUR::PanAzimuthAutomation:
+                       set_pan_azimuth_text (_val);
+                       break;
+
+               case ARDOUR::PanWidthAutomation:
+                       set_pan_width_text (_val);
+                       break;
+
+               case ARDOUR::GainAutomation:
+               case ARDOUR::BusSendLevel:
+                       set_gain_text (_val);
+                       break;
+
+               default:
+                       text.set (std::string());
+               }
+       }
+
+       redraw ();
+}
+
+void
+Push2Knob::add_flag (Flags f)
+{
+       _flags = Flags (_flags | f);
+       redraw ();
+}
+
+void
+Push2Knob::remove_flag (Flags f)
+{
+       _flags = Flags (_flags & ~f);
+       redraw ();
+}
+
+void
+Push2Knob::set_arc_start_color (uint32_t c)
+{
+       arc_start_color = c;
+       redraw ();
+}
+
+void
+Push2Knob::set_arc_end_color (uint32_t c)
+{
+       arc_end_color = c;
+       redraw ();
+}