From 16c809986d8d607905311efaa04a0ea50bbb9e81 Mon Sep 17 00:00:00 2001 From: Ben Loftis Date: Wed, 19 Jan 2022 17:17:47 -0600 Subject: [PATCH] Mini Timeline: Add Circular Cue Markers; various cleanup and pixel-pushing tweaks --- gtk2_ardour/mini_timeline.cc | 208 +++++++++++++++++++++-------------- gtk2_ardour/mini_timeline.h | 8 +- 2 files changed, 128 insertions(+), 88 deletions(-) diff --git a/gtk2_ardour/mini_timeline.cc b/gtk2_ardour/mini_timeline.cc index f9f5cd6a6d..676107ae88 100644 --- a/gtk2_ardour/mini_timeline.cc +++ b/gtk2_ardour/mini_timeline.cc @@ -24,6 +24,7 @@ #include "gtkmm2ext/colors.h" #include "gtkmm2ext/gui_thread.h" #include "gtkmm2ext/keyboard.h" +#include "gtkmm2ext/utils.h" #include "widgets/tooltips.h" @@ -40,6 +41,7 @@ #define BBT_BAR_CHAR "|" using namespace ARDOUR; +using namespace Gtkmm2ext; MiniTimeline::MiniTimeline () : _last_update_sample (-1) @@ -67,8 +69,11 @@ MiniTimeline::MiniTimeline () Location::name_changed.connect (marker_connection, invalidator (*this), boost::bind (&MiniTimeline::update_minitimeline, this), gui_context ()); Location::end_changed.connect (marker_connection, invalidator (*this), boost::bind (&MiniTimeline::update_minitimeline, this), gui_context ()); Location::start_changed.connect (marker_connection, invalidator (*this), boost::bind (&MiniTimeline::update_minitimeline, this), gui_context ()); + Location::changed.connect (marker_connection, invalidator (*this), boost::bind (&MiniTimeline::update_minitimeline, this), gui_context ()); Location::flags_changed.connect (marker_connection, invalidator (*this), boost::bind (&MiniTimeline::update_minitimeline, this), gui_context ()); + Temporal::TempoMap::MapChanged.connect (tempo_map_connection, invalidator (*this), boost::bind (&MiniTimeline::update_minitimeline, this), gui_context()); + ArdourWidgets::set_tooltip (*this, string_compose (_("Navigation Timeline. Use left-click to locate to time position or marker; scroll-wheel to jump, hold %1 for fine grained and %2 + %3 for extra-fine grained control. Right-click to set display range. The display unit is defined by the primary clock."), Gtkmm2ext::Keyboard::primary_modifier_name(), @@ -204,11 +209,6 @@ MiniTimeline::super_rapid_update () change = true; } - if (_clock_mode == AudioClock::BBT) { - // TODO check if tempo-map changed - change = true; - } - if (change) { _last_update_sample = sample; update_minitimeline (); @@ -339,7 +339,7 @@ MiniTimeline::draw_mark (cairo_t* cr, int x0, int x1, const std::string& label, const int y = PADDING; int w2 = (h - 1) / 4; - double h0 = h * .4; + double h0 = h * .6; double h1 = h - h0; int lw, lh; @@ -351,30 +351,14 @@ MiniTimeline::draw_mark (cairo_t* cr, int x0, int x1, const std::string& label, prelight = true; } - // TODO cache in set_colors() + const double scale = UIConfiguration::instance ().get_ui_scale (); uint32_t color = UIConfiguration::instance().color ( - prelight ? "entered marker" : "location marker"); + prelight ? "entered marker" : "location marker"); - double r, g, b, a; - Gtkmm2ext::color_to_rgba (color, r, g, b, a); + //shrink the height of the 'flag' part, a bit. + h -= 4*scale; - if (rw < x0) { - rw = x1; - } else { - cairo_save (cr); - cairo_rectangle (cr, x0, y, rw - x0, h); - cairo_set_source_rgba (cr, r, g, b, 0.5); // this should use a shaded color - cairo_fill_preserve (cr); - cairo_clip (cr); - - // marker label - cairo_move_to (cr, x0 + w2, y + .5 * (h - lh)); - cairo_set_source_rgb (cr, 0, 0, 0); - pango_cairo_show_layout (cr, _layout->gobj()); - cairo_restore (cr); - } - - // draw marker on top + // draw marker first cairo_move_to (cr, x0 - .5, y + .5); cairo_rel_line_to (cr, -w2 , 0); cairo_rel_line_to (cr, 0, h0); @@ -382,14 +366,67 @@ MiniTimeline::draw_mark (cairo_t* cr, int x0, int x1, const std::string& label, cairo_rel_line_to (cr, w2, -h1); cairo_rel_line_to (cr, 0, -h0); cairo_close_path (cr); - cairo_set_source_rgba (cr, r, g, b, 1.0); + set_source_rgba (cr, HSV (color).darker (0.35).color ()); cairo_set_line_width (cr, 1.0); cairo_stroke_preserve (cr); cairo_fill (cr); + if (rw < x0) { + rw = x1; + } else { + cairo_save (cr); + set_source_rgba (cr, HSV (color).darker (0.35).color ()); + cairo_rectangle (cr, x0-5*scale, y, rw - x0 + 4*scale, h); + cairo_fill_preserve (cr); + cairo_clip (cr); + + // marker label + cairo_move_to (cr, x0 + w2 - 4*scale, y + .5 * (h - lh)); + cairo_set_source_rgb (cr, 0, 0, 0); + pango_cairo_show_layout (cr, _layout->gobj()); + cairo_restore (cr); + + /* right line */ + cairo_rectangle (cr, rw-2*scale, y, 1*scale, h); + cairo_set_source_rgba (cr, 0, 0, 0, 0.7); + cairo_fill (cr); + } + return rw; } +void +MiniTimeline::draw_cue (cairo_t* cr, int x0, int x1, int cue_index, bool& prelight) +{ + int y_offs = _marker_height * 1.4; //make room for 'regular' markers + + int h = _marker_height; + x0 -= h/2; //left side of circle + x1 = x0 + h; //right side of circle + + if (_pointer_y >= y_offs && _pointer_y <= y_offs+h && _pointer_x >= x0 && _pointer_x <= x1) { + prelight = true; + } + + const double scale = UIConfiguration::instance ().get_ui_scale (); + uint32_t color = UIConfiguration::instance().color ( + prelight ? "entered marker" : "location marker"); + + // draw Cue as a circle + cairo_arc(cr, x0+h/2, y_offs+h/2, (h/2)-1*scale, 0, 2*M_PI); + set_source_rgba (cr, HSV (color).darker (0.35).color ()); + cairo_fill (cr); + + //draw cue letter + _layout->set_text (string_compose (_("%1"), (char) ('A' + cue_index))); + cairo_set_source_rgb (cr, 0, 0, 0); //black + cairo_move_to (cr, x0 + h/2, y_offs+h/2); //move to center of circle + int tw, th; + _layout->get_pixel_size (tw, th); + cairo_rel_move_to (cr, -tw/2, -th/2); //move to top-left of text + pango_cairo_show_layout (cr, _layout->gobj()); +} + int MiniTimeline::draw_edge (cairo_t* cr, int x0, int x1, bool left, const std::string& label, bool& prelight) { @@ -475,8 +512,9 @@ MiniTimeline::draw_edge (cairo_t* cr, int x0, int x1, bool left, const std::stri struct LocationMarker { - LocationMarker (const std::string& l, Temporal::timepos_t const & w) - : label (l), when (w) {} + LocationMarker (int idx, const std::string& l, Temporal::timepos_t const & w) + : cue_index(idx), label (l), when (w) {} + int cue_index; std::string label; Temporal::timepos_t when; }; @@ -518,13 +556,13 @@ MiniTimeline::render (Cairo::RefPtr const& ctx, cairo_rectangle_ } /* time */ - const samplepos_t p = _last_update_sample; - const samplepos_t lower = (std::max ((samplepos_t)0, (p - _time_span_samples)) / _time_granularity) * _time_granularity; + const samplepos_t phead = _last_update_sample; //playhead location + const samplepos_t lower = (std::max ((samplepos_t)0, (phead - _time_span_samples)) / _time_granularity) * _time_granularity; - int dot_left = width * .5 + (lower - p) * _px_per_sample; + int dot_left = width * .5 + (lower - phead) * _px_per_sample; for (int i = 0; i < 2 + _n_labels; ++i) { samplepos_t when = lower + i * _time_granularity; - double xpos = width * .5 + (when - p) * _px_per_sample; + double xpos = width * .5 + (when - phead) * _px_per_sample; // TODO round to nearest display TC in +/- 1px // prefer to display BBT |0 or .0 @@ -545,9 +583,24 @@ MiniTimeline::render (Cairo::RefPtr const& ctx, cairo_rectangle_ } draw_dots (cr, dot_left, width, height - PADDING - _time_height * .5, text); + /* playhead beneath locations */ + int xc = width * 0.5f; + cairo_set_line_width (cr, 1.0); + double r,g,b,a; Gtkmm2ext::color_to_rgba(_phead_color, r,g,b,a); + cairo_set_source_rgb (cr, r,g,b); // playhead color + cairo_move_to (cr, xc - .5, 0); + cairo_rel_line_to (cr, 0, height); + cairo_stroke (cr); + cairo_move_to (cr, xc - .5, height); + cairo_rel_line_to (cr, -3, 0); + cairo_rel_line_to (cr, 3, -4); + cairo_rel_line_to (cr, 3, 4); + cairo_close_path (cr); + cairo_fill (cr); + /* locations */ - samplepos_t lmin = std::max ((samplepos_t)0, (p - _time_span_samples)); - samplepos_t lmax = p + _time_span_samples; + samplepos_t lmin = std::max ((samplepos_t)0, (phead - _time_span_samples)); + samplepos_t lmax = phead + _time_span_samples; int tw, th; _layout->set_text (X_("Marker@")); @@ -565,8 +618,8 @@ MiniTimeline::render (Cairo::RefPtr const& ctx, cairo_rectangle_ const Locations::LocationList& ll (_session->locations ()->list ()); for (Locations::LocationList::const_iterator l = ll.begin(); l != ll.end(); ++l) { if ((*l)->is_session_range ()) { - lm.push_back (LocationMarker(_("start"), (*l)->start ())); - lm.push_back (LocationMarker(_("end"), (*l)->end ())); + lm.push_back (LocationMarker(-1, _("start"), (*l)->start ())); + lm.push_back (LocationMarker(-1, _("end"), (*l)->end ())); continue; } @@ -574,7 +627,8 @@ MiniTimeline::render (Cairo::RefPtr const& ctx, cairo_rectangle_ continue; } - lm.push_back (LocationMarker((*l)->name(), (*l)->start ())); + int cue_idx = (*l)->is_cue_marker () ? (*l)->cue_id() : -1; + lm.push_back (LocationMarker(cue_idx, (*l)->name(), (*l)->start ())); } _jumplist.clear (); @@ -593,7 +647,7 @@ MiniTimeline::render (Cairo::RefPtr const& ctx, cairo_rectangle_ if (when < lmin) { outside_left = l; if (++l != lm.end()) { - left_limit = floor (width * .5 + (when - p) * _px_per_sample) - 1 - mw; + left_limit = floor (width * .5 + (when - phead) * _px_per_sample) - 1 - mw; } else { left_limit = width * .5 - mw; } @@ -603,16 +657,31 @@ MiniTimeline::render (Cairo::RefPtr const& ctx, cairo_rectangle_ outside_right = l; break; } - int x0 = floor (width * .5 + (when - p) * _px_per_sample); + int x0 = floor (width * .5 + (when - phead) * _px_per_sample); int x1 = width; const std::string& label = (*l).label; - if (++l != lm.end()) { - x1 = floor (width * .5 + (when - p) * _px_per_sample) - 1 - mw; + const int cue_index = (*l).cue_index; + + std::vector::const_iterator peek = l; + for (peek++; peek != lm.end(); peek++) { + if ((*peek).cue_index == -1) { + x1 = floor (width * .5 + ((*peek).when.samples() - phead) * _px_per_sample) - 1 - mw; + break; + } } + + //draw the mark bool prelight = false; - x1 = draw_mark (cr, x0, x1, label, prelight); - _jumplist.push_back (JumpRange (x0 - mw, x1, when, prelight)); + if (cue_index >= 0) { + draw_cue (cr, x0, x1, cue_index, prelight); + } else { + x1 = draw_mark (cr, x0, x1, label, prelight); + } + + _jumplist.push_back (JumpRange (when, prelight)); right_limit = std::max (x1, right_limit); + + l++; } if (outside_left != lm.end ()) { @@ -622,7 +691,7 @@ MiniTimeline::render (Cairo::RefPtr const& ctx, cairo_rectangle_ bool prelight = false; x1 = draw_edge (cr, x0, x1, true, (*outside_left).label, prelight); if (x0 != x1) { - _jumplist.push_back (JumpRange (x0, x1, (*outside_left).when.samples(), prelight)); + _jumplist.push_back (JumpRange ((*outside_left).when.samples(), prelight)); right_limit = std::max (x1, right_limit); } } @@ -635,27 +704,12 @@ MiniTimeline::render (Cairo::RefPtr const& ctx, cairo_rectangle_ bool prelight = false; x0 = draw_edge (cr, x0, x1, false, (*outside_right).label, prelight); if (x0 != x1) { - _jumplist.push_back (JumpRange (x0, x1, (*outside_right).when.samples(), prelight)); + _jumplist.push_back (JumpRange ((*outside_right).when.samples(), prelight)); } } } - /* playhead on top */ - int xc = width * 0.5f; - cairo_set_line_width (cr, 1.0); - double r,g,b,a; Gtkmm2ext::color_to_rgba(_phead_color, r,g,b,a); - cairo_set_source_rgb (cr, r,g,b); // playhead color - cairo_move_to (cr, xc - .5, 0); - cairo_rel_line_to (cr, 0, height); - cairo_stroke (cr); - cairo_move_to (cr, xc - .5, height); - cairo_rel_line_to (cr, -3, 0); - cairo_rel_line_to (cr, 3, -4); - cairo_rel_line_to (cr, 3, 4); - cairo_close_path (cr); - cairo_fill (cr); - cairo_pop_group_to_source (cr); cairo_paint (cr); } @@ -711,16 +765,17 @@ MiniTimeline::on_button_release_event (GdkEventButton *ev) { if (!_session) { return true; } if (_session->actively_recording ()) { return true; } + + /* check that the release is still inside the timeline */ if (ev->y < 0 || ev->y > get_height () || ev->x < 0 || ev->x > get_width ()) { return true; } - if (ev->y <= PADDING + _marker_height) { - for (JumpList::const_iterator i = _jumplist.begin (); i != _jumplist.end(); ++i) { - if (i->left <= ev->x && ev->x <= i->right) { - _session->request_locate (i->to); - return true; - } + /* check whether any marker was prelighted; if so, that's where the user will expect to jump */ + for (JumpList::const_iterator i = _jumplist.begin (); i != _jumplist.end(); ++i) { + if (i->prelight) { + _session->request_locate (i->to); + return true; } } @@ -743,22 +798,7 @@ MiniTimeline::on_motion_notify_event (GdkEventMotion *ev) bool need_expose = false; - for (JumpList::const_iterator i = _jumplist.begin (); i != _jumplist.end(); ++i) { - if (i->left < ev->x && ev->x < i->right && ev->y <= PADDING + _marker_height) { - if (!(*i).prelight) { - need_expose = true; - break; - } - } else { - if ((*i).prelight) { - need_expose = true; - break; - } - } - } - if (need_expose) { - update_minitimeline (); - } + update_minitimeline (); return true; } diff --git a/gtk2_ardour/mini_timeline.h b/gtk2_ardour/mini_timeline.h index 5ab3551828..65a8743ee3 100644 --- a/gtk2_ardour/mini_timeline.h +++ b/gtk2_ardour/mini_timeline.h @@ -66,6 +66,7 @@ private: void update_minitimeline (); void draw_dots (cairo_t*, int left, int right, int y, Gtkmm2ext::Color); int draw_mark (cairo_t*, int x0, int x1, const std::string&, bool& prelight); + void draw_cue (cairo_t*, int x0, int x1, int cue_index, bool& prelight); int draw_edge (cairo_t*, int x0, int x1, bool left, const std::string&, bool& prelight); void render (Cairo::RefPtr const&, cairo_rectangle_t*); @@ -83,6 +84,7 @@ private: Glib::RefPtr _layout; sigc::connection super_rapid_connection; PBD::ScopedConnectionList marker_connection; + PBD::ScopedConnectionList tempo_map_connection; PBD::ScopedConnectionList session_connection; samplepos_t _last_update_sample; @@ -104,10 +106,8 @@ private: uint32_t _phead_color; struct JumpRange { - JumpRange (int l, int r, samplepos_t t, bool p = false) - : left (l), right (r), to (t), prelight (p) {} - int left; - int right; + JumpRange (samplepos_t t, bool p = false) + : to (t), prelight (p) {} samplepos_t to; bool prelight; };