Mini Timeline: Add Circular Cue Markers; various cleanup and pixel-pushing tweaks

This commit is contained in:
Ben Loftis 2022-01-19 17:17:47 -06:00
parent 90e6107972
commit 16c809986d
2 changed files with 128 additions and 88 deletions

View File

@ -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 (_("<b>Navigation Timeline</b>. 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<Cairo::Context> 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<Cairo::Context> 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<Cairo::Context> 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<Cairo::Context> 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<Cairo::Context> 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<Cairo::Context> 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<LocationMarker>::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<Cairo::Context> 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<Cairo::Context> 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;
}

View File

@ -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<Cairo::Context> const&, cairo_rectangle_t*);
@ -83,6 +84,7 @@ private:
Glib::RefPtr<Pango::Layout> _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;
};