diff options
Diffstat (limited to 'libs/surfaces/push2/knob.cc')
-rw-r--r-- | libs/surfaces/push2/knob.cc | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/libs/surfaces/push2/knob.cc b/libs/surfaces/push2/knob.cc new file mode 100644 index 0000000000..71edaf5b5c --- /dev/null +++ b/libs/surfaces/push2/knob.cc @@ -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 (); +} |