curve/timeline: improved user interface behavior
* Implements "auto-fit" setting for curve view and sets it on open. * Improves scroll zooming on all TimelineViewBase derivatives. * Improves code sharing for better maintenance.
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "curveview.h"
|
||||
|
||||
#include <QScrollBar>
|
||||
#include <QMouseEvent>
|
||||
#include <QtMath>
|
||||
|
||||
@@ -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<NodeKeyframe *> CurveView::GetKeyframesSortedByTime(int track)
|
||||
{
|
||||
QList<NodeKeyframe *> sorted;
|
||||
@@ -320,7 +301,12 @@ QList<NodeKeyframe *> 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<NodeKeyframe *, KeyframeViewItem *>::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);
|
||||
|
||||
@@ -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<NodeKeyframe*> GetKeyframesSortedByTime(int track);
|
||||
|
||||
qreal GetItemYFromKeyframeValue(NodeKeyframe* key);
|
||||
qreal GetItemYFromKeyframeValue(double value);
|
||||
|
||||
void SetItemYFromKeyframeValue(NodeKeyframe* key, KeyframeViewItem* item);
|
||||
|
||||
|
||||
@@ -169,6 +169,8 @@ void CurveWidget::SetInput(NodeInput *input)
|
||||
}
|
||||
|
||||
UpdateInputLabel();
|
||||
|
||||
QMetaObject::invokeMethod(view_, "ZoomToFit", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
const double &CurveWidget::GetVerticalScale()
|
||||
|
||||
@@ -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<NodeKeyframe *, KeyframeViewItem *> &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<double>(cursor_pos.x()) / GetScale(),
|
||||
static_cast<double>(cursor_pos.y()) / y_scale_);
|
||||
static_cast<double>(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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<NodeKeyframe*, KeyframeViewItem*>& 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<KeyframeItemAndTime> selected_keys_;
|
||||
|
||||
bool y_axis_enabled_;
|
||||
|
||||
double y_scale_;
|
||||
|
||||
bool currently_autoselecting_;
|
||||
|
||||
QList<int> hidden_tracks_;
|
||||
|
||||
@@ -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<Node *> nodes)
|
||||
}
|
||||
}
|
||||
|
||||
UpdateItemTime(GetTimestamp());
|
||||
|
||||
QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<double>(viewport_sz / kCalculateDimensionsPadding * (kCalculateDimensionsPadding-1)) / static_cast<double>(content_sz);
|
||||
}
|
||||
|
||||
double TimelineScaledObject::CalculatePaddingFromDimensionScale(double viewport_sz)
|
||||
{
|
||||
return (viewport_sz / (kCalculateDimensionsPadding * 2));
|
||||
}
|
||||
|
||||
TimelineScaledWidget::TimelineScaledWidget(QWidget *parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user