diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index 52a7e8eab..e5fc65cdd 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -20,6 +20,7 @@ #include "curveview.h" +#include #include #include @@ -251,42 +252,22 @@ void CurveView::VerticalScaleChangedEvent(double scale) void CurveView::wheelEvent(QWheelEvent *event) { - if (WheelEventIsAZoomEvent(event)) { - if (!event->angleDelta().isNull()) { - bool only_vertical = false; - bool only_horizontal = false; - - if (event->modifiers() & Qt::ShiftModifier) { - if (event->modifiers() & Qt::AltModifier) { - only_horizontal = true; - } else { - only_vertical = true; - } - } - - if (event->angleDelta().x() + event->angleDelta().y() > 0) { - if (!only_vertical) { - emit ScaleChanged(GetScale() * 1.1); - } - - if (!only_horizontal) { - SetYScale(GetYScale() * 1.1); - } - } else { - if (!only_vertical) { - emit ScaleChanged(GetScale() * 0.9); - } - - if (!only_horizontal) { - SetYScale(GetYScale() *0.9); - } - } - } - } else { + if (!HandleZoomFromScroll(event)) { KeyframeViewBase::wheelEvent(event); } } +void CurveView::ContextMenuEvent(Menu &m) +{ + m.addSeparator(); + + // View settings + QAction* zoom_fit_action = m.addAction(tr("Zoom to Fit")); + connect(zoom_fit_action, &QAction::triggered, this, &CurveView::ZoomToFit); + + //QAction* reset_zoom_action = m.addAction(tr("Reset Zoom")); +} + QList CurveView::GetKeyframesSortedByTime(int track) { QList sorted; @@ -320,7 +301,12 @@ QList CurveView::GetKeyframesSortedByTime(int track) qreal CurveView::GetItemYFromKeyframeValue(NodeKeyframe *key) { - return -key->value().toDouble() * GetYScale(); + return GetItemYFromKeyframeValue(key->value().toDouble()); +} + +qreal CurveView::GetItemYFromKeyframeValue(double value) +{ + return -value * GetYScale(); } void CurveView::SetItemYFromKeyframeValue(NodeKeyframe *key, KeyframeViewItem *item) @@ -402,6 +388,40 @@ void CurveView::BezierControlPointDestroyed() bezier_control_points_.removeOne(item); } +void CurveView::ZoomToFit() +{ + if (item_map().isEmpty()) { + // Prevent scaling to DBL_MIN/DBL_MAX + return; + } + + QMap::const_iterator i; + + rational min_time = RATIONAL_MAX; + rational max_time = RATIONAL_MIN; + + double min_val = DBL_MAX; + double max_val = DBL_MIN; + + for (i=item_map().constBegin(); i!=item_map().constEnd(); i++) { + min_time = qMin(i.key()->time(), min_time); + max_time = qMax(i.key()->time(), max_time); + + min_val = qMin(i.key()->value().toDouble(), min_val); + max_val = qMax(i.key()->value().toDouble(), max_val); + } + + double time_range = max_time.toDouble() - min_time.toDouble(); + double new_x_scale = CalculateScaleFromDimensions(this->width(), time_range); + double new_y_scale = CalculateScaleFromDimensions(this->height(), max_val - min_val); + + emit ScaleChanged(new_x_scale); + SetYScale(new_y_scale); + + horizontalScrollBar()->setValue(TimeToScene(min_time) - CalculatePaddingFromDimensionScale(this->width())); + verticalScrollBar()->setValue(GetItemYFromKeyframeValue(max_val) - CalculatePaddingFromDimensionScale(this->height())); +} + void CurveView::AddKeyframe(NodeKeyframePtr key) { KeyframeViewItem* item = AddKeyframeInternal(key); diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index 67c2e25c3..e051f4f04 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -30,6 +30,7 @@ OLIVE_NAMESPACE_ENTER class CurveView : public KeyframeViewBase { + Q_OBJECT public: CurveView(QWidget* parent = nullptr); @@ -44,6 +45,8 @@ public: public slots: void AddKeyframe(NodeKeyframePtr key); + void ZoomToFit(); + protected: virtual void drawBackground(QPainter* painter, const QRectF& rect) override; @@ -55,10 +58,13 @@ protected: virtual void wheelEvent(QWheelEvent* event) override; + virtual void ContextMenuEvent(Menu &m) override; + private: QList GetKeyframesSortedByTime(int track); qreal GetItemYFromKeyframeValue(NodeKeyframe* key); + qreal GetItemYFromKeyframeValue(double value); void SetItemYFromKeyframeValue(NodeKeyframe* key, KeyframeViewItem* item); diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index aa294deb2..da32088ec 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -169,6 +169,8 @@ void CurveWidget::SetInput(NodeInput *input) } UpdateInputLabel(); + + QMetaObject::invokeMethod(view_, "ZoomToFit", Qt::QueuedConnection); } const double &CurveWidget::GetVerticalScale() diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp index e6c4ad480..c97a85372 100644 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ b/app/widget/keyframeview/keyframeviewbase.cpp @@ -35,8 +35,6 @@ OLIVE_NAMESPACE_ENTER KeyframeViewBase::KeyframeViewBase(QWidget *parent) : TimelineViewBase(parent), dragging_bezier_point_(nullptr), - y_axis_enabled_(false), - y_scale_(1.0), currently_autoselecting_(false) { SetDefaultDragMode(RubberBandDrag); @@ -57,22 +55,6 @@ void KeyframeViewBase::Clear() item_map_.clear(); } -const double &KeyframeViewBase::GetYScale() const -{ - return y_scale_; -} - -void KeyframeViewBase::SetYScale(const double &y_scale) -{ - y_scale_ = y_scale; - - if (y_axis_enabled_) { - VerticalScaleChangedEvent(y_scale_); - - viewport()->update(); - } -} - void KeyframeViewBase::DeleteSelected() { QUndoCommand* command = new QUndoCommand(); @@ -194,7 +176,7 @@ void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) keypair.key->key()->set_time(node_time); - if (y_axis_enabled_) { + if (IsYAxisEnabled()) { keypair.key->key()->set_value(keypair.value - mouse_diff_scaled.y()); } @@ -257,7 +239,7 @@ void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event) command); // Commit value if we're setting a value - if (y_axis_enabled_) { + if (IsYAxisEnabled()) { item->key()->set_value(keypair.value); new NodeParamSetKeyframeValueCommand(item->key(), keypair.value - mouse_diff_scaled.y(), @@ -288,10 +270,6 @@ void KeyframeViewBase::ScaleChangedEvent(const double &scale) } } -void KeyframeViewBase::VerticalScaleChangedEvent(double) -{ -} - const QMap &KeyframeViewBase::item_map() const { return item_map_; @@ -310,11 +288,6 @@ void KeyframeViewBase::TimeTargetChangedEvent(Node *target) } } -void KeyframeViewBase::SetYAxisEnabled(bool e) -{ - y_axis_enabled_ = e; -} - void KeyframeViewBase::SetKeyframeTrackVisible(int track, bool visible) { if (!visible == hidden_tracks_.contains(track)) { @@ -336,6 +309,11 @@ void KeyframeViewBase::SetKeyframeTrackVisible(int track, bool visible) } } +void KeyframeViewBase::ContextMenuEvent(Menu& m) +{ + Q_UNUSED(m) +} + rational KeyframeViewBase::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff) { return rational::fromDouble(old_time.toDouble() + cursor_diff); @@ -433,7 +411,7 @@ void KeyframeViewBase::ProcessBezierDrag(QPointF mouse_diff_scaled, bool include QPointF KeyframeViewBase::GetScaledCursorPos(const QPoint &cursor_pos) { return QPointF(static_cast(cursor_pos.x()) / GetScale(), - static_cast(cursor_pos.y()) / y_scale_); + static_cast(cursor_pos.y()) / GetYScale()); } void KeyframeViewBase::ShowContextMenu() @@ -480,7 +458,11 @@ void KeyframeViewBase::ShowContextMenu() break; } } + } + ContextMenuEvent(m); + + if (!items.isEmpty()) { m.addSeparator(); QAction* properties_action = m.addAction(tr("P&roperties")); @@ -532,7 +514,7 @@ void KeyframeViewBase::ShowKeyframePropertiesDialog() void KeyframeViewBase::AutoSelectKeyTimeNeighbors() { - if (currently_autoselecting_ || y_axis_enabled_) { + if (currently_autoselecting_ || IsYAxisEnabled()) { return; } diff --git a/app/widget/keyframeview/keyframeviewbase.h b/app/widget/keyframeview/keyframeviewbase.h index 705e845c6..743ef996f 100644 --- a/app/widget/keyframeview/keyframeviewbase.h +++ b/app/widget/keyframeview/keyframeviewbase.h @@ -23,9 +23,10 @@ #include "keyframeviewitem.h" #include "node/keyframe.h" -#include "widget/timetarget/timetarget.h" #include "widget/curvewidget/beziercontrolpointitem.h" +#include "widget/menu/menu.h" #include "widget/timelinewidget/view/timelineviewbase.h" +#include "widget/timetarget/timetarget.h" OLIVE_NAMESPACE_ENTER @@ -37,9 +38,6 @@ public: virtual void Clear(); - const double& GetYScale() const; - void SetYScale(const double& y_scale); - void DeleteSelected(); public slots: @@ -54,18 +52,16 @@ protected: virtual void ScaleChangedEvent(const double& scale) override; - virtual void VerticalScaleChangedEvent(double scale); - const QMap& item_map() const; virtual void KeyframeAboutToBeRemoved(NodeKeyframe* key); virtual void TimeTargetChangedEvent(Node*) override; - void SetYAxisEnabled(bool e); - void SetKeyframeTrackVisible(int track, bool visible); + virtual void ContextMenuEvent(Menu &m); + private: rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff); @@ -96,10 +92,6 @@ private: QVector selected_keys_; - bool y_axis_enabled_; - - double y_scale_; - bool currently_autoselecting_; QList hidden_tracks_; diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 03ce22f18..24f32e7d6 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -55,6 +55,8 @@ NodeParamView::NodeParamView(QWidget *parent) : // Set up scroll area layout param_layout_ = new QVBoxLayout(param_widget_area_); param_layout_->setSpacing(0); + + // KeyframeView is offset by a ruler, so to stay synchronized with it, we should be too param_layout_->setContentsMargins(0, ruler()->height(), 0, 0); // Add a stretch to allow empty space at the bottom of the layout @@ -162,6 +164,8 @@ void NodeParamView::SetNodes(QList nodes) } } + UpdateItemTime(GetTimestamp()); + QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection); } } diff --git a/app/widget/timebased/timebased.cpp b/app/widget/timebased/timebased.cpp index b2e15b03b..5c83206c6 100644 --- a/app/widget/timebased/timebased.cpp +++ b/app/widget/timebased/timebased.cpp @@ -482,12 +482,12 @@ void TimeBasedWidget::ToggleShowAll() w = timeline_views_.first()->width(); } - w = w / 10 * 9; + toggle_show_all_old_scale_ = GetScale(); toggle_show_all_old_scroll_ = scrollbar_->value(); - SetScale(w / GetConnectedNode()->GetLength().toDouble()); + SetScaleFromDimensions(w, GetConnectedNode()->GetLength().toDouble()); scrollbar_->setValue(0); // Must explicitly do this because SetScale() will automatically set this to false diff --git a/app/widget/timelinewidget/timelinescaledobject.cpp b/app/widget/timelinewidget/timelinescaledobject.cpp index 979ecf048..d12a998f8 100644 --- a/app/widget/timelinewidget/timelinescaledobject.cpp +++ b/app/widget/timelinewidget/timelinescaledobject.cpp @@ -27,6 +27,8 @@ OLIVE_NAMESPACE_ENTER +const int TimelineScaledObject::kCalculateDimensionsPadding = 10; + TimelineScaledObject::TimelineScaledObject() : scale_(1.0), min_scale_(0), @@ -112,6 +114,21 @@ void TimelineScaledObject::SetScale(const double& scale) ScaleChangedEvent(scale_); } +void TimelineScaledObject::SetScaleFromDimensions(double viewport_width, double content_width) +{ + SetScale(CalculateScaleFromDimensions(viewport_width, content_width)); +} + +double TimelineScaledObject::CalculateScaleFromDimensions(double viewport_sz, double content_sz) +{ + return static_cast(viewport_sz / kCalculateDimensionsPadding * (kCalculateDimensionsPadding-1)) / static_cast(content_sz); +} + +double TimelineScaledObject::CalculatePaddingFromDimensionScale(double viewport_sz) +{ + return (viewport_sz / (kCalculateDimensionsPadding * 2)); +} + TimelineScaledWidget::TimelineScaledWidget(QWidget *parent) : QWidget(parent) { diff --git a/app/widget/timelinewidget/timelinescaledobject.h b/app/widget/timelinewidget/timelinescaledobject.h index c55cff296..c4aec301c 100644 --- a/app/widget/timelinewidget/timelinescaledobject.h +++ b/app/widget/timelinewidget/timelinescaledobject.h @@ -44,6 +44,10 @@ public: void SetScale(const double& scale); + void SetScaleFromDimensions(double viewport_width, double content_width); + static double CalculateScaleFromDimensions(double viewport_sz, double content_sz); + static double CalculatePaddingFromDimensionScale(double viewport_sz); + protected: double TimeToScene(const rational& time); rational SceneToTime(const double &x, bool round = false); @@ -67,6 +71,8 @@ private: double max_scale_; + static const int kCalculateDimensionsPadding; + }; class TimelineScaledWidget : public QWidget, public TimelineScaledObject diff --git a/app/widget/timelinewidget/view/timelineviewbase.cpp b/app/widget/timelinewidget/view/timelineviewbase.cpp index c044f425e..a5acb99e1 100644 --- a/app/widget/timelinewidget/view/timelineviewbase.cpp +++ b/app/widget/timelinewidget/view/timelineviewbase.cpp @@ -40,7 +40,9 @@ TimelineViewBase::TimelineViewBase(QWidget *parent) : playhead_scene_right_(-1), dragging_playhead_(false), snapped_(false), - snap_service_(nullptr) + snap_service_(nullptr), + y_axis_enabled_(false), + y_scale_(1.0) { setScene(&scene_); @@ -80,6 +82,26 @@ void TimelineViewBase::SetSnapService(SnapService *service) snap_service_ = service; } +const double &TimelineViewBase::GetYScale() const +{ + return y_scale_; +} + +void TimelineViewBase::VerticalScaleChangedEvent(double) +{ +} + +void TimelineViewBase::SetYScale(const double &y_scale) +{ + y_scale_ = y_scale; + + if (y_axis_enabled_) { + VerticalScaleChangedEvent(y_scale_); + + viewport()->update(); + } +} + void TimelineViewBase::SetTime(const int64_t time) { playhead_ = time; @@ -252,10 +274,56 @@ bool TimelineViewBase::HandleZoomFromScroll(QWheelEvent *event) if (WheelEventIsAZoomEvent(event)) { // If CTRL is held (or a preference is set to swap CTRL behavior), we zoom instead of scrolling if (!event->angleDelta().isNull()) { - if (event->angleDelta().x() + event->angleDelta().y() > 0) { - emit ScaleChanged(GetScale() * 1.1); + bool only_vertical = false; + bool only_horizontal = false; + + // Ctrl+Shift limits to only one axis + // Alt switches between horizontal only (alt held) or vertical only (alt not held) + if (y_axis_enabled_) { + if (event->modifiers() & Qt::ShiftModifier) { + if (event->modifiers() & Qt::AltModifier) { + only_horizontal = true; + } else { + only_vertical = true; + } + } } else { - emit ScaleChanged(GetScale() * 0.9); + only_horizontal = true; + } + + double scale_multiplier; + + if (event->angleDelta().x() + event->angleDelta().y() > 0) { + scale_multiplier = 1.1; + } else { + scale_multiplier = 0.9; + } + + QPointF cursor_pos; +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + cursor_pos = event->position(); +#else + cursor_pos = event->posF(); +#endif + + if (!only_vertical) { + double new_x_scale = GetScale() * scale_multiplier; + + int new_x_scroll = qRound(horizontalScrollBar()->value() / GetScale() * new_x_scale + (cursor_pos.x() - cursor_pos.x() / new_x_scale * GetScale())); + + emit ScaleChanged(new_x_scale); + + horizontalScrollBar()->setValue(new_x_scroll); + } + + if (!only_horizontal) { + double new_y_scale = GetYScale() * scale_multiplier; + + int new_y_scroll = qRound(verticalScrollBar()->value() / GetYScale() * new_y_scale + (cursor_pos.y() - cursor_pos.y() / new_y_scale * GetYScale())); + + SetYScale(new_y_scale); + + verticalScrollBar()->setValue(new_y_scroll); } } diff --git a/app/widget/timelinewidget/view/timelineviewbase.h b/app/widget/timelinewidget/view/timelineviewbase.h index cb667dd41..d24af659e 100644 --- a/app/widget/timelinewidget/view/timelineviewbase.h +++ b/app/widget/timelinewidget/view/timelineviewbase.h @@ -48,6 +48,9 @@ public: void SetSnapService(SnapService* service); + const double& GetYScale() const; + void SetYScale(const double& y_scale); + public slots: void SetTime(const int64_t time); @@ -69,6 +72,8 @@ protected: virtual void SceneRectUpdateEvent(QRectF&){} + virtual void VerticalScaleChangedEvent(double scale); + bool HandleZoomFromScroll(QWheelEvent* event); bool WheelEventIsAZoomEvent(QWheelEvent* event); @@ -81,6 +86,16 @@ protected: virtual void TimebaseChangedEvent(const rational &) override; + bool IsYAxisEnabled() const + { + return y_axis_enabled_; + } + + void SetYAxisEnabled(bool e) + { + y_axis_enabled_ = e; + } + private: qreal GetPlayheadX(); @@ -102,6 +117,10 @@ private: SnapService* snap_service_; + bool y_axis_enabled_; + + double y_scale_; + private slots: /** * @brief Slot called whenever the view resizes or the scene contents change to enforce minimum scene sizes