Basic snapping in the timeline.
authorCarl Hetherington <cth@carlh.net>
Thu, 10 Oct 2013 14:00:41 +0000 (15:00 +0100)
committerCarl Hetherington <cth@carlh.net>
Thu, 10 Oct 2013 14:00:41 +0000 (15:00 +0100)
ChangeLog
src/wx/timeline.cc
src/wx/timeline.h
src/wx/timeline_dialog.cc
src/wx/timeline_dialog.h

index 2599b9b97fc626482c2111a5682dbc5e973454e6..d741a3a2e8350c903d93e2bf62c812e2b1b7bf6b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,7 @@
 2013-10-10  Carl Hetherington  <cth@carlh.net>
 
+       * Basic snapping in the timeline.
+
        * Various improvements to dcpomatic_kdm.
 
 2013-10-10  Carl Hetherington  <cth@carlh.net>
index 87070b35edb485d0003f5a971590d349a38a87e3..1f0aee4e8528be86bf5020a760532ed98be85882 100644 (file)
@@ -33,7 +33,9 @@ using boost::shared_ptr;
 using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
 using boost::bind;
+using boost::optional;
 
+/** Parent class for components of the timeline (e.g. a piece of content or an axis) */
 class View : public boost::noncopyable
 {
 public:
@@ -332,6 +334,7 @@ Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film)
        , _down_view_position (0)
        , _first_move (false)
        , _menu (film, this)
+       , _snap (true)
 {
 #ifndef __WXOSX__
        SetDoubleBuffered (true);
@@ -577,6 +580,9 @@ Timeline::set_position_from_event (wxMouseEvent& ev)
        wxPoint const p = ev.GetPosition();
 
        if (!_first_move) {
+               /* We haven't moved yet; in that case, we must move the mouse some reasonable distance
+                  before the drag is considered to have started.
+               */
                int const dist = sqrt (pow (p.x - _down_point.x, 2) + pow (p.y - _down_point.y, 2));
                if (dist < 8) {
                        return;
@@ -584,9 +590,56 @@ Timeline::set_position_from_event (wxMouseEvent& ev)
                _first_move = true;
        }
 
-       Time const time_diff = (p.x - _down_point.x) / _pixels_per_time_unit;
        if (_down_view) {
-               _down_view->content()->set_position (max (static_cast<Time> (0), _down_view_position + time_diff));
+               Time new_position = _down_view_position + (p.x - _down_point.x) / _pixels_per_time_unit;
+               
+               if (_snap) {
+
+                       bool first = true;
+                       Time nearest_distance = TIME_MAX;
+                       Time nearest_new_position = TIME_MAX;
+                       
+                       /* Find the nearest content edge; this is inefficient */
+                       for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
+                               shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
+                               if (!cv || cv == _down_view) {
+                                       continue;
+                               }
+
+                               {
+                                       /* Snap starts to ends */
+                                       Time const d = abs (cv->content()->end() - new_position);
+                                       if (first || d < nearest_distance) {
+                                               nearest_distance = d;
+                                               nearest_new_position = cv->content()->end();
+                                       }
+                               }
+
+                               {
+                                       /* Snap ends to starts */
+                                       Time const d = abs (cv->content()->position() - (new_position + _down_view->content()->length_after_trim()));
+                                       if (d < nearest_distance) {
+                                               nearest_distance = d;
+                                               nearest_new_position = cv->content()->position() - _down_view->content()->length_after_trim ();
+                                       }
+                               }
+
+                               first = false;
+                       }
+
+                       if (!first) {
+                               /* Snap if it's close; `close' means within a proportion of the time on the timeline */
+                               if (nearest_distance < (width() / pixels_per_time_unit()) / 32) {
+                                       new_position = nearest_new_position;
+                               }
+                       }
+               }
+               
+               if (new_position < 0) {
+                       new_position = 0;
+               }
+       
+               _down_view->content()->set_position (new_position);
 
                shared_ptr<Film> film = _film.lock ();
                assert (film);
index 0217373b9c3bbbf6a6a6986d77c3f74e1ecc99d7..ef1d10797c6e75d114c9d55d7b5d97ff40777279 100644 (file)
@@ -64,6 +64,14 @@ public:
 
        void setup_pixels_per_time_unit ();
 
+       void set_snap (bool s) {
+               _snap = s;
+       }
+
+       bool snap () const {
+               return _snap;
+       }
+
 private:
        void paint ();
        void left_down (wxMouseEvent &);
@@ -95,6 +103,7 @@ private:
        Time _down_view_position;
        bool _first_move;
        ContentMenu _menu;
+       bool _snap;
 
        boost::signals2::scoped_connection _playlist_connection;
 };
index 9493d0acbcfa2ad02d3c9a7cdac3258a167091a5..dbf7ae232be2cd5f72dd8a247e98a80fff968fa7 100644 (file)
@@ -33,10 +33,24 @@ TimelineDialog::TimelineDialog (FilmEditor* ed, shared_ptr<Film> film)
        , _timeline (this, ed, film)
 {
        wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
-       
+
+       wxBoxSizer* controls = new wxBoxSizer (wxHORIZONTAL);
+       _snap = new wxCheckBox (this, wxID_ANY, _("Snap"));
+       controls->Add (_snap);
+
+       sizer->Add (controls, 0, wxALL, 12);
        sizer->Add (&_timeline, 1, wxEXPAND | wxALL, 12);
 
        SetSizer (sizer);
        sizer->Layout ();
        sizer->SetSizeHints (this);
+
+       _snap->SetValue (_timeline.snap ());
+       _snap->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&TimelineDialog::snap_toggled, this));
+}
+
+void
+TimelineDialog::snap_toggled ()
+{
+       _timeline.set_snap (_snap->GetValue ());
 }
index 17ca22c493281ad8dfc3b3a211e78959c295d96b..1e595500303b5b964b0ba54157c67d47f260625b 100644 (file)
@@ -30,5 +30,8 @@ public:
        TimelineDialog (FilmEditor *, boost::shared_ptr<Film>);
 
 private:
+       void snap_toggled ();
+       
        Timeline _timeline;
+       wxCheckBox* _snap;
 };