diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2016-09-16 08:39:28 -0500 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2016-09-27 14:59:31 -0500 |
commit | 7c9f3acc60fb29a562d28d1f43a9e22298545bf1 (patch) | |
tree | 7418a4922ff7670d6ffc08dce39432afd1e5669c /libs/surfaces | |
parent | 2810e5619a1926a286c6192143ada4973066efd8 (diff) |
first compiling and theoretically correct version of Push2 canvas display.
Not tested with device at this point
Diffstat (limited to 'libs/surfaces')
-rw-r--r-- | libs/surfaces/push2/canvas.cc | 190 | ||||
-rw-r--r-- | libs/surfaces/push2/canvas.h | 95 | ||||
-rw-r--r-- | libs/surfaces/push2/knob.cc | 354 | ||||
-rw-r--r-- | libs/surfaces/push2/knob.h | 90 | ||||
-rw-r--r-- | libs/surfaces/push2/layout.cc | 27 | ||||
-rw-r--r-- | libs/surfaces/push2/layout.h | 15 | ||||
-rw-r--r-- | libs/surfaces/push2/menu.cc | 110 | ||||
-rw-r--r-- | libs/surfaces/push2/menu.h | 29 | ||||
-rw-r--r-- | libs/surfaces/push2/mix.cc | 218 | ||||
-rw-r--r-- | libs/surfaces/push2/mix.h | 21 | ||||
-rw-r--r-- | libs/surfaces/push2/push2.cc | 204 | ||||
-rw-r--r-- | libs/surfaces/push2/push2.h | 39 | ||||
-rw-r--r-- | libs/surfaces/push2/scale.cc | 28 | ||||
-rw-r--r-- | libs/surfaces/push2/scale.h | 6 | ||||
-rw-r--r-- | libs/surfaces/push2/splash.cc | 98 | ||||
-rw-r--r-- | libs/surfaces/push2/splash.h | 47 | ||||
-rw-r--r-- | libs/surfaces/push2/track_mix.cc | 111 | ||||
-rw-r--r-- | libs/surfaces/push2/track_mix.h | 15 | ||||
-rw-r--r-- | libs/surfaces/push2/wscript | 2 |
19 files changed, 1198 insertions, 501 deletions
diff --git a/libs/surfaces/push2/canvas.cc b/libs/surfaces/push2/canvas.cc new file mode 100644 index 0000000000..e178e0d8b5 --- /dev/null +++ b/libs/surfaces/push2/canvas.cc @@ -0,0 +1,190 @@ +/* + 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 <cairomm/region.h> +#include <cairomm/surface.h> +#include <cairomm/context.h> + +#include "canvas.h" +#include "layout.h" +#include "push2.h" + +using namespace ArdourCanvas; +using namespace ArdourSurface; + +const int Push2Canvas::pixels_per_row = 1024; + +Push2Canvas::Push2Canvas (Push2& pr, int c, int r) + : p2 (pr) + , _cols (c) + , _rows (r) + , frame_buffer (Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _cols, _rows)) +{ + context = Cairo::Context::create (frame_buffer); + expose_region = Cairo::Region::create (); + + device_frame_buffer = new uint16_t[pixel_area()]; + memset (device_frame_buffer, 0, sizeof (uint16_t) * pixel_area()); + + frame_header[0] = 0xef; + frame_header[1] = 0xcd; + frame_header[2] = 0xab; + frame_header[3] = 0x89; + + memset (&frame_header[4], 0, 12); +} + +Push2Canvas::~Push2Canvas () +{ + delete [] device_frame_buffer; + device_frame_buffer = 0; +} + +bool +Push2Canvas::vblank () +{ + /* re-render dirty areas, if any */ + + if (expose ()) { + /* something rendered, update device_frame_buffer */ + blit_to_device_frame_buffer (); + } + + int transferred = 0; + const int timeout_msecs = 1000; + int err; + + /* transfer to device */ + + if ((err = libusb_bulk_transfer (p2.usb_handle(), 0x01, frame_header, sizeof (frame_header), &transferred, timeout_msecs))) { + return false; + } + + if ((err = libusb_bulk_transfer (p2.usb_handle(), 0x01, (uint8_t*) device_frame_buffer, 2 * pixel_area (), &transferred, timeout_msecs))) { + return false; + } + + return true; +} + +void +Push2Canvas::request_redraw () +{ + request_redraw (Rect (0, 0, _cols, _rows)); +} + +void +Push2Canvas::request_redraw (Rect const & r) +{ + Cairo::RectangleInt cr; + + cr.x = r.x1; + cr.y = r.y1; + cr.width = r.width(); + cr.width = r.height(); + + expose_region->do_union (cr); + + /* next vblank will redraw */ +} + +bool +Push2Canvas::expose () +{ + if (expose_region->empty()) { + return false; /* nothing drawn */ + } + + /* set up clipping */ + + const int nrects = expose_region->get_num_rectangles (); + + for (int n = 0; n < nrects; ++n) { + Cairo::RectangleInt r = expose_region->get_rectangle (n); + context->rectangle (r.x, r.y, r.width, r.height); + } + + context->clip (); + + Push2Layout* layout = p2.current_layout(); + + if (layout) { + Cairo::RectangleInt r = expose_region->get_extents(); + layout->render (Rect (r.x, r.y, r.x + r.width, r.y + r.height), context); + } + + context->reset_clip (); + + return true; +} + +/** render host-side frame buffer (a Cairo ImageSurface) to the current + * device-side frame buffer. The device frame buffer will be pushed to the + * device on the next call to vblank() + */ + +int +Push2Canvas::blit_to_device_frame_buffer () +{ + /* ensure that all drawing has been done before we fetch pixel data */ + + frame_buffer->flush (); + + const int stride = 3840; /* bytes per row for Cairo::FORMAT_ARGB32 */ + const uint8_t* data = frame_buffer->get_data (); + + /* fill frame buffer (320kB) */ + + uint16_t* fb = (uint16_t*) device_frame_buffer; + + for (int row = 0; row < _rows; ++row) { + + const uint8_t* dp = data + row * stride; + + for (int col = 0; col < _cols; ++col) { + + /* fetch r, g, b (range 0..255). Ignore alpha */ + + const int r = (*((const uint32_t*)dp) >> 16) & 0xff; + const int g = (*((const uint32_t*)dp) >> 8) & 0xff; + const int b = *((const uint32_t*)dp) & 0xff; + + /* convert to 5 bits, 6 bits, 5 bits, respectively */ + /* generate 16 bit BGB565 value */ + + *fb++ = (r >> 3) | ((g & 0xfc) << 3) | ((b & 0xf8) << 8); + + /* the push2 docs state that we should xor the pixel + * data. Doing so doesn't work correctly, and not doing + * so seems to work fine (colors roughly match intended + * values). + */ + + dp += 4; + } + + /* skip 128 bytes to next line. This is filler, used to avoid line borders occuring in the middle of 512 + byte USB buffers + */ + + fb += 64; /* 128 bytes = 64 int16_t */ + } + + return 0; +} diff --git a/libs/surfaces/push2/canvas.h b/libs/surfaces/push2/canvas.h new file mode 100644 index 0000000000..853b93f778 --- /dev/null +++ b/libs/surfaces/push2/canvas.h @@ -0,0 +1,95 @@ +/* + 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. + +*/ + +#ifndef __ardour_push2_canvas_h__ +#define __ardour_push2_canvas_h__ + +#include <cairomm/refptr.h> +#include <glibmm/threads.h> + +#include "canvas/canvas.h" + +namespace Cairo { + class ImageSurface; + class Context; + class Region; +} + +namespace ArdourSurface { + +class Push2; + +/* A canvas which renders to the Push2 display */ + +class Push2Canvas : public ArdourCanvas::Canvas +{ + public: + Push2Canvas (Push2& p2, int cols, int rows); + ~Push2Canvas(); + + void request_redraw (); + void request_redraw (ArdourCanvas::Rect const &); + bool vblank (); + + void splash (); + + Cairo::RefPtr<Cairo::Context> image_context() { return context; } + + int rows() const { return _rows; } + int cols() const { return _cols; } + + static double inter_button_spacing() { return 120.0; } + + ArdourCanvas::Coord width() const { return cols(); } + ArdourCanvas::Coord height() const { return rows(); } + void request_size (ArdourCanvas::Duple); + ArdourCanvas::Rect visible_area () const; + + /* API that does nothing since we have no input events */ + void ungrab () {} + void grab (ArdourCanvas::Item*) {} + void focus (ArdourCanvas::Item*) {} + void unfocus (ArdourCanvas::Item*) {} + void re_enter() {} + void pick_current_item (int) {} + void pick_current_item (ArdourCanvas::Duple const &, int) {} + bool get_mouse_position (ArdourCanvas::Duple&) const { return false; } + + private: + Push2& p2; + int _cols; + int _rows; + + static const int pixels_per_row; + int pixel_area () const { return _rows * pixels_per_row; } + + uint8_t frame_header[16]; + uint16_t* device_frame_buffer; + + Cairo::RefPtr<Cairo::ImageSurface> frame_buffer; + Cairo::RefPtr<Cairo::Context> context; + Cairo::RefPtr<Cairo::Region> expose_region; + + bool expose (); + int blit_to_device_frame_buffer (); +}; + +} /* namespace ArdourSurface */ + +#endif /* __ardour_push2_canvas_h__ */ 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 (); +} diff --git a/libs/surfaces/push2/knob.h b/libs/surfaces/push2/knob.h new file mode 100644 index 0000000000..54b0236155 --- /dev/null +++ b/libs/surfaces/push2/knob.h @@ -0,0 +1,90 @@ +#ifndef __ardour_push2_knob_h__ +#define __ardour_push2_knob_h__ + +#include <boost/shared_ptr.hpp> +#include <sigc++/trackable.h> + +#include <cairomm/refptr.h> + +#include "pbd/signals.h" + +#include "canvas/item.h" +#include "canvas/text.h" + +namespace ARDOUR { + class AutomationControl; +} + +namespace Cairo { + class Context; + class Region; +} + +namespace ArdourSurface { + +class Push2; + +class Push2Knob : public sigc::trackable, public ArdourCanvas::Item +{ +public: + enum Element { + Arc = 0x1, + Bevel = 0x2, + unused2 = 0x4, + unused3 = 0x8, + unused4 = 0x10, + unused5 = 0x20, + }; + + enum Flags { + NoFlags = 0, + Detent = 0x1, + ArcToZero = 0x2, + }; + + Push2Knob (Push2& p, ArdourCanvas::Item*, Element e = default_elements, Flags flags = NoFlags); + virtual ~Push2Knob (); + + static Element default_elements; + + void add_flag (Flags); + void remove_flag (Flags); + + void set_controllable (boost::shared_ptr<ARDOUR::AutomationControl> c); + boost::shared_ptr<ARDOUR::AutomationControl> controllable() const { return _controllable; } + + void set_text_color (ArdourCanvas::Color); + void set_arc_start_color (ArdourCanvas::Color); + void set_arc_end_color (ArdourCanvas::Color); + void set_position (double x, double y); + void set_radius (double r); + + void render (ArdourCanvas::Rect const &, Cairo::RefPtr<Cairo::Context>) const; + void compute_bounding_box() const; + + protected: + void controllable_changed (); + PBD::ScopedConnection watch_connection; + boost::shared_ptr<ARDOUR::AutomationControl> _controllable; + + private: + Push2& p2; + Element _elements; + Flags _flags; + double _r; + float _val; // current value [0..1] + float _normal; // default value, arc + + ArdourCanvas::Color text_color; + ArdourCanvas::Color arc_start_color; + ArdourCanvas::Color arc_end_color; + ArdourCanvas::Text text; + + void set_pan_azimuth_text (double); + void set_pan_width_text (double); + void set_gain_text (double); +}; + +} // namespace + +#endif /* __ardour_push2_knob_h__ */ diff --git a/libs/surfaces/push2/layout.cc b/libs/surfaces/push2/layout.cc index f675e3ddbb..be5ff6fdaf 100644 --- a/libs/surfaces/push2/layout.cc +++ b/libs/surfaces/push2/layout.cc @@ -16,14 +16,17 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include "canvas.h" #include "layout.h" #include "push2.h" using namespace ARDOUR; using namespace ArdourSurface; +using namespace ArdourCanvas; Push2Layout::Push2Layout (Push2& p, Session& s) - : p2 (p) + : Container (p.canvas()) + , p2 (p) , session (s) { } @@ -32,8 +35,24 @@ Push2Layout::~Push2Layout () { } -bool -Push2Layout::mapped () const +void +Push2Layout::compute_bounding_box () const { - return p2.current_layout() == this; + /* all layouts occupy at least the full screen, even if their combined + * child boxes do not. + */ + _bounding_box = Rect (0, 0, display_width(), display_height()); + _bounding_box_dirty = false; +} + +int +Push2Layout::display_height() const +{ + return p2.canvas()->rows(); +} + +int +Push2Layout::display_width() const +{ + return p2.canvas()->cols(); } diff --git a/libs/surfaces/push2/layout.h b/libs/surfaces/push2/layout.h index 30e52da5a8..b4f3703f9a 100644 --- a/libs/surfaces/push2/layout.h +++ b/libs/surfaces/push2/layout.h @@ -25,6 +25,12 @@ #include <cairomm/refptr.h> +#include "canvas/container.h" + +namespace Cairo { + class Region; +} + namespace ARDOUR { class Session; } @@ -37,17 +43,16 @@ namespace ArdourSurface { class Push2; -class Push2Layout : public sigc::trackable +class Push2Layout : public sigc::trackable, public ArdourCanvas::Container { public: Push2Layout (Push2& p, ARDOUR::Session& s); virtual ~Push2Layout (); - bool mapped() const; + int display_width () const; + int display_height () const; - virtual bool redraw (Cairo::RefPtr<Cairo::Context>, bool force) const = 0; - virtual void on_show () {} - virtual void on_hide () {} + void compute_bounding_box () const; virtual void button_upper (uint32_t n) {} virtual void button_lower (uint32_t n) {} diff --git a/libs/surfaces/push2/menu.cc b/libs/surfaces/push2/menu.cc index cdb5d60e91..da1c98ac04 100644 --- a/libs/surfaces/push2/menu.cc +++ b/libs/surfaces/push2/menu.cc @@ -18,38 +18,59 @@ #include <cairomm/context.h> #include <cairomm/surface.h> +#include <cairomm/region.h> #include <pangomm/layout.h> -#include "push2.h" +#include "canvas/text.h" +#include "canvas/rectangle.h" +#include "canvas/colors.h" + +#include "canvas.h" #include "gui.h" +#include "push2.h" using namespace ARDOUR; using namespace std; using namespace PBD; using namespace Glib; using namespace ArdourSurface; +using namespace ArdourCanvas; #include "pbd/i18n.h" #include "menu.h" -Push2Menu::Push2Menu (Cairo::RefPtr<Cairo::Context> context) - : _dirty (true) +Push2Menu::Push2Menu (Item* parent) + : Container (parent) + , baseline (-1) { Pango::FontDescription fd2 ("Sans 10"); - { - Glib::RefPtr<Pango::Layout> throwaway = Pango::Layout::create (context); + if (baseline < 0) { + Push2Canvas* p2c = dynamic_cast<Push2Canvas*> (canvas()); + Glib::RefPtr<Pango::Layout> throwaway = Pango::Layout::create (p2c->image_context()); throwaway->set_font_description (fd2); throwaway->set_text (X_("Hg")); /* ascender + descender) */ int h, w; throwaway->get_pixel_size (w, h); baseline = h; - nrows = Push2::rows / baseline; + // nrows = Push2::rows / baseline; } + for (int n = 0; n < 8; ++n) { - columns[n].layout = Pango::Layout::create (context); - columns[n].layout->set_font_description (fd2); + Text* t = new Text (this); + t->set_font_description (fd2); + t->set_color (rgba_to_color (0.23, 0.0, 0.349, 1.0)); + + const double x = 10.0 + (n * Push2Canvas::inter_button_spacing()); + const double y = 2.0; + t->set_position (Duple (x, y)); + + Rectangle* r = new Rectangle (this); + r->set (Rect (x, y, x + Push2Canvas::inter_button_spacing(), y + baseline)); + + columns[n].lines = t; + columns[n].active_bg = r; columns[n].top = -1; columns[n].active = -1; } @@ -71,8 +92,6 @@ Push2Menu::fill_column (int col, vector<string> v) } set_text (col, 0); - - _dirty = true; } void @@ -86,6 +105,7 @@ Push2Menu::set_text (int col, int top_row) return; } + vector<string>::iterator s = columns[col].text.begin(); s += top_row; @@ -101,10 +121,10 @@ Push2Menu::set_text (int col, int top_row) } } - columns[col].layout->set_text (rows); + columns[col].lines->set (rows); columns[col].top = top_row; - _dirty = true; + redraw (); } void @@ -115,24 +135,43 @@ Push2Menu::scroll (int col, int dir) } else { set_text (col, columns[col].top - 1); } + + redraw (); } void Push2Menu::set_active (int col, int index) { if (col < 0 || col > 7) { + columns[col].active_bg->hide (); return; } if (index < 0 || index > (int) columns[col].text.size()) { + columns[col].active_bg->hide (); return; } columns[col].active = index; + int effective_row = columns[col].active - columns[col].top; + + /* Move active bg */ + + Duple p (columns[col].active_bg->position()); + + columns[col].active_bg->set (Rect (p.x, p.y + (effective_row * baseline), + p.x + Push2Canvas::inter_button_spacing(), p.y + baseline)); + columns[col].active_bg->show (); + + if (columns[col].active < nrows/2) { + set_text (col, 0); + } else { + set_text (col, columns[col].active - (nrows/2) + 1); + } ActiveChanged (); /* emit signal */ - _dirty = true; + redraw (); } void @@ -149,31 +188,25 @@ Push2Menu::step_active (int col, int dir) if (dir < 0) { if (columns[col].active == -1) { - columns[col].active = 0; + set_active (col, -1); } else { columns[col].active = columns[col].active - 1; if (columns[col].active < 0) { - columns[col].active = columns[col].text.size() - 1; + set_active (col, columns[col].text.size() - 1); } } } else { if (columns[col].active == -1) { - columns[col].active = 0; + set_active (col, 0); } else { columns[col].active = columns[col].active + 1; if (columns[col].active >= (int) columns[col].text.size()) { - columns[col].active = 0; + set_active (col, 0); } } } - if (columns[col].active < nrows/2) { - set_text (col, 0); - } else { - set_text (col, columns[col].active - (nrows/2) + 1); - } - - _dirty = true; + redraw (); } int @@ -187,32 +220,7 @@ Push2Menu::get_active (int col) } void -Push2Menu::redraw (Cairo::RefPtr<Cairo::Context> context, bool force) const +Push2Menu::render (Rect const& area, Cairo::RefPtr<Cairo::Context> context) const { - for (int n = 0; n < 8; ++n) { - - /* Active: move to column/now, draw background indicator - for active row. - */ - - const double x = 10.0 + (n * 120.0); - const double y = 2.0; - - if (columns[n].active >= 0) { - int effective_row = columns[n].active - columns[n].top; - context->rectangle (x, y + (effective_row * baseline), 120.0, baseline); - context->set_source_rgb (1.0, 1.0, 1.0); - context->fill (); - } - - /* now draw all the text, in one go */ - - context->move_to (x, y); - context->set_source_rgb (0.23, 0.0, 0.349); - columns[n].layout->update_from_cairo_context (context); - columns[n].layout->show_in_cairo_context (context); - - } - - _dirty = false; + render_children (area, context); } diff --git a/libs/surfaces/push2/menu.h b/libs/surfaces/push2/menu.h index 946d2df56d..1e6e3d6d10 100644 --- a/libs/surfaces/push2/menu.h +++ b/libs/surfaces/push2/menu.h @@ -19,20 +19,30 @@ #ifndef __ardour_push2_menu_h__ #define __ardour_push2_menu_h__ -#include <cairomm/context.h> -#include <cairomm/surface.h> +namespace Cairo { + class Context; + class Region; +} + #include <pangomm/layout.h> #include "pbd/signals.h" +#include "canvas/container.h" + +namespace ArdourCanvas { + class Text; + class Rectangle; +} + namespace ArdourSurface { -class Push2Menu { +class Push2Menu : public ArdourCanvas::Container +{ public: - Push2Menu (Cairo::RefPtr<Cairo::Context>); + Push2Menu (ArdourCanvas::Item* parent); - void redraw (Cairo::RefPtr<Cairo::Context>, bool force) const; - bool dirty () const { return _dirty; } + void render (ArdourCanvas::Rect const& area, Cairo::RefPtr<Cairo::Context> context) const; void fill_column (int col, std::vector<std::string>); void set_active (int col, int index); @@ -45,7 +55,8 @@ class Push2Menu { private: struct Column { std::vector<std::string> text; - Glib::RefPtr<Pango::Layout> layout; + ArdourCanvas::Rectangle* active_bg; + ArdourCanvas::Text* lines; int top; int active; }; @@ -56,9 +67,7 @@ class Push2Menu { void set_text (int col, int top); int nrows; - double baseline; - - mutable bool _dirty; + mutable double baseline; }; } // namespace diff --git a/libs/surfaces/push2/mix.cc b/libs/surfaces/push2/mix.cc index 09d92e0a91..bf10abf30a 100644 --- a/libs/surfaces/push2/mix.cc +++ b/libs/surfaces/push2/mix.cc @@ -16,6 +16,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include <cairomm/region.h> #include <pangomm/layout.h> #include "pbd/compose.h" @@ -42,9 +43,11 @@ #include "ardour/vca_manager.h" #include "canvas/colors.h" +#include "canvas/rectangle.h" #include "gtkmm2ext/gui_thread.h" +#include "canvas.h" #include "mix.h" #include "knob.h" #include "push2.h" @@ -57,17 +60,22 @@ using namespace std; using namespace PBD; using namespace Glib; using namespace ArdourSurface; +using namespace ArdourCanvas; -MixLayout::MixLayout (Push2& p, Session& s, Cairo::RefPtr<Cairo::Context> context) +MixLayout::MixLayout (Push2& p, Session& s) : Push2Layout (p, s) - , _dirty (true) , bank_start (0) , vpot_mode (Volume) { + selection_bg = new Rectangle (this); + selection_bg->hide (); + Pango::FontDescription fd2 ("Sans 10"); for (int n = 0; n < 8; ++n) { - upper_layout[n] = Pango::Layout::create (context); - upper_layout[n]->set_font_description (fd2); + Text* t = new Text (this); + upper_text.push_back (t); + t->set_font_description (fd2); + t->set_color (p2.get_color (Push2::ParameterName)); string txt; switch (n) { @@ -96,18 +104,19 @@ MixLayout::MixLayout (Push2& p, Session& s, Cairo::RefPtr<Cairo::Context> contex txt = _("E Sends"); break; } - upper_layout[n]->set_text (txt); - } + t->set (txt); - Pango::FontDescription fd3 ("Sans 10"); - for (int n = 0; n < 8; ++n) { - lower_layout[n] = Pango::Layout::create (context); - lower_layout[n]->set_font_description (fd3); - } + t = new Text (this); + lower_text.push_back (t); + t->set_font_description (fd2); + t->set_color (p2.get_color (Push2::ParameterName)); - for (int n = 0; n < 8; ++n) { - knobs[n] = new Push2Knob (p2, context); - knobs[n]->set_position (60 + (n*120), 95); + Rectangle* r = new Rectangle (this); + r->set (Rect (10 + (n*Push2Canvas::inter_button_spacing()) - 5, 2, Push2Canvas::inter_button_spacing(), 21)); + backgrounds.push_back (r); + + knobs[n] = new Push2Knob (p2, this); + knobs[n]->set_position (60 + (n*Push2Canvas::inter_button_spacing()), 95); knobs[n]->set_radius (25); } @@ -119,14 +128,14 @@ MixLayout::MixLayout (Push2& p, Session& s, Cairo::RefPtr<Cairo::Context> contex MixLayout::~MixLayout () { - for (int n = 0; n < 8; ++n) { - delete knobs[n]; - } + // Item destructor deletes all children } void -MixLayout::on_show () +MixLayout::show () { + Item::show (); + mode_button->set_color (Push2::LED::White); mode_button->set_state (Push2::LED::OneShot24th); p2.write (mode_button->state_msg()); @@ -134,116 +143,19 @@ MixLayout::on_show () switch_bank (bank_start); } -bool -MixLayout::redraw (Cairo::RefPtr<Cairo::Context> context, bool force) const +void +MixLayout::render (Rect const& area, Cairo::RefPtr<Cairo::Context> context) const { - bool children_dirty = false; - - for (int n = 0; n < 8; ++n) { - if (knobs[n]->dirty()) { - children_dirty = true; - break; - } - } - - for (int n = 0; n < 8; ++n) { - if (stripable[n]) { - - string shortname = short_version (stripable[n]->name(), 10); - string text; - boost::shared_ptr<AutomationControl> ac; - ac = stripable[n]->solo_control(); - if (ac && ac->get_value()) { - text += "* "; - } - boost::shared_ptr<MuteControl> mc; - mc = stripable[n]->mute_control (); - if (mc) { - if (mc->muted_by_self_or_masters()) { - text += "! "; - } else if (mc->muted_by_others_soloing()) { - text += "- "; // it would be nice to use Unicode mute"\uD83D\uDD07 "; - } - } - text += shortname; - - if (text != lower_layout[n]->get_text()) { - lower_layout[n]->set_text (text); - children_dirty = true; - } - } - } - - if (!children_dirty && !_dirty && !force) { - return false; - } - set_source_rgb (context, p2.get_color (Push2::DarkBackground)); - context->rectangle (0, 0, p2.cols, p2.rows); + context->rectangle (0, 0, display_width(), display_height()); context->fill (); - set_source_rgb (context, p2.get_color (Push2::ParameterName)); - - for (int n = 0; n < 8; ++n) { - - if (upper_layout[n]->get_text().empty()) { - continue; - } - - /* Draw highlight box */ - - uint32_t color = p2.get_color (Push2::ParameterName); - - if (n == (int) vpot_mode) { - set_source_rgb (context, color); - context->rectangle (10 + (n*120) - 5, 2, 120, 21); - context->fill(); - set_source_rgb (context, ArdourCanvas::contrasting_text_color (color)); - } else { - set_source_rgb (context, color); - } - - context->move_to (10 + (n*120), 2); - upper_layout[n]->update_from_cairo_context (context); - upper_layout[n]->show_in_cairo_context (context); - } - context->move_to (0, 22.5); - context->line_to (p2.cols, 22.5); + context->line_to (display_width(), 22.5); context->set_line_width (1.0); context->stroke (); - for (int n = 0; n < 8; ++n) { - knobs[n]->redraw (context, force); - } - - for (int n = 0; n < 8; ++n) { - - if (lower_layout[n]->get_text().empty()) { - continue; - } - - if (stripable[n]) { - uint32_t color = stripable[n]->presentation_info().color(); - - if (stripable[n]->presentation_info().selected()) { - set_source_rgb (context, color); - context->rectangle (10 + (n*120) - 5, 137, 120, 21); - context->fill(); - set_source_rgb (context, ArdourCanvas::contrasting_text_color (color)); - } else { - set_source_rgb (context, color); - } - - context->move_to (10 + (n*120), 140); - lower_layout[n]->update_from_cairo_context (context); - lower_layout[n]->show_in_cairo_context (context); - } - } - - _dirty = false; - - return true; + render_children (area, context); } void @@ -466,12 +378,53 @@ MixLayout::stripable_property_change (PropertyChange const& what_changed, int wh return; } - /* cancel string, which will cause a redraw on the next update - * cycle. The redraw will reflect selected status - */ + if (stripable[which]->presentation_info().selected()) { + selection_bg->show (); + selection_bg->set_fill_color (stripable[which]->presentation_info().color()); + selection_bg->set (Rect (10 + (which*Push2Canvas::inter_button_spacing()) - 5, 137, + 10 + (which*Push2Canvas::inter_button_spacing()) - 5 + Push2Canvas::inter_button_spacing(), + 137 + 21)); + lower_text[which]->set_color (ArdourCanvas::contrasting_text_color (selection_bg->fill_color())); + } else { + selection_bg->hide (); + lower_text[which]->set_color (stripable[which]->presentation_info().color()); + } + } +} + +void +MixLayout::solo_changed (uint32_t n) +{ + solo_mute_changed (n); +} - lower_layout[which]->set_text (string()); +void +MixLayout::mute_changed (uint32_t n) +{ + solo_mute_changed (n); +} + +void +MixLayout::solo_mute_changed (uint32_t n) +{ + string shortname = short_version (stripable[n]->name(), 10); + string text; + boost::shared_ptr<AutomationControl> ac; + ac = stripable[n]->solo_control(); + if (ac && ac->get_value()) { + text += "* "; } + boost::shared_ptr<MuteControl> mc; + mc = stripable[n]->mute_control (); + if (mc) { + if (mc->muted_by_self_or_masters()) { + text += "! "; + } else if (mc->muted_by_others_soloing()) { + text += "- "; // it would be nice to use Unicode mute"\uD83D\uDD07 "; + } + } + text += shortname; + lower_text[n]->set (text); } void @@ -499,6 +452,11 @@ MixLayout::switch_bank (uint32_t base) /* some missing strips; new bank the same or more empty stripables than the old one, do nothing since we had already reached the end. */ + for (int n = 0; n < 8; ++n) { + upper_text[n]->hide (); + lower_text[n]->hide (); + backgrounds[n]->hide (); + } return; } @@ -512,13 +470,25 @@ MixLayout::switch_bank (uint32_t base) for (int n = 0; n < 8; ++n) { if (!stripable[n]) { + upper_text[n]->hide (); + lower_text[n]->hide (); + backgrounds[n]->hide (); continue; } + upper_text[n]->show (); + lower_text[n]->show (); + backgrounds[n]->show (); + backgrounds[n]->set_fill_color (stripable[n]->presentation_info().color()); + /* stripable goes away? refill the bank, starting at the same point */ stripable[n]->DropReferences.connect (stripable_connections, invalidator (*this), boost::bind (&MixLayout::switch_bank, this, bank_start), &p2); stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, invalidator (*this), boost::bind (&MixLayout::stripable_property_change, this, _1, n), &p2); + stripable[n]->solo_control()->Changed.connect (stripable_connections, invalidator (*this), boost::bind (&MixLayout::solo_changed, this, n), &p2); + stripable[n]->mute_control()->Changed.connect (stripable_connections, invalidator (*this), boost::bind (&MixLayout::mute_changed, this, n), &p2); + + solo_mute_changed (n); Push2::Button* b; diff --git a/libs/surfaces/push2/mix.h b/libs/surfaces/push2/mix.h index ebd936aff1..85b1feba10 100644 --- a/libs/surfaces/push2/mix.h +++ b/libs/surfaces/push2/mix.h @@ -26,6 +26,11 @@ namespace ARDOUR { class Stripable; } +namespace ArdourCanvas { + class Rectangle; + class Text; +} + namespace ArdourSurface { class Push2Knob; @@ -33,11 +38,11 @@ class Push2Knob; class MixLayout : public Push2Layout { public: - MixLayout (Push2& p, ARDOUR::Session&, Cairo::RefPtr<Cairo::Context>); + MixLayout (Push2& p, ARDOUR::Session&); ~MixLayout (); - bool redraw (Cairo::RefPtr<Cairo::Context>, bool force) const; - void on_show (); + void render (ArdourCanvas::Rect const &, Cairo::RefPtr<Cairo::Context>) const; + void show (); void button_upper (uint32_t n); void button_lower (uint32_t n); @@ -53,8 +58,10 @@ class MixLayout : public Push2Layout private: mutable bool _dirty; - Glib::RefPtr<Pango::Layout> upper_layout[8]; - Glib::RefPtr<Pango::Layout> lower_layout[8]; + std::vector<ArdourCanvas::Text*> upper_text; + std::vector<ArdourCanvas::Text*> lower_text; + std::vector<ArdourCanvas::Rectangle*> backgrounds; + ArdourCanvas::Rectangle* selection_bg; Push2Knob* knobs[8]; /* stripables */ @@ -80,6 +87,10 @@ class MixLayout : public Push2Layout Push2::Button* mode_button; VPotMode vpot_mode; void show_vpot_mode (); + + void solo_changed (uint32_t n); + void mute_changed (uint32_t n); + void solo_mute_changed (uint32_t n); }; } /* namespace */ diff --git a/libs/surfaces/push2/push2.cc b/libs/surfaces/push2/push2.cc index caa3f1e102..0cf7ceee96 100644 --- a/libs/surfaces/push2/push2.cc +++ b/libs/surfaces/push2/push2.cc @@ -33,7 +33,6 @@ #include "ardour/async_midi_port.h" #include "ardour/audioengine.h" #include "ardour/debug.h" -#include "ardour/filesystem_paths.h" #include "ardour/midiport_manager.h" #include "ardour/midi_track.h" #include "ardour/midi_port.h" @@ -45,13 +44,14 @@ #include "canvas/colors.h" -#include "push2.h" +#include "canvas.h" #include "gui.h" #include "layout.h" -#include "scale.h" +#include "menu.h" #include "mix.h" +#include "push2.h" +#include "scale.h" #include "track_mix.h" -#include "menu.h" #include "pbd/i18n.h" @@ -63,10 +63,6 @@ using namespace ArdourSurface; #include "pbd/abstract_ui.cc" // instantiate template -const int Push2::cols = 960; -const int Push2::rows = 160; -const int Push2::pixels_per_row = 1024; - #define ABLETON 0x2982 #define PUSH2 0x1967 @@ -127,8 +123,6 @@ Push2::Push2 (ARDOUR::Session& s) : ControlProtocol (s, string (X_("Ableton Push 2"))) , AbstractUI<Push2Request> (name()) , handle (0) - , device_buffer (0) - , frame_buffer (Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, cols, rows)) , _modifier_state (None) , splash_start (0) , _current_layout (0) @@ -143,7 +137,6 @@ Push2::Push2 (ARDOUR::Session& s) , percussion (false) , _pressure_mode (AfterTouch) { - context = Cairo::Context::create (frame_buffer); build_maps (); build_color_map (); @@ -234,15 +227,17 @@ Push2::open () return -1; } - device_frame_buffer = new uint16_t[rows*pixels_per_row]; - - memset (device_frame_buffer, 0, sizeof (uint16_t) * rows * pixels_per_row); - - frame_header[0] = 0xef; - frame_header[1] = 0xcd; - frame_header[2] = 0xab; - frame_header[3] = 0x89; - memset (&frame_header[4], 0, 12); + try { + _canvas = new Push2Canvas (*this, 160, 960); + mix_layout = new MixLayout (*this, *session); + scale_layout = new ScaleLayout (*this, *session); + track_mix_layout = new TrackMixLayout (*this, *session); + } catch (...) { + error << _("Cannot construct Canvas for display") << endmsg; + libusb_release_interface (handle, 0x00); + libusb_close (handle); + return -1; + } /* setup ports */ @@ -284,9 +279,7 @@ Push2::open () connect_to_parser (); - mix_layout = new MixLayout (*this, *session, context); - scale_layout = new ScaleLayout (*this, *session, context); - track_mix_layout = new TrackMixLayout (*this, *session, context); + _canvas->splash (); return 0; } @@ -321,7 +314,6 @@ Push2::close () _input_port = 0; _output_port = 0; - vblank_connection.disconnect (); periodic_connection.disconnect (); session_connections.drop_connections (); @@ -338,9 +330,6 @@ Push2::close () handle = 0; } - delete [] device_frame_buffer; - device_frame_buffer = 0; - return 0; } @@ -460,63 +449,8 @@ Push2::stop () return 0; } -/** render host-side frame buffer (a Cairo ImageSurface) to the current - * device-side frame buffer. The device frame buffer will be pushed to the - * device on the next call to vblank() - */ - -int -Push2::blit_to_device_frame_buffer () -{ - /* ensure that all drawing has been done before we fetch pixel data */ - - frame_buffer->flush (); - - const int stride = 3840; /* bytes per row for Cairo::FORMAT_ARGB32 */ - const uint8_t* data = frame_buffer->get_data (); - - /* fill frame buffer (320kB) */ - - uint16_t* fb = (uint16_t*) device_frame_buffer; - - for (int row = 0; row < rows; ++row) { - - const uint8_t* dp = data + row * stride; - - for (int col = 0; col < cols; ++col) { - - /* fetch r, g, b (range 0..255). Ignore alpha */ - - const int r = (*((const uint32_t*)dp) >> 16) & 0xff; - const int g = (*((const uint32_t*)dp) >> 8) & 0xff; - const int b = *((const uint32_t*)dp) & 0xff; - - /* convert to 5 bits, 6 bits, 5 bits, respectively */ - /* generate 16 bit BGB565 value */ - - *fb++ = (r >> 3) | ((g & 0xfc) << 3) | ((b & 0xf8) << 8); - - /* the push2 docs state that we should xor the pixel - * data. Doing so doesn't work correctly, and not doing - * so seems to work fine (colors roughly match intended - * values). - */ - - dp += 4; - } - - /* skip 128 bytes to next line. This is filler, used to avoid line borders occuring in the middle of 512 - byte USB buffers - */ - - fb += 64; /* 128 bytes = 64 int16_t */ - } - - return 0; -} - bool -Push2::redraw () +Push2::vblank () { if (splash_start) { @@ -524,50 +458,15 @@ Push2::redraw () if (get_microseconds() - splash_start > 3000000) { splash_start = 0; - } else { - return false; } - } - - Glib::Threads::Mutex::Lock lm (layout_lock, Glib::Threads::TRY_LOCK); - - if (!lm.locked()) { - /* can't get layout, no re-render needed */ - return false; - } - - bool render_needed = false; - - if (drawn_layout != _current_layout) { - render_needed = true; - } - bool dirty = _current_layout->redraw (context, render_needed); - drawn_layout = _current_layout; + return true; - return dirty || render_needed; -} - -bool -Push2::vblank () -{ - int transferred = 0; - const int timeout_msecs = 1000; - int err; - - if ((err = libusb_bulk_transfer (handle, 0x01, frame_header, sizeof (frame_header), &transferred, timeout_msecs))) { - return false; - } + } else { - if (redraw()) { - /* things changed */ - blit_to_device_frame_buffer (); - } + _canvas->vblank(); - if ((err = libusb_bulk_transfer (handle, 0x01, (uint8_t*) device_frame_buffer , 2 * rows * pixels_per_row, &transferred, timeout_msecs))) { - return false; } - return true; } @@ -1210,60 +1109,6 @@ Push2::end_shift () void Push2::splash () { - std::string splash_file; - - Searchpath rc (ARDOUR::ardour_data_search_path()); - rc.add_subdirectory_to_paths ("resources"); - - if (!find_file (rc, PROGRAM_NAME "-splash.png", splash_file)) { - cerr << "Cannot find splash screen image file\n"; - throw failed_constructor(); - } - - Cairo::RefPtr<Cairo::ImageSurface> img = Cairo::ImageSurface::create_from_png (splash_file); - - double x_ratio = (double) img->get_width() / (cols - 20); - double y_ratio = (double) img->get_height() / (rows - 20); - double scale = min (x_ratio, y_ratio); - - /* background */ - - context->set_source_rgb (0.764, 0.882, 0.882); - context->paint (); - - /* image */ - - context->save (); - context->translate (5, 5); - context->scale (scale, scale); - context->set_source (img, 0, 0); - context->paint (); - context->restore (); - - /* text */ - - Glib::RefPtr<Pango::Layout> some_text = Pango::Layout::create (context); - - Pango::FontDescription fd ("Sans 38"); - some_text->set_font_description (fd); - some_text->set_text (string_compose ("%1 %2", PROGRAM_NAME, VERSIONSTRING)); - - context->move_to (200, 10); - context->set_source_rgb (0, 0, 0); - some_text->update_from_cairo_context (context); - some_text->show_in_cairo_context (context); - - Pango::FontDescription fd2 ("Sans Italic 18"); - some_text->set_font_description (fd2); - some_text->set_text (_("Ableton Push 2 Support")); - - context->move_to (200, 80); - context->set_source_rgb (0, 0, 0); - some_text->update_from_cairo_context (context); - some_text->show_in_cairo_context (context); - - splash_start = get_microseconds (); - blit_to_device_frame_buffer (); } bool @@ -1730,7 +1575,7 @@ Push2::fill_color_table () } -uint32_t +ArdourCanvas::Color Push2::get_color (ColorName name) { Colors::iterator c = colors.find (name); @@ -1745,14 +1590,15 @@ void Push2::set_current_layout (Push2Layout* layout) { if (_current_layout) { - _current_layout->on_hide (); + _current_layout->hide (); + _canvas->root()->remove (_current_layout); } _current_layout = layout; - drawn_layout = 0; if (_current_layout) { - _current_layout->on_show (); + _current_layout->show (); + _canvas->root()->add (_current_layout); } } diff --git a/libs/surfaces/push2/push2.h b/libs/surfaces/push2/push2.h index be4c116895..4704ce9a30 100644 --- a/libs/surfaces/push2/push2.h +++ b/libs/surfaces/push2/push2.h @@ -27,8 +27,6 @@ #include <libusb.h> -#include <cairomm/refptr.h> - #define ABSTRACT_UI_EXPORTS #include "pbd/abstract_ui.h" @@ -42,11 +40,6 @@ #include "midi_byte_array.h" #include "mode.h" -namespace Cairo { - class ImageSurface; - class Context; -} - namespace Pango { class Layout; } @@ -74,6 +67,7 @@ public: class P2GUI; class Push2Menu; class Push2Layout; +class Push2Canvas; class Push2 : public ARDOUR::ControlProtocol , public AbstractUI<Push2Request> @@ -341,6 +335,7 @@ class Push2 : public ARDOUR::ControlProtocol bool in_key() const { return _in_key; } Push2Layout* current_layout() const; + Push2Canvas* canvas() const { return _canvas; } enum ModifierState { None = 0, @@ -357,33 +352,20 @@ class Push2 : public ARDOUR::ControlProtocol uint8_t get_color_index (uint32_t rgb); uint32_t get_color (ColorName); - static const int cols; - static const int rows; - PressureMode pressure_mode () const { return _pressure_mode; } void set_pressure_mode (PressureMode); PBD::Signal1<void,PressureMode> PressureModeChange; - + + libusb_device_handle* usb_handle() const { return handle; } + private: libusb_device_handle *handle; - uint8_t frame_header[16]; - uint16_t* device_frame_buffer; - int device_buffer; - Cairo::RefPtr<Cairo::ImageSurface> frame_buffer; - sigc::connection vblank_connection; - sigc::connection periodic_connection; - ModifierState _modifier_state; - static const int pixels_per_row; - void do_request (Push2Request*); int stop (); int open (); int close (); - bool redraw (); - int blit_to_device_frame_buffer (); - bool vblank (); void relax () {} @@ -431,6 +413,8 @@ class Push2 : public ARDOUR::ControlProtocol void handle_midi_sysex (MIDI::Parser&, MIDI::byte *, size_t count); bool midi_input_handler (Glib::IOCondition ioc, MIDI::Port* port); + + sigc::connection periodic_connection; bool periodic (); void thread_init (); @@ -517,13 +501,16 @@ class Push2 : public ARDOUR::ControlProtocol boost::shared_ptr<ARDOUR::Stripable> master; boost::shared_ptr<ARDOUR::Stripable> monitor; - /* Cairo graphics context */ - - Cairo::RefPtr<Cairo::Context> context; + sigc::connection vblank_connection; + bool vblank (); void splash (); ARDOUR::microseconds_t splash_start; + /* the canvas */ + + Push2Canvas* _canvas; + /* Layouts */ mutable Glib::Threads::Mutex layout_lock; diff --git a/libs/surfaces/push2/scale.cc b/libs/surfaces/push2/scale.cc index 8f04b22635..97b59220c3 100644 --- a/libs/surfaces/push2/scale.cc +++ b/libs/surfaces/push2/scale.cc @@ -16,6 +16,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include <cairomm/region.h> #include <pangomm/layout.h> #include "pbd/compose.h" @@ -51,37 +52,26 @@ using namespace std; using namespace PBD; using namespace Glib; using namespace ArdourSurface; +using namespace ArdourCanvas; -ScaleLayout::ScaleLayout (Push2& p, Session& s, Cairo::RefPtr<Cairo::Context> context) +ScaleLayout::ScaleLayout (Push2& p, Session& s) : Push2Layout (p, s) { - build_scale_menu (context); + build_scale_menu (); } ScaleLayout::~ScaleLayout () { } -bool -ScaleLayout::redraw (Cairo::RefPtr<Cairo::Context> context, bool force) const +void +ScaleLayout::render (Rect const& area, Cairo::RefPtr<Cairo::Context> context) const { - bool draw = false; - - if (scale_menu->dirty()) { - draw = true; - } - - if (!draw) { - return false; - } - context->set_source_rgb (0.764, 0.882, 0.882); context->rectangle (0, 0, 960, 160); context->fill (); - scale_menu->redraw (context, force); - - return true; + scale_menu->render (area, context); } void @@ -109,11 +99,11 @@ ScaleLayout::strip_vpot_touch (int, bool) } void -ScaleLayout::build_scale_menu (Cairo::RefPtr<Cairo::Context> context) +ScaleLayout::build_scale_menu () { vector<string> v; - scale_menu = new Push2Menu (context); + scale_menu = new Push2Menu (this); v.push_back ("Dorian"); v.push_back ("IonianMajor"); diff --git a/libs/surfaces/push2/scale.h b/libs/surfaces/push2/scale.h index 6df6abf921..7d7b1f870a 100644 --- a/libs/surfaces/push2/scale.h +++ b/libs/surfaces/push2/scale.h @@ -30,10 +30,10 @@ namespace ArdourSurface { class ScaleLayout : public Push2Layout { public: - ScaleLayout (Push2& p, ARDOUR::Session&, Cairo::RefPtr<Cairo::Context>); + ScaleLayout (Push2& p, ARDOUR::Session&); ~ScaleLayout (); - bool redraw (Cairo::RefPtr<Cairo::Context>, bool force) const; + void render (ArdourCanvas::Rect const &, Cairo::RefPtr<Cairo::Context>) const; void button_upper (uint32_t n); void button_lower (uint32_t n); @@ -43,7 +43,7 @@ class ScaleLayout : public Push2Layout private: Push2Menu* scale_menu; - void build_scale_menu (Cairo::RefPtr<Cairo::Context>); + void build_scale_menu (); }; } /* namespace */ diff --git a/libs/surfaces/push2/splash.cc b/libs/surfaces/push2/splash.cc new file mode 100644 index 0000000000..d03205e3a6 --- /dev/null +++ b/libs/surfaces/push2/splash.cc @@ -0,0 +1,98 @@ +/* + 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 <pangomm/layout.h> + +#include "pbd/compose.h" +#include "pbd/failed_constructor.h" +#include "pbd/file_utils.h" +#include "pbd/i18n.h" +#include "pbd/search_path.h" + +#include "ardour/filesystem_paths.h" + +#include "splash.h" + +using namespace ARDOUR; +using namespace PBD; +using namespace std; +using namespace ArdourSurface; +using namespace ArdourCanvas; + +SplashLayout::SplashLayout (Push2& p, Session& s) + : Push2Layout (p, s) +{ + std::string splash_file; + + Searchpath rc (ARDOUR::ardour_data_search_path()); + rc.add_subdirectory_to_paths ("resources"); + + if (!find_file (rc, PROGRAM_NAME "-splash.png", splash_file)) { + cerr << "Cannot find splash screen image file\n"; + throw failed_constructor(); + } + + img = Cairo::ImageSurface::create_from_png (splash_file); +} + +void +SplashLayout::render (Rect const& area, Cairo::RefPtr<Cairo::Context> context) const +{ + int rows = display_height (); + int cols = display_width (); + + double x_ratio = (double) img->get_width() / (cols - 20); + double y_ratio = (double) img->get_height() / (rows - 20); + double scale = min (x_ratio, y_ratio); + + /* background */ + + context->set_source_rgb (0.764, 0.882, 0.882); + context->paint (); + + /* image */ + + context->save (); + context->translate (5, 5); + context->scale (scale, scale); + context->set_source (img, 0, 0); + context->paint (); + context->restore (); + + /* text */ + + Glib::RefPtr<Pango::Layout> some_text = Pango::Layout::create (context); + + Pango::FontDescription fd ("Sans 38"); + some_text->set_font_description (fd); + some_text->set_text (string_compose ("%1 %2", PROGRAM_NAME, VERSIONSTRING)); + + context->move_to (200, 10); + context->set_source_rgb (0, 0, 0); + some_text->update_from_cairo_context (context); + some_text->show_in_cairo_context (context); + + Pango::FontDescription fd2 ("Sans Italic 18"); + some_text->set_font_description (fd2); + some_text->set_text (_("Ableton Push 2 Support")); + + context->move_to (200, 80); + context->set_source_rgb (0, 0, 0); + some_text->update_from_cairo_context (context); + some_text->show_in_cairo_context (context); +} diff --git a/libs/surfaces/push2/splash.h b/libs/surfaces/push2/splash.h new file mode 100644 index 0000000000..c58a383f52 --- /dev/null +++ b/libs/surfaces/push2/splash.h @@ -0,0 +1,47 @@ +/* + 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. +*/ + +#ifndef __ardour_push2_splash_h__ +#define __ardour_push2_splash_h__ + +#include <cairomm/surface.h> + +#include "layout.h" +#include "push2.h" + +namespace ARDOUR { + class Stripable; +} + +namespace ArdourSurface { + +class SplashLayout : public Push2Layout +{ + public: + SplashLayout (Push2& p, ARDOUR::Session&); + ~SplashLayout (); + + void render (ArdourCanvas::Rect const &, Cairo::RefPtr<Cairo::Context>) const; + + private: + Cairo::RefPtr<Cairo::ImageSurface> img; +}; + +} /* namespace */ + +#endif /* __ardour_push2_splash_h__ */ diff --git a/libs/surfaces/push2/track_mix.cc b/libs/surfaces/push2/track_mix.cc index f313435635..cdb1bacd95 100644 --- a/libs/surfaces/push2/track_mix.cc +++ b/libs/surfaces/push2/track_mix.cc @@ -16,6 +16,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include <cairomm/region.h> #include <pangomm/layout.h> #include "pbd/compose.h" @@ -23,6 +24,7 @@ #include "pbd/debug.h" #include "pbd/failed_constructor.h" #include "pbd/file_utils.h" +#include "pbd/i18n.h" #include "pbd/search_path.h" #include "pbd/enumwriter.h" @@ -43,70 +45,77 @@ #include "gtkmm2ext/gui_thread.h" #include "gtkmm2ext/rgb_macros.h" +#include "canvas.h" #include "knob.h" #include "menu.h" #include "push2.h" #include "track_mix.h" #include "utils.h" -#include "pbd/i18n.h" - using namespace ARDOUR; using namespace std; using namespace PBD; using namespace Glib; using namespace ArdourSurface; +using namespace ArdourCanvas; -TrackMixLayout::TrackMixLayout (Push2& p, Session& s, Cairo::RefPtr<Cairo::Context> context) +TrackMixLayout::TrackMixLayout (Push2& p, Session& s) : Push2Layout (p, s) - , _dirty (true) { Pango::FontDescription fd2 ("Sans 10"); for (int n = 0; n < 8; ++n) { - upper_layout[n] = Pango::Layout::create (context); - upper_layout[n]->set_font_description (fd2); + Text* t = new Text (this); + t->set_font_description (fd2); + t->set_color (p2.get_color (Push2::ParameterName)); + t->set_position ( Duple (10 + (n*Push2Canvas::inter_button_spacing()), 2)); + + upper_text.push_back (t); - lower_layout[n] = Pango::Layout::create (context); - lower_layout[n]->set_font_description (fd2); + t = new Text (this); + t->set_font_description (fd2); + t->set_color (p2.get_color (Push2::ParameterName)); + t->set_position (Duple (10 + (n*Push2Canvas::inter_button_spacing()), 140)); + + lower_text.push_back (t); switch (n) { case 0: - upper_layout[n]->set_text (_("TRACK VOLUME")); - lower_layout[n]->set_text (_("MUTE")); + upper_text[n]->set (_("TRACK VOLUME")); + lower_text[n]->set (_("MUTE")); break; case 1: - upper_layout[n]->set_text (_("TRACK PAN")); - lower_layout[n]->set_text (_("SOLO")); + upper_text[n]->set (_("TRACK PAN")); + lower_text[n]->set (_("SOLO")); break; case 2: - upper_layout[n]->set_text (_("TRACK WIDTH")); - lower_layout[n]->set_text (_("REC-ENABLE")); + upper_text[n]->set (_("TRACK WIDTH")); + lower_text[n]->set (_("REC-ENABLE")); break; case 3: - upper_layout[n]->set_text (_("TRACK TRIM")); - lower_layout[n]->set_text (_("IN")); + upper_text[n]->set (_("TRACK TRIM")); + lower_text[n]->set (_("IN")); break; case 4: - upper_layout[n]->set_text (_("")); - lower_layout[n]->set_text (_("DISK")); + upper_text[n]->set (_("")); + lower_text[n]->set (_("DISK")); break; case 5: - upper_layout[n]->set_text (_("")); - lower_layout[n]->set_text (_("SOLO ISO")); + upper_text[n]->set (_("")); + lower_text[n]->set (_("SOLO ISO")); break; case 6: - upper_layout[n]->set_text (_("")); - lower_layout[n]->set_text (_("SOLO LOCK")); + upper_text[n]->set (_("")); + lower_text[n]->set (_("SOLO LOCK")); break; case 7: - upper_layout[n]->set_text (_("")); - lower_layout[n]->set_text (_("")); + upper_text[n]->set (_("")); + lower_text[n]->set (_("")); break; } - knobs[n] = new Push2Knob (p2, context); - knobs[n]->set_position (60 + (120*n), 95); + knobs[n] = new Push2Knob (p2, this); + knobs[n]->set_position (60 + (Push2Canvas::inter_button_spacing()*n), 95); knobs[n]->set_radius (25); } @@ -129,62 +138,24 @@ TrackMixLayout::selection_changed () } } void -TrackMixLayout::on_show () +TrackMixLayout::show () { selection_changed (); } -bool -TrackMixLayout::redraw (Cairo::RefPtr<Cairo::Context> context, bool force) const +void +TrackMixLayout::render (ArdourCanvas::Rect const & area, Cairo::RefPtr<Cairo::Context> context) const { - bool children_dirty = false; - - for (int n = 0; n < 8; ++n) { - if (knobs[n]->dirty()) { - children_dirty = true; - break; - } - } - - if (!children_dirty) { - return false; - } - set_source_rgb (context, p2.get_color (Push2::DarkBackground)); - context->rectangle (0, 0, p2.cols, p2.rows); + context->rectangle (0, 0, display_width(), display_height()); context->fill (); - for (int n = 0; n < 8; ++n) { - - if (!upper_layout[n]->get_text().empty()) { - - /* Draw highlight box */ - - uint32_t color = p2.get_color (Push2::ParameterName); - set_source_rgb (context, color); - - context->move_to (10 + (n*120), 2); - upper_layout[n]->update_from_cairo_context (context); - upper_layout[n]->show_in_cairo_context (context); - } - - if (!lower_layout[n]->get_text().empty()) { - context->move_to (10 + (n*120), 140); - lower_layout[n]->update_from_cairo_context (context); - lower_layout[n]->show_in_cairo_context (context); - } - } - context->move_to (0, 22.5); - context->line_to (p2.cols, 22.5); + context->line_to (display_width(), 22.5); context->set_line_width (1.0); context->stroke (); - for (int n = 0; n < 8; ++n) { - knobs[n]->redraw (context, force); - } - - return true; + Container::render_children (area, context); } void diff --git a/libs/surfaces/push2/track_mix.h b/libs/surfaces/push2/track_mix.h index 102b9940da..e2f3d8707c 100644 --- a/libs/surfaces/push2/track_mix.h +++ b/libs/surfaces/push2/track_mix.h @@ -27,6 +27,10 @@ namespace ARDOUR { class Stripable; } +namespace ArdourCanvas { + class Text; +} + namespace ArdourSurface { class Push2Knob; @@ -34,13 +38,14 @@ class Push2Knob; class TrackMixLayout : public Push2Layout { public: - TrackMixLayout (Push2& p, ARDOUR::Session&, Cairo::RefPtr<Cairo::Context>); + TrackMixLayout (Push2& p, ARDOUR::Session&); ~TrackMixLayout (); void set_stripable (boost::shared_ptr<ARDOUR::Stripable>); - bool redraw (Cairo::RefPtr<Cairo::Context>, bool force) const; - void on_show (); + void render (ArdourCanvas::Rect const &, Cairo::RefPtr<Cairo::Context>) const; + + void show (); void button_upper (uint32_t n); void button_lower (uint32_t n); @@ -53,8 +58,8 @@ class TrackMixLayout : public Push2Layout PBD::ScopedConnectionList stripable_connections; bool _dirty; - Glib::RefPtr<Pango::Layout> upper_layout[8]; - Glib::RefPtr<Pango::Layout> lower_layout[8]; + std::vector<ArdourCanvas::Text*> upper_text; + std::vector<ArdourCanvas::Text*> lower_text; Push2Knob* knobs[8]; diff --git a/libs/surfaces/push2/wscript b/libs/surfaces/push2/wscript index 0f1b1c3b7d..07f095ed82 100644 --- a/libs/surfaces/push2/wscript +++ b/libs/surfaces/push2/wscript @@ -22,6 +22,7 @@ def build(bld): obj.source = ''' push2.cc buttons.cc + canvas.cc interface.cc midi_byte_array.cc leds.cc @@ -32,6 +33,7 @@ def build(bld): menu.cc mix.cc scale.cc + splash.cc track_mix.cc utils.cc ''' |