Mini Timeline: Add Circular Cue Markers; various cleanup and pixel-pushing tweaks
This commit is contained in:
parent
90e6107972
commit
16c809986d
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user