diff --git a/app/widget/timelinewidget/CMakeLists.txt b/app/widget/timelinewidget/CMakeLists.txt index 1f29b4d15..293c4c972 100644 --- a/app/widget/timelinewidget/CMakeLists.txt +++ b/app/widget/timelinewidget/CMakeLists.txt @@ -21,6 +21,8 @@ add_subdirectory(view) set(OLIVE_SOURCES ${OLIVE_SOURCES} + widget/timelinewidget/snapservice.h + widget/timelinewidget/snapservice.cpp widget/timelinewidget/timelineandtrackview.h widget/timelinewidget/timelineandtrackview.cpp widget/timelinewidget/timelinescaledobject.h diff --git a/app/widget/timelinewidget/snapservice.cpp b/app/widget/timelinewidget/snapservice.cpp new file mode 100644 index 000000000..b3a853f1e --- /dev/null +++ b/app/widget/timelinewidget/snapservice.cpp @@ -0,0 +1 @@ +#include "snapservice.h" diff --git a/app/widget/timelinewidget/snapservice.h b/app/widget/timelinewidget/snapservice.h new file mode 100644 index 000000000..ad7b09834 --- /dev/null +++ b/app/widget/timelinewidget/snapservice.h @@ -0,0 +1,31 @@ +#ifndef SNAPSERVICE_H +#define SNAPSERVICE_H + +#include "common/rational.h" + +OLIVE_NAMESPACE_ENTER + +class SnapService +{ +public: + SnapService() = default; + + enum SnapPoints { + kSnapToClips = 0x1, + kSnapToPlayhead = 0x2, + kSnapToMarkers = 0x4, + kSnapAll = 0xFF + }; + + /** + * @brief Snaps point `start_point` that is moving by `movement` to currently existing clips + */ + virtual bool SnapPoint(QList start_times, rational *movement, int snap_points = kSnapAll) = 0; + + virtual void HideSnaps() = 0; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // SNAPSERVICE_H diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 9c0b0e35d..f7e2f8819 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -20,11 +20,13 @@ #include "timelinewidget.h" +#include #include #include #include #include "core.h" +#include "common/range.h" #include "common/timecodefunctions.h" #include "dialog/sequence/sequence.h" #include "dialog/speedduration/speedduration.h" @@ -56,6 +58,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) : ruler_and_time_layout->addWidget(timecode_label_); ruler_and_time_layout->addWidget(ruler()); + ruler()->SetSnapService(this); // Create list of TimelineViews - these MUST correspond to the ViewType enum @@ -103,6 +106,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) : view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + view->SetSnapService(this); view_splitter->addWidget(tview); @@ -906,6 +910,8 @@ void TimelineWidget::ClearGhosts() ghost_items_.clear(); } + + HideSnaps(); } bool TimelineWidget::HasGhosts() @@ -1266,7 +1272,7 @@ void TimelineWidget::SetBlockLinksSelected(Block* block, bool selected) } QVector TimelineWidget::GetEditToInfo(const rational& playhead_time, - Timeline::MovementMode mode) + Timeline::MovementMode mode) { // Get list of unlocked tracks QVector tracks = GetConnectedNode()->GetUnlockedTracks(); @@ -1399,6 +1405,20 @@ void TimelineWidget::EditTo(Timeline::MovementMode mode) Core::instance()->undo_stack()->pushIfHasChildren(command); } +void TimelineWidget::ShowSnap(const QList ×) +{ + foreach (TimelineAndTrackView* tview, views_) { + tview->view()->EnableSnap(times); + } +} + +void TimelineWidget::HideSnaps() +{ + foreach (TimelineAndTrackView* tview, views_) { + tview->view()->DisableSnap(); + } +} + void TimelineWidget::StartRubberBandSelect(bool enable_selecting, bool select_links) { drag_origin_ = QCursor::pos(); @@ -1478,4 +1498,107 @@ void TimelineWidget::EndRubberBandSelect(bool enable_selecting, bool select_link ViewSelectionChanged(); } +struct SnapData { + rational time; + rational movement; +}; + +QList AttemptSnap(const QList& screen_pt, + double compare_pt, + const QList& start_times, + const rational& compare_time) { + const qreal kSnapRange = 10; // FIXME: Hardcoded number + + QList snap_data; + + for (int i=0;i start_times, rational* movement, int snap_points) +{ + QList screen_pt; + + foreach (const rational& s, start_times) { + screen_pt.append(TimeToScene(s + *movement)); + } + + QList potential_snaps; + + if (snap_points & kSnapToPlayhead) { + rational playhead_abs_time = GetTime(); + qreal playhead_pos = TimeToScene(playhead_abs_time); + potential_snaps.append(AttemptSnap(screen_pt, playhead_pos, start_times, playhead_abs_time)); + } + + if (snap_points & kSnapToClips) { + QMap::const_iterator i; + + for (i=block_items_.constBegin(); i!=block_items_.constEnd(); i++) { + TimelineViewBlockItem* item = i.value(); + + if (item) { + qreal rect_left = item->x(); + qreal rect_right = rect_left + item->rect().width(); + + // Attempt snapping to clip in point + potential_snaps.append(AttemptSnap(screen_pt, rect_left, start_times, item->block()->in())); + + // Attempt snapping to clip out point + potential_snaps.append(AttemptSnap(screen_pt, rect_right, start_times, item->block()->out())); + } + } + } + + if ((snap_points & kSnapToMarkers) && GetConnectedTimelinePoints()) { + foreach (TimelineMarker* m, GetConnectedTimelinePoints()->markers()->list()) { + qreal marker_pos = TimeToScene(m->time().in()); + potential_snaps.append(AttemptSnap(screen_pt, marker_pos, start_times, m->time().in())); + + if (m->time().in() != m->time().out()) { + marker_pos = TimeToScene(m->time().out()); + potential_snaps.append(AttemptSnap(screen_pt, marker_pos, start_times, m->time().out())); + } + } + } + + if (potential_snaps.isEmpty()) { + HideSnaps(); + return false; + } + + int closest_snap = 0; + rational closest_diff = qAbs(potential_snaps.at(0).movement - *movement); + + // Determine which snap point was the closest + for (int i=1; i snap_times; + foreach (const SnapData& data, potential_snaps) { + if (data.movement == *movement) { + snap_times.append(data.time); + } + } + + ShowSnap(snap_times); + + return true; +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index d0eed8496..d8a6c0dc5 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -27,6 +27,7 @@ #include "core.h" #include "node/output/viewer/viewer.h" +#include "snapservice.h" #include "timeline/timelinecommon.h" #include "timelineandtrackview.h" #include "widget/nodecopypaste/nodecopypaste.h" @@ -40,7 +41,7 @@ OLIVE_NAMESPACE_ENTER * * Encapsulates TimelineViews, TimeRulers, and scrollbars for a complete widget to manipulate Timelines */ -class TimelineWidget : public TimeBasedWidget, public NodeCopyPasteWidget +class TimelineWidget : public TimeBasedWidget, public NodeCopyPasteWidget, public SnapService { Q_OBJECT public: @@ -93,6 +94,10 @@ public: QList GetSelectedBlocks(); + virtual bool SnapPoint(QList start_times, rational *movement, int snap_points = kSnapAll) override; + + virtual void HideSnaps() override; + signals: void SelectionChanged(const QList& selected_blocks); @@ -189,17 +194,6 @@ private: */ int ValidateTrackMovement(int movement, const QVector ghosts); - enum SnapPoints { - kSnapToClips = 0x1, - kSnapToPlayhead = 0x2, - kSnapAll = 0xFF - }; - - /** - * @brief Snaps point `start_point` that is moving by `movement` to currently existing clips - */ - bool SnapPoint(QList start_times, rational *movement, int snap_points = kSnapAll); - void GetGhostData(const QVector& ghosts, rational *earliest_point, rational *latest_point); void InsertGapsAtGhostDestination(const QVector& ghosts, QUndoCommand* command); @@ -455,6 +449,8 @@ private: void EditTo(Timeline::MovementMode mode); + void ShowSnap(const QList& times); + QPoint drag_origin_; void StartRubberBandSelect(bool enable_selecting, bool select_links); diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index 4d16ea080..19e6f5322 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -150,13 +150,13 @@ void TimelineWidget::AddTool::MouseMoveInternal(const rational &cursor_frame, bo rational movement = cursor_frame - drag_start_point_; // Snap movement - bool snapped = SnapPoint(snap_points_, &movement); + bool snapped = parent()->SnapPoint(snap_points_, &movement); // If alt is held, our movement goes both ways (outwards) if (!snapped && outwards) { // Snap backwards too movement = -movement; - SnapPoint(snap_points_, &movement); + parent()->SnapPoint(snap_points_, &movement); // We don't need to un-neg here because outwards means all future processing will be done both pos and neg } diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index c48593326..3a8513d65 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -122,14 +122,17 @@ void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event) rational time_movement = event->GetFrame() - drag_start_.GetFrame(); int track_movement = event->GetTrack().index() - drag_start_.GetTrack().index(); - // If snapping is enabled, check for snap points - if (Core::instance()->snapping()) { - SnapPoint(snap_points_, &time_movement); - } - time_movement = ValidateTimeMovement(time_movement, parent()->ghost_items_); track_movement = ValidateTrackMovement(track_movement, parent()->ghost_items_); + // If snapping is enabled, check for snap points + if (Core::instance()->snapping()) { + parent()->SnapPoint(snap_points_, &time_movement); + + time_movement = ValidateTimeMovement(time_movement, parent()->ghost_items_); + track_movement = ValidateTrackMovement(track_movement, parent()->ghost_items_); + } + rational earliest_ghost = RATIONAL_MAX; // Move ghosts to the mouse cursor diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index d1d53e178..80d8dc703 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -314,17 +314,20 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po // Determine frame movement rational time_movement = mouse_pos.GetFrame() - drag_start_.GetFrame(); - // Perform snapping if enabled (adjusts time_movement if it's close to any potential snap points) - if (Core::instance()->snapping()) { - SnapPoint(snap_points_, &time_movement); - } - // Validate movement (enforce all ghosts moving in legal ways) - // NOTE: Always do this after snapping to ensure the snap hasn't made an illegal movement. time_movement = ValidateTimeMovement(time_movement, parent()->ghost_items_); time_movement = ValidateInTrimming(time_movement, parent()->ghost_items_, !trim_overwrite_allowed_); time_movement = ValidateOutTrimming(time_movement, parent()->ghost_items_, !trim_overwrite_allowed_); + // Perform snapping if enabled (adjusts time_movement if it's close to any potential snap points) + if (Core::instance()->snapping()) { + parent()->SnapPoint(snap_points_, &time_movement); + + time_movement = ValidateTimeMovement(time_movement, parent()->ghost_items_); + time_movement = ValidateInTrimming(time_movement, parent()->ghost_items_, !trim_overwrite_allowed_); + time_movement = ValidateOutTrimming(time_movement, parent()->ghost_items_, !trim_overwrite_allowed_); + } + // Validate ghosts that are being moved (clips from other track types do NOT get moved) { QVector validate_track_ghosts = parent()->ghost_items_; diff --git a/app/widget/timelinewidget/tool/tool.cpp b/app/widget/timelinewidget/tool/tool.cpp index e12b41139..51f9425ce 100644 --- a/app/widget/timelinewidget/tool/tool.cpp +++ b/app/widget/timelinewidget/tool/tool.cpp @@ -20,9 +20,6 @@ #include "widget/timelinewidget/timelinewidget.h" -#include - -#include "common/range.h" #include "node/block/transition/transition.h" #include "widget/nodeview/nodeviewundo.h" @@ -76,28 +73,6 @@ TimelineViewBlockItem *TimelineWidget::Tool::GetItemAtScenePos(const TimelineCoo return nullptr; } -void AttemptSnap(const QList& proposed_pts, - double compare_point, - const QList& start_times, - rational compare_time, - rational* movement, - double* diff) { - const qreal kSnapRange = 10; // FIXME: Hardcoded number - - for (int i=0;i= 0) { - *movement = compare_time - start_times.at(i); - *diff = this_diff; - } - } - } -} - rational TimelineWidget::Tool::ValidateTimeMovement(rational movement, const QVector ghosts) { foreach (TimelineViewGhostItem* ghost, ghosts) { @@ -153,52 +128,6 @@ int TimelineWidget::Tool::ValidateTrackMovement(int movement, const QVector