reworked and vastly improved snapping subsystem
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
#include <QTimer>
|
||||
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "widget/timebased/timebasedwidget.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -63,7 +64,7 @@ void TimeBasedView::TimebaseChangedEvent(const rational &)
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimeBasedView::EnableSnap(const QVector<rational> &points)
|
||||
void TimeBasedView::EnableSnap(const std::vector<rational> &points)
|
||||
{
|
||||
snapped_ = true;
|
||||
snap_time_ = points;
|
||||
@@ -78,11 +79,6 @@ void TimeBasedView::DisableSnap()
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimeBasedView::SetSnapService(SnapService *service)
|
||||
{
|
||||
snap_service_ = service;
|
||||
}
|
||||
|
||||
const double &TimeBasedView::GetYScale() const
|
||||
{
|
||||
return y_scale_;
|
||||
@@ -211,7 +207,7 @@ bool TimeBasedView::PlayheadMove(QMouseEvent *event)
|
||||
if (Core::instance()->snapping() && snap_service_) {
|
||||
rational movement;
|
||||
|
||||
snap_service_->SnapPoint({mouse_time}, &movement, SnapService::kSnapAll & ~SnapService::kSnapToPlayhead);
|
||||
snap_service_->SnapPoint({mouse_time}, &movement, TimeBasedWidget::kSnapAll & ~TimeBasedWidget::kSnapToPlayhead);
|
||||
|
||||
mouse_time += movement;
|
||||
}
|
||||
|
||||
@@ -22,21 +22,23 @@
|
||||
#define TIMELINEVIEWBASE_H
|
||||
|
||||
#include <QGraphicsView>
|
||||
#include <vector>
|
||||
|
||||
#include "core.h"
|
||||
#include "timescaledobject.h"
|
||||
#include "widget/handmovableview/handmovableview.h"
|
||||
#include "widget/snapservice/snapservice.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class TimeBasedWidget;
|
||||
|
||||
class TimeBasedView : public HandMovableView, public TimeScaledObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TimeBasedView(QWidget* parent = nullptr);
|
||||
|
||||
void EnableSnap(const QVector<rational> &points);
|
||||
void EnableSnap(const std::vector<rational> &points);
|
||||
void DisableSnap();
|
||||
bool IsSnapped() const
|
||||
{
|
||||
@@ -45,8 +47,8 @@ public:
|
||||
|
||||
const rational &GetTime() const { return playhead_; }
|
||||
|
||||
SnapService *GetSnapService() const { return snap_service_; }
|
||||
void SetSnapService(SnapService* service);
|
||||
TimeBasedWidget *GetSnapService() const { return snap_service_; }
|
||||
void SetSnapService(TimeBasedWidget* service) { snap_service_ = service; }
|
||||
|
||||
const double& GetYScale() const;
|
||||
void SetYScale(const double& y_scale);
|
||||
@@ -117,11 +119,11 @@ private:
|
||||
QGraphicsScene scene_;
|
||||
|
||||
bool snapped_;
|
||||
QVector<rational> snap_time_;
|
||||
std::vector<rational> snap_time_;
|
||||
|
||||
rational end_time_;
|
||||
|
||||
SnapService* snap_service_;
|
||||
TimeBasedWidget* snap_service_;
|
||||
|
||||
bool y_axis_enabled_;
|
||||
|
||||
|
||||
@@ -22,5 +22,4 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "common/rational.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "timebasedview.h"
|
||||
#include "timebasedwidget.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -38,9 +39,15 @@ class TimeBasedViewSelectionManager
|
||||
public:
|
||||
TimeBasedViewSelectionManager(TimeBasedView *view) :
|
||||
view_(view),
|
||||
rubberband_(nullptr)
|
||||
rubberband_(nullptr),
|
||||
snap_mask_(TimeBasedWidget::kSnapAll)
|
||||
{}
|
||||
|
||||
void SetSnapMask(TimeBasedWidget::SnapMask e)
|
||||
{
|
||||
snap_mask_ = e;
|
||||
}
|
||||
|
||||
void ClearDrawnObjects()
|
||||
{
|
||||
drawn_objects_.clear();
|
||||
@@ -156,19 +163,40 @@ public:
|
||||
initial_drag_item_ = initial_item;
|
||||
|
||||
dragging_.resize(selected_.size());
|
||||
snap_points_.resize(selected_.size()*2);
|
||||
for (size_t i=0; i<selected_.size(); i++) {
|
||||
T *obj = selected_.at(i);
|
||||
|
||||
dragging_[i] = obj->time();
|
||||
|
||||
snap_points_[i] = obj->time();
|
||||
snap_points_[i+selected_.size()] = obj->time_range().out();
|
||||
}
|
||||
|
||||
drag_mouse_start_ = view_->mapToScene(event->pos());
|
||||
}
|
||||
|
||||
void SnapPoints(rational *movement)
|
||||
{
|
||||
if (Core::instance()->snapping() && view_->GetSnapService()) {
|
||||
view_->GetSnapService()->SnapPoint(snap_points_, movement, snap_mask_);
|
||||
}
|
||||
}
|
||||
|
||||
void Unsnap()
|
||||
{
|
||||
if (view_->GetSnapService()) {
|
||||
view_->GetSnapService()->HideSnaps();
|
||||
}
|
||||
}
|
||||
|
||||
void DragMove(QMouseEvent *event, const QString &tip_format = QString())
|
||||
{
|
||||
rational time_diff = view_->SceneToTimeNoGrid(view_->mapToScene(event->pos()).x() - drag_mouse_start_.x());
|
||||
|
||||
// Snap points
|
||||
SnapPoints(&time_diff);
|
||||
|
||||
// Validate movement
|
||||
for (size_t i=0; i<selected_.size(); i++) {
|
||||
rational proposed_time = dragging_.at(i) + time_diff;
|
||||
@@ -186,11 +214,13 @@ public:
|
||||
loop = false;
|
||||
while (sel->has_sibling_at_time(proposed_time)) {
|
||||
proposed_time += adj;
|
||||
Unsnap();
|
||||
}
|
||||
|
||||
if (proposed_time < 0) {
|
||||
// Prevent any object from going below zero
|
||||
proposed_time = 0;
|
||||
Unsnap();
|
||||
|
||||
// Setting our proposed time to zero may (re)introduce a conflict that we just avoided
|
||||
// with the sibling check above, so we request it to happen again. To avoid a negative
|
||||
@@ -230,6 +260,7 @@ public:
|
||||
}
|
||||
|
||||
dragging_.clear();
|
||||
Unsnap();
|
||||
}
|
||||
|
||||
void RubberBandStart(QMouseEvent *event)
|
||||
@@ -325,6 +356,7 @@ private:
|
||||
std::vector<T*> selected_;
|
||||
|
||||
std::vector<rational> dragging_;
|
||||
std::vector<rational> snap_points_;
|
||||
|
||||
T *initial_drag_item_;
|
||||
|
||||
@@ -336,6 +368,8 @@ private:
|
||||
QPoint rubberband_start_;
|
||||
std::vector<T*> rubberband_preselected_;
|
||||
|
||||
TimeBasedWidget::SnapMask snap_mask_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -23,11 +23,13 @@
|
||||
#include <QInputDialog>
|
||||
|
||||
#include "common/autoscroll.h"
|
||||
#include "common/range.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "config/config.h"
|
||||
#include "core.h"
|
||||
#include "dialog/markerproperties/markerpropertiesdialog.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "widget/timeruler/timeruler.h"
|
||||
#include "widget/timelinewidget/undo/timelineundoworkarea.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -41,6 +43,7 @@ TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_statu
|
||||
{
|
||||
ruler_ = new TimeRuler(ruler_text_visible, ruler_cache_status_visible, this);
|
||||
ConnectTimelineView(ruler_, true);
|
||||
ruler()->SetSnapService(this);
|
||||
|
||||
scrollbar_ = new ResizableTimelineScrollBar(Qt::Horizontal, this);
|
||||
connect(scrollbar_, &ResizableScrollBar::ResizeBegan, this, &TimeBasedWidget::ScrollBarResizeBegan);
|
||||
@@ -693,4 +696,155 @@ bool TimeBasedWidget::eventFilter(QObject *object, QEvent *event)
|
||||
return false;
|
||||
}
|
||||
|
||||
struct SnapData {
|
||||
rational time;
|
||||
rational movement;
|
||||
};
|
||||
|
||||
void AttemptSnap(std::vector<SnapData> &snap_data,
|
||||
const std::vector<double>& screen_pt,
|
||||
double compare_pt,
|
||||
const std::vector<rational>& start_times,
|
||||
const rational& compare_time)
|
||||
{
|
||||
const qreal kSnapRange = 10; // FIXME: Hardcoded number
|
||||
|
||||
for (size_t i=0;i<screen_pt.size();i++) {
|
||||
// Attempt snapping to clip out point
|
||||
if (InRange(screen_pt.at(i), compare_pt, kSnapRange)) {
|
||||
snap_data.push_back({compare_time, compare_time - start_times.at(i)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TimeBasedWidget::SnapPoint(const std::vector<rational> &start_times, rational *movement, SnapMask snap_points)
|
||||
{
|
||||
std::vector<double> screen_pt(start_times.size());
|
||||
|
||||
for (size_t i=0; i<start_times.size(); i++) {
|
||||
screen_pt[i] = TimeToScene(start_times.at(i) + *movement);
|
||||
}
|
||||
|
||||
std::vector<SnapData> potential_snaps;
|
||||
|
||||
if (snap_points & kSnapToPlayhead) {
|
||||
rational playhead_abs_time = GetTime();
|
||||
qreal playhead_pos = TimeToScene(playhead_abs_time);
|
||||
AttemptSnap(potential_snaps, screen_pt, playhead_pos, start_times, playhead_abs_time);
|
||||
}
|
||||
|
||||
if ((snap_points & kSnapToClips) && GetSnapBlocks()) {
|
||||
for (auto it=GetSnapBlocks()->cbegin(); it!=GetSnapBlocks()->cend(); it++) {
|
||||
Block *b = *it;
|
||||
|
||||
qreal rect_left = TimeToScene(b->in());
|
||||
qreal rect_right = TimeToScene(b->out());
|
||||
|
||||
// Attempt snapping to clip in point
|
||||
AttemptSnap(potential_snaps, screen_pt, rect_left, start_times, b->in());
|
||||
|
||||
// Attempt snapping to clip out point
|
||||
AttemptSnap(potential_snaps, screen_pt, rect_right, start_times, b->out());
|
||||
|
||||
if (snap_points & kSnapToMarkers) {
|
||||
// Snap to clip markers too
|
||||
if (ClipBlock *clip = dynamic_cast<ClipBlock*>(b)) {
|
||||
if (clip->connected_viewer()) {
|
||||
TimelineMarkerList *markers = clip->connected_viewer()->GetTimelinePoints()->markers();
|
||||
for (auto jt=markers->cbegin(); jt!=markers->cend(); jt++) {
|
||||
TimelineMarker *marker = *jt;
|
||||
|
||||
TimeRange marker_range = marker->time_range() + clip->in() - clip->media_in();
|
||||
|
||||
qreal marker_in_screen = TimeToScene(marker_range.in());
|
||||
qreal marker_out_screen = TimeToScene(marker_range.out());
|
||||
|
||||
AttemptSnap(potential_snaps, screen_pt, marker_in_screen, start_times, marker_range.in());
|
||||
AttemptSnap(potential_snaps, screen_pt, marker_out_screen, start_times, marker_range.out());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((snap_points & kSnapToMarkers) && ruler()->GetTimelinePoints()) {
|
||||
for (auto it=ruler()->GetTimelinePoints()->markers()->cbegin(); it!=ruler()->GetTimelinePoints()->markers()->cend(); it++) {
|
||||
TimelineMarker* m = *it;
|
||||
|
||||
qreal marker_pos = TimeToScene(m->time_range().in());
|
||||
AttemptSnap(potential_snaps, screen_pt, marker_pos, start_times, m->time_range().in());
|
||||
|
||||
if (m->time_range().in() != m->time_range().out()) {
|
||||
marker_pos = TimeToScene(m->time_range().out());
|
||||
AttemptSnap(potential_snaps, screen_pt, marker_pos, start_times, m->time_range().out());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((snap_points & kSnapToKeyframes) && GetSnapKeyframes()) {
|
||||
for (auto it=GetSnapKeyframes()->cbegin(); it!=GetSnapKeyframes()->cend(); it++) {
|
||||
const QVector<NodeKeyframe*> &keys = (*it)->GetKeyframes();
|
||||
for (auto jt=keys.cbegin(); jt!=keys.cend(); jt++) {
|
||||
NodeKeyframe *key = *jt;
|
||||
|
||||
auto ignore = GetSnapIgnoreKeyframes();
|
||||
if (ignore && std::find(ignore->cbegin(), ignore->cend(), key) != ignore->cend()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
qreal key_scene_pt = TimeToScene(key->time());
|
||||
|
||||
AttemptSnap(potential_snaps, screen_pt, key_scene_pt, start_times, key->time());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (potential_snaps.empty()) {
|
||||
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 (size_t i=1; i<potential_snaps.size(); i++) {
|
||||
rational this_diff = qAbs(potential_snaps.at(i).movement - *movement);
|
||||
|
||||
if (this_diff < closest_diff) {
|
||||
closest_snap = i;
|
||||
closest_diff = this_diff;
|
||||
}
|
||||
}
|
||||
|
||||
*movement = potential_snaps.at(closest_snap).movement;
|
||||
|
||||
// Find all points at this movement
|
||||
std::vector<rational> snap_times;
|
||||
foreach (const SnapData& d, potential_snaps) {
|
||||
if (d.movement == *movement) {
|
||||
snap_times.push_back(d.time);
|
||||
}
|
||||
}
|
||||
|
||||
ShowSnaps(snap_times);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TimeBasedWidget::ShowSnaps(const std::vector<rational> ×)
|
||||
{
|
||||
foreach (TimeBasedView* view, timeline_views_) {
|
||||
view->EnableSnap(times);
|
||||
}
|
||||
}
|
||||
|
||||
void TimeBasedWidget::HideSnaps()
|
||||
{
|
||||
foreach (TimeBasedView* view, timeline_views_) {
|
||||
view->DisableSnap();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,13 +25,15 @@
|
||||
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "timeline/timelinecommon.h"
|
||||
#include "widget/keyframeview/keyframeviewinputconnection.h"
|
||||
#include "widget/resizablescrollbar/resizabletimelinescrollbar.h"
|
||||
#include "widget/timebased/timescaledobject.h"
|
||||
#include "widget/timelinewidget/view/timelineview.h"
|
||||
#include "widget/timeruler/timeruler.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class TimeRuler;
|
||||
|
||||
class TimeBasedWidget : public TimelineScaledWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -54,6 +56,22 @@ public:
|
||||
|
||||
virtual bool eventFilter(QObject* object, QEvent* event) override;
|
||||
|
||||
using SnapMask = uint32_t;
|
||||
enum SnapPoints {
|
||||
kSnapToClips = 0x1,
|
||||
kSnapToPlayhead = 0x2,
|
||||
kSnapToMarkers = 0x4,
|
||||
kSnapToKeyframes = 0x8,
|
||||
kSnapAll = UINT32_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Snaps point `start_point` that is moving by `movement` to currently existing clips
|
||||
*/
|
||||
bool SnapPoint(const std::vector<rational> &start_times, rational *movement, SnapMask snap_points = kSnapAll);
|
||||
void ShowSnaps(const std::vector<rational> ×);
|
||||
void HideSnaps();
|
||||
|
||||
public slots:
|
||||
void SetTime(const rational &time);
|
||||
|
||||
@@ -119,6 +137,10 @@ protected:
|
||||
|
||||
void PassWheelEventsToScrollBar(QObject* object);
|
||||
|
||||
virtual const QVector<Block*> *GetSnapBlocks() const { return nullptr; }
|
||||
virtual const QVector<KeyframeViewInputConnection*> *GetSnapKeyframes() const { return nullptr; }
|
||||
virtual const std::vector<NodeKeyframe*> *GetSnapIgnoreKeyframes() const { return nullptr; }
|
||||
|
||||
protected slots:
|
||||
/**
|
||||
* @brief Slot to center the horizontal scroll bar on the playhead's current position
|
||||
@@ -141,7 +163,7 @@ signals:
|
||||
void ConnectedNodeChanged(ViewerOutput* old, ViewerOutput* now);
|
||||
|
||||
private:
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Set either in or out point to the current playhead
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "common/rational.h"
|
||||
#include "node/block/block.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user