Added a mouse chevron to the ruler
This commit is contained in:
parent
7752130d99
commit
8d63d7adb4
7 changed files with 184 additions and 36 deletions
|
|
@ -136,6 +136,7 @@ style "timeline_ruler" = "default_base"
|
||||||
gtkmm__CustomObject_TimelineRuler::annotation_horz_margin = 3
|
gtkmm__CustomObject_TimelineRuler::annotation_horz_margin = 3
|
||||||
gtkmm__CustomObject_TimelineRuler::annotation_vert_margin = 0
|
gtkmm__CustomObject_TimelineRuler::annotation_vert_margin = 0
|
||||||
gtkmm__CustomObject_TimelineRuler::min_division_width = 100
|
gtkmm__CustomObject_TimelineRuler::min_division_width = 100
|
||||||
|
gtkmm__CustomObject_TimelineRuler::mouse_chevron_size = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
style "timeline_header_base" = "default_base"
|
style "timeline_header_base" = "default_base"
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,12 @@ TimelineWidget::zoom_view(int point, int zoom_size)
|
||||||
set_time_scale(new_time_scale);
|
set_time_scale(new_time_scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineWidget::on_mouse_move_in_body(int x, int y)
|
||||||
|
{
|
||||||
|
ruler.set_mouse_chevron_time(x * timeScale + timeOffset);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace widgets
|
} // namespace widgets
|
||||||
} // namespace gui
|
} // namespace gui
|
||||||
} // namespace lumiera
|
} // namespace lumiera
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,8 @@ protected:
|
||||||
|
|
||||||
void zoom_view(int point, int zoom_size);
|
void zoom_view(int point, int zoom_size);
|
||||||
|
|
||||||
|
void on_mouse_move_in_body(int x, int y);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
gavl_time_t timeOffset;
|
gavl_time_t timeOffset;
|
||||||
int64_t timeScale;
|
int64_t timeScale;
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,15 @@ TimelineBody::TimelineBody(lumiera::gui::widgets::TimelineWidget *timeline_widge
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineBody::on_realize()
|
||||||
|
{
|
||||||
|
Widget::on_realize();
|
||||||
|
|
||||||
|
// We wish to receive all event notifications
|
||||||
|
add_events(Gdk::POINTER_MOTION_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
TimelineBody::on_scroll()
|
TimelineBody::on_scroll()
|
||||||
{
|
{
|
||||||
|
|
@ -69,6 +78,7 @@ TimelineBody::on_scroll()
|
||||||
bool
|
bool
|
||||||
TimelineBody::on_scroll_event (GdkEventScroll* event)
|
TimelineBody::on_scroll_event (GdkEventScroll* event)
|
||||||
{
|
{
|
||||||
|
REQUIRE(event != NULL);
|
||||||
REQUIRE(timelineWidget != NULL);
|
REQUIRE(timelineWidget != NULL);
|
||||||
|
|
||||||
if(event->state & GDK_CONTROL_MASK)
|
if(event->state & GDK_CONTROL_MASK)
|
||||||
|
|
@ -103,18 +113,23 @@ TimelineBody::on_scroll_event (GdkEventScroll* event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
TimelineBody::on_realize()
|
TimelineBody::on_motion_notify_event(GdkEventMotion *event)
|
||||||
{
|
{
|
||||||
Widget::on_realize();
|
REQUIRE(event != NULL);
|
||||||
|
REQUIRE(timelineWidget != NULL);
|
||||||
|
|
||||||
// We wish to receive all event notifications
|
timelineWidget->on_mouse_move_in_body(event->x, event->y);
|
||||||
add_events(Gdk::ALL_EVENTS_MASK);
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
TimelineBody::on_expose_event(GdkEventExpose* event)
|
TimelineBody::on_expose_event(GdkEventExpose* event)
|
||||||
{
|
{
|
||||||
|
REQUIRE(event != NULL);
|
||||||
|
REQUIRE(timelineWidget != NULL);
|
||||||
|
|
||||||
// This is where we draw on the window
|
// This is where we draw on the window
|
||||||
Glib::RefPtr<Gdk::Window> window = get_window();
|
Glib::RefPtr<Gdk::Window> window = get_window();
|
||||||
if(!window)
|
if(!window)
|
||||||
|
|
@ -128,6 +143,9 @@ TimelineBody::on_expose_event(GdkEventExpose* event)
|
||||||
Gtk::Allocation allocation = get_allocation();
|
Gtk::Allocation allocation = get_allocation();
|
||||||
Cairo::RefPtr<Cairo::Context> cairo = window->create_cairo_context();
|
Cairo::RefPtr<Cairo::Context> cairo = window->create_cairo_context();
|
||||||
|
|
||||||
|
REQUIRE(style);
|
||||||
|
REQUIRE(cairo);
|
||||||
|
|
||||||
// Translate the view by the scroll distance
|
// Translate the view by the scroll distance
|
||||||
cairo->translate(0,
|
cairo->translate(0,
|
||||||
-(int)timelineWidget->verticalAdjustment.get_value());
|
-(int)timelineWidget->verticalAdjustment.get_value());
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,13 @@ class TimelineBody : public Gtk::DrawingArea
|
||||||
|
|
||||||
/* ===== Events ===== */
|
/* ===== Events ===== */
|
||||||
protected:
|
protected:
|
||||||
|
void on_realize();
|
||||||
|
|
||||||
void on_scroll();
|
void on_scroll();
|
||||||
|
|
||||||
bool on_scroll_event (GdkEventScroll* event);
|
bool on_scroll_event(GdkEventScroll* event);
|
||||||
|
|
||||||
void on_realize();
|
bool on_motion_notify_event(GdkEventMotion *event);
|
||||||
|
|
||||||
bool on_expose_event(GdkEventExpose* event);
|
bool on_expose_event(GdkEventExpose* event);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ extern "C" {
|
||||||
}
|
}
|
||||||
|
|
||||||
using namespace Gtk;
|
using namespace Gtk;
|
||||||
|
using namespace Cairo;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace lumiera::gui;
|
using namespace lumiera::gui;
|
||||||
using namespace lumiera::gui::widgets;
|
using namespace lumiera::gui::widgets;
|
||||||
|
|
@ -44,15 +45,15 @@ TimelineRuler::TimelineRuler() :
|
||||||
Glib::ObjectBase("TimelineRuler"),
|
Glib::ObjectBase("TimelineRuler"),
|
||||||
timeOffset(0),
|
timeOffset(0),
|
||||||
timeScale(1),
|
timeScale(1),
|
||||||
|
mouseChevronTime(0),
|
||||||
annotationHorzMargin(0),
|
annotationHorzMargin(0),
|
||||||
annotationVertMargin(0),
|
annotationVertMargin(0),
|
||||||
majorTickHeight(0),
|
majorTickHeight(0),
|
||||||
minorLongTickHeight(0),
|
minorLongTickHeight(0),
|
||||||
minorShortTickHeight(0),
|
minorShortTickHeight(0),
|
||||||
minDivisionWidth(100)
|
minDivisionWidth(100),
|
||||||
|
mouseChevronSize(0)
|
||||||
{
|
{
|
||||||
set_flags(Gtk::NO_WINDOW); // This widget will not have a window
|
|
||||||
|
|
||||||
// Install style properties
|
// Install style properties
|
||||||
register_styles();
|
register_styles();
|
||||||
}
|
}
|
||||||
|
|
@ -61,6 +62,7 @@ void
|
||||||
TimelineRuler::set_time_offset(gavl_time_t time_offset)
|
TimelineRuler::set_time_offset(gavl_time_t time_offset)
|
||||||
{
|
{
|
||||||
timeOffset = time_offset;
|
timeOffset = time_offset;
|
||||||
|
rulerImage.clear();
|
||||||
queue_draw();
|
queue_draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,6 +71,14 @@ TimelineRuler::set_time_scale(int64_t time_scale)
|
||||||
{
|
{
|
||||||
REQUIRE(time_scale > 0);
|
REQUIRE(time_scale > 0);
|
||||||
timeScale = time_scale;
|
timeScale = time_scale;
|
||||||
|
rulerImage.clear();
|
||||||
|
queue_draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineRuler::set_mouse_chevron_time(gavl_time_t time)
|
||||||
|
{
|
||||||
|
mouseChevronTime = time;
|
||||||
queue_draw();
|
queue_draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,6 +87,9 @@ TimelineRuler::on_realize()
|
||||||
{
|
{
|
||||||
Widget::on_realize();
|
Widget::on_realize();
|
||||||
|
|
||||||
|
// Set event notifications
|
||||||
|
add_events(Gdk::POINTER_MOTION_MASK);
|
||||||
|
|
||||||
// Load styles
|
// Load styles
|
||||||
read_styles();
|
read_styles();
|
||||||
}
|
}
|
||||||
|
|
@ -84,30 +97,99 @@ TimelineRuler::on_realize()
|
||||||
bool
|
bool
|
||||||
TimelineRuler::on_expose_event(GdkEventExpose* event)
|
TimelineRuler::on_expose_event(GdkEventExpose* event)
|
||||||
{
|
{
|
||||||
|
REQUIRE(event != NULL);
|
||||||
|
|
||||||
// This is where we draw on the window
|
// This is where we draw on the window
|
||||||
Glib::RefPtr<Gdk::Window> window = get_window();
|
Glib::RefPtr<Gdk::Window> window = get_window();
|
||||||
if(!window)
|
if(!window)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Prepare to render via cairo
|
// Prepare to render via cairo
|
||||||
Allocation allocation = get_allocation();
|
const Allocation allocation = get_allocation();
|
||||||
const int height = allocation.get_height();
|
|
||||||
Glib::RefPtr<Style> style = get_style();
|
|
||||||
Cairo::RefPtr<Cairo::Context> cairo = window->create_cairo_context();
|
|
||||||
Glib::RefPtr<Pango::Layout> pango_layout = create_pango_layout("");
|
|
||||||
|
|
||||||
cairo->translate(allocation.get_x(), allocation.get_y());
|
Cairo::RefPtr<Context> cairo = window->create_cairo_context();
|
||||||
|
REQUIRE(cairo);
|
||||||
|
|
||||||
|
// Draw the ruler
|
||||||
|
if(!rulerImage)
|
||||||
|
{
|
||||||
|
// We have no cached rendering - it must be redrawn
|
||||||
|
// but do we need ro allocate a new image?
|
||||||
|
if(!rulerImage ||
|
||||||
|
rulerImage->get_width() != allocation.get_width() ||
|
||||||
|
rulerImage->get_height() != allocation.get_height())
|
||||||
|
rulerImage = ImageSurface::create(FORMAT_RGB24,
|
||||||
|
allocation.get_width(), allocation.get_height());
|
||||||
|
|
||||||
|
ENSURE(rulerImage);
|
||||||
|
|
||||||
|
Cairo::RefPtr<Context> image_cairo = Context::create(rulerImage);
|
||||||
|
ENSURE(image_cairo);
|
||||||
|
|
||||||
|
draw_ruler(image_cairo, allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the cached ruler image
|
||||||
|
cairo->set_source(rulerImage, 0, 0);
|
||||||
|
cairo->paint();
|
||||||
|
|
||||||
|
// Draw the mouse chevron
|
||||||
|
draw_mouse_chevron(cairo, allocation);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
TimelineRuler::on_motion_notify_event(GdkEventMotion *event)
|
||||||
|
{
|
||||||
|
REQUIRE(event != NULL);
|
||||||
|
|
||||||
|
set_mouse_chevron_time(event->x * timeScale + timeOffset);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineRuler::on_size_request (Gtk::Requisition *requisition)
|
||||||
|
{
|
||||||
|
REQUIRE(requisition != NULL);
|
||||||
|
|
||||||
|
// Initialize the output parameter
|
||||||
|
*requisition = Gtk::Requisition();
|
||||||
|
|
||||||
|
requisition->width = 0;
|
||||||
|
get_style_property("height", requisition->height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineRuler::on_size_allocate(Gtk::Allocation& allocation)
|
||||||
|
{
|
||||||
|
Widget::on_size_allocate(allocation);
|
||||||
|
rulerImage.clear(); // The widget has changed size - redraw
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineRuler::draw_ruler(Cairo::RefPtr<Cairo::Context> cairo,
|
||||||
|
Gdk::Rectangle ruler_rect)
|
||||||
|
{
|
||||||
|
REQUIRE(cairo);
|
||||||
|
REQUIRE(ruler_rect.get_width() > 0);
|
||||||
|
REQUIRE(ruler_rect.get_height() > 0);
|
||||||
|
|
||||||
|
// Preparation steps
|
||||||
|
const int height = ruler_rect.get_height();
|
||||||
|
Glib::RefPtr<Pango::Layout> pango_layout = create_pango_layout("");
|
||||||
|
Glib::RefPtr<Style> style = get_style();
|
||||||
|
|
||||||
// Render the background, and clip inside the area
|
// Render the background, and clip inside the area
|
||||||
Gdk::Cairo::set_source_color(cairo, style->get_bg(STATE_NORMAL));
|
Gdk::Cairo::set_source_color(cairo, style->get_bg(STATE_NORMAL));
|
||||||
cairo->rectangle(0, 0,
|
cairo->rectangle(0, 0,
|
||||||
allocation.get_width(), allocation.get_height());
|
ruler_rect.get_width(), ruler_rect.get_height());
|
||||||
cairo->fill_preserve();
|
cairo->fill_preserve();
|
||||||
cairo->clip();
|
cairo->clip();
|
||||||
|
|
||||||
// Make sure we don't have impossible zoom
|
// Make sure we don't have impossible zoom
|
||||||
if(timeScale <= 0)
|
if(timeScale <= 0)
|
||||||
return true;
|
return;
|
||||||
|
|
||||||
// Render ruler annotations
|
// Render ruler annotations
|
||||||
Gdk::Cairo::set_source_color(cairo, style->get_fg(STATE_NORMAL));
|
Gdk::Cairo::set_source_color(cairo, style->get_fg(STATE_NORMAL));
|
||||||
|
|
@ -115,8 +197,10 @@ TimelineRuler::on_expose_event(GdkEventExpose* event)
|
||||||
const gavl_time_t major_spacing = calculate_major_spacing();
|
const gavl_time_t major_spacing = calculate_major_spacing();
|
||||||
const gavl_time_t minor_spacing = major_spacing / 10;
|
const gavl_time_t minor_spacing = major_spacing / 10;
|
||||||
|
|
||||||
|
int64_t time_offset = timeOffset - timeOffset % major_spacing;
|
||||||
|
if(timeOffset < 0)
|
||||||
|
time_offset -= major_spacing;
|
||||||
|
|
||||||
int64_t time_offset = timeOffset - timeOffset % minor_spacing;
|
|
||||||
int x = 0;
|
int x = 0;
|
||||||
const int64_t x_offset = timeOffset / timeScale;
|
const int64_t x_offset = timeOffset / timeScale;
|
||||||
|
|
||||||
|
|
@ -153,19 +237,30 @@ TimelineRuler::on_expose_event(GdkEventExpose* event)
|
||||||
|
|
||||||
time_offset += minor_spacing;
|
time_offset += minor_spacing;
|
||||||
}
|
}
|
||||||
while(x < allocation.get_width());
|
while(x < ruler_rect.get_width());
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
TimelineRuler::on_size_request (Gtk::Requisition *requisition)
|
TimelineRuler::draw_mouse_chevron(Cairo::RefPtr<Cairo::Context> cairo,
|
||||||
|
Gdk::Rectangle ruler_rect)
|
||||||
{
|
{
|
||||||
// Initialize the output parameter
|
REQUIRE(cairo);
|
||||||
*requisition = Gtk::Requisition();
|
REQUIRE(ruler_rect.get_width() > 0);
|
||||||
|
REQUIRE(ruler_rect.get_height() > 0);
|
||||||
|
|
||||||
requisition->width = 0;
|
// Set the source colour
|
||||||
get_style_property("height", requisition->height);
|
Glib::RefPtr<Style> style = get_style();
|
||||||
|
Gdk::Cairo::set_source_color(cairo, style->get_fg(STATE_NORMAL));
|
||||||
|
|
||||||
|
const int x = (mouseChevronTime - timeOffset) / timeScale;
|
||||||
|
cairo->move_to(x + 0.5,
|
||||||
|
ruler_rect.get_height());
|
||||||
|
cairo->line_to(x + mouseChevronSize + 0.5,
|
||||||
|
ruler_rect.get_height() - mouseChevronSize);
|
||||||
|
cairo->line_to(x - mouseChevronSize + 0.5,
|
||||||
|
ruler_rect.get_height() - mouseChevronSize);
|
||||||
|
|
||||||
|
cairo->fill();
|
||||||
}
|
}
|
||||||
|
|
||||||
gavl_time_t
|
gavl_time_t
|
||||||
|
|
@ -256,6 +351,12 @@ TimelineRuler::register_styles() const
|
||||||
"Minimum Division Width",
|
"Minimum Division Width",
|
||||||
"The minimum distance in pixels that two major division may approach.",
|
"The minimum distance in pixels that two major division may approach.",
|
||||||
0, G_MAXINT, 100, G_PARAM_READABLE));
|
0, G_MAXINT, 100, G_PARAM_READABLE));
|
||||||
|
|
||||||
|
gtk_widget_class_install_style_property(klass,
|
||||||
|
g_param_spec_int("mouse_chevron_size",
|
||||||
|
"Mouse Chevron Size",
|
||||||
|
"The height of the mouse chevron in pixels.",
|
||||||
|
0, G_MAXINT, 5, G_PARAM_READABLE));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -267,6 +368,7 @@ TimelineRuler::read_styles()
|
||||||
get_style_property("minor_long_tick_height", minorLongTickHeight);
|
get_style_property("minor_long_tick_height", minorLongTickHeight);
|
||||||
get_style_property("minor_short_tick_height", minorShortTickHeight);
|
get_style_property("minor_short_tick_height", minorShortTickHeight);
|
||||||
get_style_property("min_division_width", minDivisionWidth);
|
get_style_property("min_division_width", minDivisionWidth);
|
||||||
|
get_style_property("mouse_chevron_size", mouseChevronSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace timeline
|
} // namespace timeline
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ namespace gui {
|
||||||
namespace widgets {
|
namespace widgets {
|
||||||
namespace timeline {
|
namespace timeline {
|
||||||
|
|
||||||
class TimelineRuler : public Gtk::Widget
|
class TimelineRuler : public Gtk::DrawingArea
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TimelineRuler();
|
TimelineRuler();
|
||||||
|
|
@ -53,6 +53,8 @@ public:
|
||||||
*/
|
*/
|
||||||
void set_time_scale(int64_t time_scale);
|
void set_time_scale(int64_t time_scale);
|
||||||
|
|
||||||
|
void set_mouse_chevron_time(gavl_time_t time);
|
||||||
|
|
||||||
/* ===== Events ===== */
|
/* ===== Events ===== */
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
|
@ -60,10 +62,20 @@ protected:
|
||||||
|
|
||||||
bool on_expose_event(GdkEventExpose *event);
|
bool on_expose_event(GdkEventExpose *event);
|
||||||
|
|
||||||
void on_size_request (Gtk::Requisition *requisition);
|
bool on_motion_notify_event(GdkEventMotion *event);
|
||||||
|
|
||||||
|
void on_size_request(Gtk::Requisition *requisition);
|
||||||
|
|
||||||
|
void on_size_allocate(Gtk::Allocation& allocation);
|
||||||
|
|
||||||
/* ===== Internals ===== */
|
/* ===== Internals ===== */
|
||||||
private:
|
private:
|
||||||
|
void draw_ruler(Cairo::RefPtr<Cairo::Context> cairo,
|
||||||
|
Gdk::Rectangle ruler_rect);
|
||||||
|
|
||||||
|
void draw_mouse_chevron(Cairo::RefPtr<Cairo::Context> cairo,
|
||||||
|
Gdk::Rectangle ruler_rect);
|
||||||
|
|
||||||
gavl_time_t calculate_major_spacing() const;
|
gavl_time_t calculate_major_spacing() const;
|
||||||
|
|
||||||
void register_styles() const;
|
void register_styles() const;
|
||||||
|
|
@ -74,6 +86,7 @@ private:
|
||||||
// View values
|
// View values
|
||||||
gavl_time_t timeOffset;
|
gavl_time_t timeOffset;
|
||||||
int64_t timeScale;
|
int64_t timeScale;
|
||||||
|
int mouseChevronTime;
|
||||||
|
|
||||||
// Style values
|
// Style values
|
||||||
int annotationHorzMargin;
|
int annotationHorzMargin;
|
||||||
|
|
@ -82,6 +95,10 @@ private:
|
||||||
int minorLongTickHeight;
|
int minorLongTickHeight;
|
||||||
int minorShortTickHeight;
|
int minorShortTickHeight;
|
||||||
int minDivisionWidth;
|
int minDivisionWidth;
|
||||||
|
int mouseChevronSize;
|
||||||
|
|
||||||
|
// Cached ruler image
|
||||||
|
Cairo::RefPtr<Cairo::ImageSurface> rulerImage;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace timeline
|
} // namespace timeline
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue