refined curve view
This commit is contained in:
@@ -30,7 +30,6 @@
|
||||
#include "common/qtutils.h"
|
||||
#include "widget/keyframeview/keyframeviewundo.h"
|
||||
#include "widget/nodeparamview/nodeparamviewundo.h"
|
||||
#include "widget/slider/floatslider.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -61,6 +60,9 @@ void CurveView::ConnectInput(const NodeKeyframeTrackReference& ref)
|
||||
track_con->SetBrush(keyframe_colors_.value(ref));
|
||||
track_connections_.insert(ref, track_con);
|
||||
|
||||
// Signal to CurveWidget to update its bezier/linear/hold buttons if a key type changes
|
||||
connect(track_con, &KeyframeViewInputConnection::TypeChanged, this, &CurveView::SelectionChanged);
|
||||
|
||||
// Append to the list
|
||||
connected_inputs_.append(ref);
|
||||
}
|
||||
@@ -90,13 +92,6 @@ void CurveView::SelectKeyframesOfInput(const NodeKeyframeTrackReference& ref)
|
||||
}
|
||||
}
|
||||
|
||||
void CurveView::ZoomToFitInput(const NodeKeyframeTrackReference& ref)
|
||||
{
|
||||
if (KeyframeViewInputConnection *con = track_connections_.value(ref)) {
|
||||
ZoomToFitInternal(con->GetKeyframes());
|
||||
}
|
||||
}
|
||||
|
||||
void CurveView::SetKeyframeTrackColor(const NodeKeyframeTrackReference &ref, const QColor &color)
|
||||
{
|
||||
// Insert color into hashmap
|
||||
@@ -265,8 +260,28 @@ void CurveView::ContextMenuEvent(Menu &m)
|
||||
|
||||
void CurveView::SceneRectUpdateEvent(QRectF &r)
|
||||
{
|
||||
r.setTop(r.top() - this->height());
|
||||
r.setBottom(r.bottom() + this->height());
|
||||
double min_val, max_val;
|
||||
bool got_val = false;
|
||||
|
||||
foreach (KeyframeViewInputConnection *con, track_connections_) {
|
||||
foreach (NodeKeyframe *key, con->GetKeyframes()) {
|
||||
qreal key_y = GetItemYFromKeyframeValue(key);
|
||||
|
||||
if (got_val) {
|
||||
min_val = qMin(key_y, min_val);
|
||||
max_val = qMax(key_y, max_val);
|
||||
} else {
|
||||
min_val = key_y;
|
||||
max_val = key_y;
|
||||
got_val = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (got_val) {
|
||||
r.setTop(min_val - this->height());
|
||||
r.setBottom(max_val + this->height());
|
||||
}
|
||||
}
|
||||
|
||||
qreal CurveView::GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key)
|
||||
@@ -392,7 +407,8 @@ void CurveView::KeyframeDragStart(QMouseEvent *event)
|
||||
{
|
||||
drag_keyframe_values_.resize(GetSelectedKeyframes().size());
|
||||
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
|
||||
drag_keyframe_values_[i] = GetSelectedKeyframes().at(i)->value();
|
||||
NodeKeyframe *key = GetSelectedKeyframes().at(i);
|
||||
drag_keyframe_values_[i] = key->value();
|
||||
}
|
||||
|
||||
drag_start_ = mapToScene(event->pos());
|
||||
@@ -401,7 +417,11 @@ void CurveView::KeyframeDragStart(QMouseEvent *event)
|
||||
void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip)
|
||||
{
|
||||
if (event->modifiers() & Qt::ShiftModifier) {
|
||||
// Lock to X axis only
|
||||
// Lock to X axis only and set original values on all keys
|
||||
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
|
||||
NodeKeyframe *key = GetSelectedKeyframes().at(i);
|
||||
key->set_value(drag_keyframe_values_.at(i));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -412,10 +432,11 @@ void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip)
|
||||
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
|
||||
NodeKeyframe *key = GetSelectedKeyframes().at(i);
|
||||
|
||||
FloatSlider::DisplayType display = GetFloatDisplayTypeFromKeyframe(key);
|
||||
Node* node = key->parent();
|
||||
double original_val = drag_keyframe_values_.at(i).toDouble();
|
||||
double original_val = FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).toDouble(), display);
|
||||
const QString& input = key->input();
|
||||
double new_val = original_val - scaled_diff;
|
||||
double new_val = FloatSlider::TransformDisplayToValue(original_val - scaled_diff, display);
|
||||
double limited = new_val;
|
||||
|
||||
if (node->HasInputProperty(input, QStringLiteral("min"))) {
|
||||
@@ -434,23 +455,18 @@ void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip)
|
||||
// Set values
|
||||
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
|
||||
NodeKeyframe *key = GetSelectedKeyframes().at(i);
|
||||
key->set_value(drag_keyframe_values_.at(i).toDouble() - scaled_diff);
|
||||
FloatSlider::DisplayType display = GetFloatDisplayTypeFromKeyframe(key);
|
||||
key->set_value(FloatSlider::TransformDisplayToValue(FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).toDouble(), display) - scaled_diff, display));
|
||||
}
|
||||
|
||||
NodeKeyframe *tip_item = GetSelectedKeyframes().first();
|
||||
FloatSlider::DisplayType display_type = FloatSlider::kNormal;
|
||||
Node* initial_drag_input = tip_item->parent();
|
||||
const QString& initial_drag_input_id = tip_item->input();
|
||||
if (initial_drag_input->HasInputProperty(initial_drag_input_id, QStringLiteral("view"))) {
|
||||
display_type = static_cast<FloatSlider::DisplayType>(initial_drag_input->GetInputProperty(initial_drag_input_id, QStringLiteral("view")).toInt());
|
||||
}
|
||||
|
||||
bool ok;
|
||||
double num_value = tip_item->value().toDouble(&ok);
|
||||
|
||||
if (ok) {
|
||||
tip = QStringLiteral("%1\n");
|
||||
tip.append(FloatSlider::ValueToString(num_value, display_type, 2, true));
|
||||
tip.append(FloatSlider::ValueToString(num_value + GetOffsetFromKeyframe(tip_item), GetFloatDisplayTypeFromKeyframe(tip_item), 2, true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,7 +474,9 @@ void CurveView::KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *comman
|
||||
{
|
||||
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
|
||||
NodeKeyframe *k = GetSelectedKeyframes().at(i);
|
||||
command->add_child(new NodeParamSetKeyframeValueCommand(k, k->value(), drag_keyframe_values_.at(i)));
|
||||
if (!qFuzzyCompare(k->value().toDouble(), drag_keyframe_values_.at(i).toDouble())) {
|
||||
command->add_child(new NodeParamSetKeyframeValueCommand(k, k->value(), drag_keyframe_values_.at(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,53 +506,88 @@ QPointF CurveView::GetScaledCursorPos(const QPointF &cursor_pos)
|
||||
cursor_pos.y() / GetYScale());
|
||||
}
|
||||
|
||||
void CurveView::ZoomToFitInternal(const QVector<NodeKeyframe *> &keys)
|
||||
void CurveView::ZoomToFitInternal(bool selected_only)
|
||||
{
|
||||
if (keys.isEmpty()) {
|
||||
// Prevent scaling to DBL_MIN/DBL_MAX
|
||||
return;
|
||||
bool got_val = false;
|
||||
|
||||
rational min_time, max_time;
|
||||
double min_val, max_val;
|
||||
|
||||
foreach (KeyframeViewInputConnection *con, track_connections_) {
|
||||
foreach (NodeKeyframe *key, con->GetKeyframes()) {
|
||||
if (!selected_only || IsKeyframeSelected(key)) {
|
||||
rational transformed_time = GetAdjustedTime(key->parent(),
|
||||
GetTimeTarget(),
|
||||
key->time(),
|
||||
false);
|
||||
|
||||
qreal key_y = GetUnscaledItemYFromKeyframeValue(key);
|
||||
|
||||
if (got_val) {
|
||||
min_time = qMin(transformed_time, min_time);
|
||||
max_time = qMax(transformed_time, max_time);
|
||||
|
||||
min_val = qMin(key_y, min_val);
|
||||
max_val = qMax(key_y, max_val);
|
||||
} else {
|
||||
min_time = transformed_time;
|
||||
max_time = transformed_time;
|
||||
|
||||
min_val = key_y;
|
||||
max_val = key_y;
|
||||
|
||||
got_val = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rational min_time = RATIONAL_MAX;
|
||||
rational max_time = RATIONAL_MIN;
|
||||
// Prevent scaling if no keyframes were found
|
||||
if (got_val) {
|
||||
QRectF desired(QPointF(min_time.toDouble(), min_val), QPointF(max_time.toDouble(), max_val));
|
||||
|
||||
double min_val = DBL_MAX;
|
||||
double max_val = DBL_MIN;
|
||||
const double scale_divider = 0.5;
|
||||
double scale_half_divider = scale_divider*0.5;
|
||||
|
||||
foreach (NodeKeyframe* key, keys) {
|
||||
rational transformed_time = GetAdjustedTime(key->parent(),
|
||||
GetTimeTarget(),
|
||||
key->time(),
|
||||
false);
|
||||
double new_x_scale = viewport()->width() / desired.width() * scale_divider;
|
||||
double new_y_scale;
|
||||
|
||||
min_time = qMin(transformed_time, min_time);
|
||||
max_time = qMax(transformed_time, max_time);
|
||||
if (qFuzzyIsNull(desired.height())) {
|
||||
// Catch divide by zero
|
||||
new_y_scale = 1.0;
|
||||
scale_half_divider = 0.5;
|
||||
} else {
|
||||
// Use height as normal
|
||||
new_y_scale = viewport()->height() / desired.height() * scale_divider;
|
||||
}
|
||||
|
||||
min_val = qMin(key->value().toDouble(), min_val);
|
||||
max_val = qMax(key->value().toDouble(), max_val);
|
||||
emit ScaleChanged(new_x_scale);
|
||||
SetYScale(new_y_scale);
|
||||
|
||||
UpdateSceneRect();
|
||||
|
||||
int sb_x = desired.left() * new_x_scale - viewport()->width() * scale_half_divider;
|
||||
QMetaObject::invokeMethod(horizontalScrollBar(), "setValue", Qt::QueuedConnection, Q_ARG(int, sb_x));
|
||||
|
||||
int sb_y = desired.top() * new_y_scale - viewport()->height() * scale_half_divider;
|
||||
QMetaObject::invokeMethod(verticalScrollBar(), "setValue", Qt::QueuedConnection, Q_ARG(int, sb_y));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
QMetaObject::invokeMethod(horizontalScrollBar(), "setValue", Qt::QueuedConnection,
|
||||
Q_ARG(int, TimeToScene(min_time) - CalculatePaddingFromDimensionScale(this->width())));
|
||||
QMetaObject::invokeMethod(verticalScrollBar(), "setValue", Qt::QueuedConnection,
|
||||
Q_ARG(int, GetItemYFromKeyframeValue(max_val) - CalculatePaddingFromDimensionScale(this->height())));
|
||||
}
|
||||
|
||||
qreal CurveView::GetItemYFromKeyframeValue(NodeKeyframe *key)
|
||||
{
|
||||
return GetItemYFromKeyframeValue(key->value().toDouble());
|
||||
return GetUnscaledItemYFromKeyframeValue(key) * GetYScale();
|
||||
}
|
||||
|
||||
qreal CurveView::GetItemYFromKeyframeValue(double value)
|
||||
qreal CurveView::GetUnscaledItemYFromKeyframeValue(NodeKeyframe *key)
|
||||
{
|
||||
return -value * GetYScale();
|
||||
double val = key->value().toDouble();
|
||||
|
||||
val = FloatSlider::TransformValueToDisplay(val, GetFloatDisplayTypeFromKeyframe(key));
|
||||
|
||||
val += GetOffsetFromKeyframe(key);
|
||||
|
||||
return -val;
|
||||
}
|
||||
|
||||
QPointF CurveView::ScalePoint(const QPointF &point)
|
||||
@@ -543,41 +596,48 @@ QPointF CurveView::ScalePoint(const QPointF &point)
|
||||
return QPointF(point.x() * GetScale(), - point.y() * GetYScale());
|
||||
}
|
||||
|
||||
FloatSlider::DisplayType CurveView::GetFloatDisplayTypeFromKeyframe(NodeKeyframe *key)
|
||||
{
|
||||
Node* node = key->parent();
|
||||
const QString& input = key->input();
|
||||
if (node->HasInputProperty(input, QStringLiteral("view"))) {
|
||||
// Try to get view from input (which will be normal if unset)
|
||||
return static_cast<FloatSlider::DisplayType>(node->GetInputProperty(input, QStringLiteral("view")).toInt());
|
||||
}
|
||||
|
||||
// Fallback to normal
|
||||
return FloatSlider::kNormal;
|
||||
}
|
||||
|
||||
double CurveView::GetOffsetFromKeyframe(NodeKeyframe *key)
|
||||
{
|
||||
Node *node = key->parent();
|
||||
const QString &input = key->input();
|
||||
if (node->HasInputProperty(input, QStringLiteral("offset"))) {
|
||||
QVariant v = node->GetInputProperty(input, QStringLiteral("offset"));
|
||||
|
||||
// NOTE: Implement getting correct offset for the track based on the data type
|
||||
QVector<QVariant> track_vals = NodeValue::split_normal_value_into_track_values(node->GetInputDataType(input), v);
|
||||
|
||||
return track_vals.at(key->track()).toDouble();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
QPointF CurveView::GetKeyframePosition(NodeKeyframe *key)
|
||||
{
|
||||
return QPointF(GetKeyframeSceneX(key), GetItemYFromKeyframeValue(key));
|
||||
}
|
||||
|
||||
void CurveView::KeyframeTypeChanged()
|
||||
{
|
||||
qDebug() << "STUB!";
|
||||
/*NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
|
||||
KeyframeViewItem* item = item_map().value(key);
|
||||
|
||||
if (item->isSelected()) {
|
||||
item->setSelected(false);
|
||||
item->setSelected(true);
|
||||
}*/
|
||||
}
|
||||
|
||||
void CurveView::ZoomToFit()
|
||||
{
|
||||
QVector<NodeKeyframe*> keys;
|
||||
|
||||
foreach (KeyframeViewInputConnection *con, track_connections_) {
|
||||
foreach (NodeKeyframe *k, con->GetKeyframes()) {
|
||||
if (!keys.contains(k)) {
|
||||
keys.append(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ZoomToFitInternal(keys);
|
||||
ZoomToFitInternal(false);
|
||||
}
|
||||
|
||||
void CurveView::ZoomToFitSelected()
|
||||
{
|
||||
ZoomToFitInternal(GetSelectedKeyframes());
|
||||
ZoomToFitInternal(true);
|
||||
}
|
||||
|
||||
void CurveView::ResetZoom()
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "node/keyframe.h"
|
||||
#include "widget/keyframeview/keyframeview.h"
|
||||
#include "widget/slider/floatslider.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -38,8 +39,6 @@ public:
|
||||
|
||||
void SelectKeyframesOfInput(const NodeKeyframeTrackReference &ref);
|
||||
|
||||
void ZoomToFitInput(const NodeKeyframeTrackReference &ref);
|
||||
|
||||
void SetKeyframeTrackColor(const NodeKeyframeTrackReference& ref, const QColor& color);
|
||||
|
||||
public slots:
|
||||
@@ -70,13 +69,17 @@ protected:
|
||||
virtual void KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command) override;
|
||||
|
||||
private:
|
||||
void ZoomToFitInternal(const QVector<NodeKeyframe *> &keys);
|
||||
void ZoomToFitInternal(bool selected_only);
|
||||
|
||||
qreal GetItemYFromKeyframeValue(NodeKeyframe* key);
|
||||
qreal GetItemYFromKeyframeValue(double value);
|
||||
qreal GetUnscaledItemYFromKeyframeValue(NodeKeyframe* key);
|
||||
|
||||
QPointF ScalePoint(const QPointF& point);
|
||||
|
||||
static FloatSlider::DisplayType GetFloatDisplayTypeFromKeyframe(NodeKeyframe *key);
|
||||
|
||||
static double GetOffsetFromKeyframe(NodeKeyframe *key);
|
||||
|
||||
void AdjustLines();
|
||||
|
||||
QPointF GetKeyframePosition(NodeKeyframe *key);
|
||||
@@ -112,9 +115,6 @@ private:
|
||||
|
||||
QVector<QVariant> drag_keyframe_values_;
|
||||
|
||||
private slots:
|
||||
void KeyframeTypeChanged();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -47,10 +47,7 @@ CurveWidget::CurveWidget(QWidget *parent) :
|
||||
tree_view_ = new NodeTreeView();
|
||||
tree_view_->SetOnlyShowKeyframable(true);
|
||||
tree_view_->SetShowKeyframeTracksAsRows(true);
|
||||
connect(tree_view_, &NodeTreeView::NodeEnableChanged, this, &CurveWidget::NodeEnabledChanged);
|
||||
connect(tree_view_, &NodeTreeView::InputEnableChanged, this, &CurveWidget::InputEnabledChanged);
|
||||
connect(tree_view_, &NodeTreeView::InputSelectionChanged, this, &CurveWidget::InputSelectionChanged);
|
||||
connect(tree_view_, &NodeTreeView::InputDoubleClicked, this, &CurveWidget::InputDoubleClicked);
|
||||
splitter->addWidget(tree_view_);
|
||||
|
||||
QWidget* workarea = new QWidget();
|
||||
@@ -101,7 +98,7 @@ CurveWidget::CurveWidget(QWidget *parent) :
|
||||
|
||||
// Connect ruler and view together
|
||||
connect(view_, &CurveView::TimeChanged, this, &CurveWidget::SetTimeAndSignal);
|
||||
connect(view_->scene(), &QGraphicsScene::selectionChanged, this, &CurveWidget::SelectionChanged);
|
||||
connect(view_, &CurveView::SelectionChanged, this, &CurveWidget::SelectionChanged);
|
||||
connect(view_, &CurveView::ScaleChanged, this, &CurveWidget::SetScale);
|
||||
connect(view_, &CurveView::Dragged, this, &CurveWidget::KeyframeViewDragged);
|
||||
|
||||
@@ -176,6 +173,8 @@ void CurveWidget::ScaleChangedEvent(const double &scale)
|
||||
|
||||
void CurveWidget::TimeTargetChangedEvent(Node *target)
|
||||
{
|
||||
TimeTargetObject::TimeTargetChangedEvent(target);
|
||||
|
||||
key_control_->SetTimeTarget(target);
|
||||
|
||||
view_->SetTimeTarget(target);
|
||||
@@ -183,6 +182,8 @@ void CurveWidget::TimeTargetChangedEvent(Node *target)
|
||||
|
||||
void CurveWidget::ConnectedNodeChangeEvent(ViewerOutput *n)
|
||||
{
|
||||
super::ConnectedNodeChangeEvent(n);
|
||||
|
||||
SetTimeTarget(n);
|
||||
}
|
||||
|
||||
@@ -215,7 +216,7 @@ void CurveWidget::UpdateBridgeTime(const rational &time)
|
||||
void CurveWidget::ConnectNode(Node *node, bool connect)
|
||||
{
|
||||
foreach (const QString& input, node->inputs()) {
|
||||
if (node->IsInputKeyframable(input)) {
|
||||
if (node->IsInputKeyframable(input) && !node->IsInputHidden(input)) {
|
||||
ConnectInput(node, input, connect);
|
||||
}
|
||||
}
|
||||
@@ -228,9 +229,6 @@ void CurveWidget::ConnectInput(Node *node, const QString &input, bool connect)
|
||||
return;
|
||||
}
|
||||
|
||||
int track_count = NodeValue::get_number_of_keyframe_tracks(node->GetInputDataType(input));
|
||||
bool multiple_tracks = track_count > 1;
|
||||
|
||||
int arr_sz = node->InputArraySize(input);
|
||||
for (int i=-1; i<arr_sz; i++) {
|
||||
// Generate a random color for this input
|
||||
@@ -247,28 +245,6 @@ void CurveWidget::ConnectInput(Node *node, const QString &input, bool connect)
|
||||
view_->SetKeyframeTrackColor(ref, c);
|
||||
}
|
||||
}
|
||||
|
||||
if (tree_view_->IsInputEnabled(NodeKeyframeTrackReference(NodeInput(node, input, i), multiple_tracks ? -1 : 0))) {
|
||||
if (multiple_tracks) {
|
||||
for (int j=0; j<track_count; j++) {
|
||||
NodeKeyframeTrackReference ref(NodeInput(node, input, i), j);
|
||||
if (tree_view_->IsInputEnabled(ref)) {
|
||||
if (connect) {
|
||||
view_->ConnectInput(ref);
|
||||
} else {
|
||||
view_->DisconnectInput(ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NodeKeyframeTrackReference ref(NodeInput(node, input, i), 0);
|
||||
if (connect) {
|
||||
view_->ConnectInput(ref);
|
||||
} else {
|
||||
view_->DisconnectInput(ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,32 +315,35 @@ void CurveWidget::KeyframeTypeButtonTriggered(bool checked)
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
}
|
||||
|
||||
void CurveWidget::NodeEnabledChanged(Node* n, bool e)
|
||||
{
|
||||
ConnectNode(n, e);
|
||||
}
|
||||
|
||||
void CurveWidget::InputEnabledChanged(const NodeKeyframeTrackReference& ref, bool e)
|
||||
{
|
||||
if (e) {
|
||||
view_->ConnectInput(ref);
|
||||
} else {
|
||||
view_->DisconnectInput(ref);
|
||||
}
|
||||
}
|
||||
|
||||
void CurveWidget::InputSelectionChanged(const NodeKeyframeTrackReference& ref)
|
||||
{
|
||||
key_control_->SetInput(ref.input());
|
||||
|
||||
if (ref.IsValid()) {
|
||||
view_->SelectKeyframesOfInput(ref);
|
||||
foreach (const NodeKeyframeTrackReference &c, selected_tracks_) {
|
||||
view_->DisconnectInput(c);
|
||||
}
|
||||
}
|
||||
|
||||
void CurveWidget::InputDoubleClicked(const NodeKeyframeTrackReference& ref)
|
||||
{
|
||||
view_->ZoomToFitInput(ref);
|
||||
selected_tracks_.clear();
|
||||
|
||||
if (ref.IsValid()) {
|
||||
view_->ConnectInput(ref);
|
||||
selected_tracks_.append(ref);
|
||||
} else if (ref.input().IsValid()) {
|
||||
int track_count = NodeValue::get_number_of_keyframe_tracks(ref.input().GetDataType());
|
||||
for (int i=0; i<track_count; i++) {
|
||||
NodeKeyframeTrackReference track_ref(ref.input(), i);
|
||||
view_->ConnectInput(track_ref);
|
||||
selected_tracks_.append(track_ref);
|
||||
}
|
||||
} else if (Node *node = ref.input().node()) {
|
||||
foreach (const QString &input, node->inputs()) {
|
||||
if (!node->IsInputKeyframable(input) || node->IsInputHidden(input)) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view_->ZoomToFit();
|
||||
}
|
||||
|
||||
void CurveWidget::KeyframeViewDragged(int x, int y)
|
||||
|
||||
@@ -96,19 +96,15 @@ private:
|
||||
|
||||
QVector<Node*> nodes_;
|
||||
|
||||
QVector<NodeKeyframeTrackReference> selected_tracks_;
|
||||
|
||||
private slots:
|
||||
void SelectionChanged();
|
||||
|
||||
void KeyframeTypeButtonTriggered(bool checked);
|
||||
|
||||
void NodeEnabledChanged(Node* n, bool e);
|
||||
|
||||
void InputEnabledChanged(const NodeKeyframeTrackReference &ref, bool e);
|
||||
|
||||
void InputSelectionChanged(const NodeKeyframeTrackReference& ref);
|
||||
|
||||
void InputDoubleClicked(const NodeKeyframeTrackReference& ref);
|
||||
|
||||
void KeyframeViewDragged(int x, int y);
|
||||
|
||||
void CatchUpYScrollToPoint(int point);
|
||||
|
||||
@@ -116,6 +116,7 @@ void KeyframeView::RemoveKeyframesOfTrack(KeyframeViewInputConnection *connectio
|
||||
}
|
||||
delete connection;
|
||||
Redraw();
|
||||
emit SelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +158,8 @@ void KeyframeView::SelectionManagerSelectEvent(void *obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit SelectionChanged();
|
||||
}
|
||||
|
||||
void KeyframeView::SelectionManagerDeselectEvent(void *obj)
|
||||
@@ -170,6 +173,8 @@ void KeyframeView::SelectionManagerDeselectEvent(void *obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit SelectionChanged();
|
||||
}
|
||||
|
||||
void KeyframeView::mousePressEvent(QMouseEvent *event)
|
||||
@@ -235,6 +240,7 @@ void KeyframeView::mouseReleaseEvent(QMouseEvent *event)
|
||||
} else if (selection_manager_.IsRubberBanding()) {
|
||||
selection_manager_.RubberBandStop();
|
||||
Redraw();
|
||||
emit SelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +331,8 @@ void KeyframeView::SelectKeyframe(NodeKeyframe *key)
|
||||
{
|
||||
if (selection_manager_.Select(key)) {
|
||||
Redraw();
|
||||
|
||||
emit SelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,6 +340,8 @@ void KeyframeView::DeselectKeyframe(NodeKeyframe *key)
|
||||
{
|
||||
if (selection_manager_.Deselect(key)) {
|
||||
Redraw();
|
||||
|
||||
emit SelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,8 @@ public:
|
||||
signals:
|
||||
void Dragged(int current_x, int current_y);
|
||||
|
||||
void SelectionChanged();
|
||||
|
||||
protected:
|
||||
virtual void mousePressEvent(QMouseEvent *event) override;
|
||||
virtual void mouseMoveEvent(QMouseEvent *event) override;
|
||||
|
||||
@@ -38,6 +38,7 @@ KeyframeViewInputConnection::KeyframeViewInputConnection(const NodeKeyframeTrack
|
||||
connect(n, &Node::KeyframeRemoved, this, &KeyframeViewInputConnection::RemoveKeyframe);
|
||||
connect(n, &Node::KeyframeTimeChanged, this, &KeyframeViewInputConnection::KeyframeChanged);
|
||||
connect(n, &Node::KeyframeTypeChanged, this, &KeyframeViewInputConnection::KeyframeChanged);
|
||||
connect(n, &Node::KeyframeTypeChanged, this, &KeyframeViewInputConnection::KeyframeTypeChanged);
|
||||
connect(n, &Node::KeyframeValueChanged, this, &KeyframeViewInputConnection::KeyframeChanged);
|
||||
}
|
||||
|
||||
@@ -89,4 +90,11 @@ void KeyframeViewInputConnection::KeyframeChanged(NodeKeyframe *key)
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewInputConnection::KeyframeTypeChanged(NodeKeyframe *key)
|
||||
{
|
||||
if (key->key_track_ref() == input_) {
|
||||
emit TypeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ public:
|
||||
signals:
|
||||
void RequireUpdate();
|
||||
|
||||
void TypeChanged();
|
||||
|
||||
private:
|
||||
KeyframeView *keyframe_view_;
|
||||
|
||||
@@ -83,6 +85,8 @@ private slots:
|
||||
|
||||
void KeyframeChanged(NodeKeyframe *key);
|
||||
|
||||
void KeyframeTypeChanged(NodeKeyframe *key);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -417,11 +417,15 @@ void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item)
|
||||
QVector<QPair<NodeParamViewItem*, int> > distances;
|
||||
|
||||
for (auto it=context_item->GetItems().cbegin(); it!=context_item->GetItems().cend(); it++) {
|
||||
int distance = 0;
|
||||
int distance = -1;
|
||||
foreach (Node *ctx, context_item->GetContexts()) {
|
||||
distance = qMax(distance, GetDistanceBetweenNodes(ctx, it.key()));
|
||||
}
|
||||
|
||||
if (distance == -1) {
|
||||
distance = INT_MAX;
|
||||
}
|
||||
|
||||
bool inserted = false;
|
||||
QPair<NodeParamViewItem*, int> dist(it.value(), distance);
|
||||
|
||||
@@ -439,6 +443,7 @@ void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item)
|
||||
}
|
||||
|
||||
foreach (auto info, distances) {
|
||||
qDebug() << "Inserting" << info.first->GetNode() << "with distance" << info.second;
|
||||
context_item->GetDockArea()->AddItem(info.first);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,42 +648,12 @@ void NodeParamViewWidgetBridge::PropertyChanged(const QString& input, const QStr
|
||||
break;
|
||||
}
|
||||
} else if (key == QStringLiteral("offset")) {
|
||||
switch (data_type) {
|
||||
case NodeValue::kInt:
|
||||
static_cast<IntegerSlider*>(widgets_.first())->SetOffset(value);
|
||||
break;
|
||||
case NodeValue::kFloat:
|
||||
static_cast<FloatSlider*>(widgets_.first())->SetOffset(value);
|
||||
break;
|
||||
case NodeValue::kRational:
|
||||
static_cast<RationalSlider*>(widgets_.first())->SetOffset(value);
|
||||
break;
|
||||
case NodeValue::kVec2:
|
||||
{
|
||||
QVector2D offs = value.value<QVector2D>();
|
||||
static_cast<FloatSlider*>(widgets_.at(0))->SetOffset(offs.x());
|
||||
static_cast<FloatSlider*>(widgets_.at(1))->SetOffset(offs.y());
|
||||
break;
|
||||
}
|
||||
case NodeValue::kVec3:
|
||||
{
|
||||
QVector3D offs = value.value<QVector3D>();
|
||||
static_cast<FloatSlider*>(widgets_.at(0))->SetOffset(offs.x());
|
||||
static_cast<FloatSlider*>(widgets_.at(1))->SetOffset(offs.y());
|
||||
static_cast<FloatSlider*>(widgets_.at(2))->SetOffset(offs.z());
|
||||
break;
|
||||
}
|
||||
case NodeValue::kVec4:
|
||||
{
|
||||
QVector4D offs = value.value<QVector4D>();
|
||||
static_cast<FloatSlider*>(widgets_.at(0))->SetOffset(offs.x());
|
||||
static_cast<FloatSlider*>(widgets_.at(1))->SetOffset(offs.y());
|
||||
static_cast<FloatSlider*>(widgets_.at(2))->SetOffset(offs.z());
|
||||
static_cast<FloatSlider*>(widgets_.at(3))->SetOffset(offs.w());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
int tracks = NodeValue::get_number_of_keyframe_tracks(data_type);
|
||||
|
||||
QVector<QVariant> offsets = NodeValue::split_normal_value_into_track_values(data_type, value);
|
||||
|
||||
for (int i=0; i<tracks; i++) {
|
||||
static_cast<NumericSliderBase*>(widgets_.at(i))->SetOffset(offsets.at(i));
|
||||
}
|
||||
|
||||
UpdateWidgetValues();
|
||||
|
||||
@@ -27,7 +27,8 @@ namespace olive {
|
||||
NodeTreeView::NodeTreeView(QWidget *parent) :
|
||||
QTreeWidget(parent),
|
||||
only_show_keyframable_(false),
|
||||
show_keyframe_tracks_as_rows_(false)
|
||||
show_keyframe_tracks_as_rows_(false),
|
||||
checkboxes_enabled_(false)
|
||||
{
|
||||
connect(this, &NodeTreeView::itemChanged, this, &NodeTreeView::ItemCheckStateChanged);
|
||||
connect(this, &NodeTreeView::itemSelectionChanged, this, &NodeTreeView::SelectionChanged);
|
||||
@@ -67,12 +68,14 @@ void NodeTreeView::SetNodes(const QVector<Node *> &nodes)
|
||||
foreach (Node* n, nodes_) {
|
||||
QTreeWidgetItem* node_item = new QTreeWidgetItem();
|
||||
node_item->setText(0, n->Name());
|
||||
node_item->setCheckState(0, disabled_nodes_.contains(n) ? Qt::Unchecked : Qt::Checked);
|
||||
if (checkboxes_enabled_) {
|
||||
node_item->setCheckState(0, disabled_nodes_.contains(n) ? Qt::Unchecked : Qt::Checked);
|
||||
}
|
||||
node_item->setData(0, kItemType, kItemTypeNode);
|
||||
node_item->setData(0, kItemNodePointer, Node::PtrToValue(n));
|
||||
|
||||
foreach (const QString& input, n->inputs()) {
|
||||
if (only_show_keyframable_ && !n->IsInputKeyframable(input)) {
|
||||
if (n->IsInputHidden(input) || (only_show_keyframable_ && !n->IsInputKeyframable(input))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -105,7 +108,6 @@ void NodeTreeView::SetNodes(const QVector<Node *> &nodes)
|
||||
CreateItemsForTracks(element_item, input_ref, key_tracks.size());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add at the end to prevent unnecessary signalling while we're setting these objects up
|
||||
@@ -114,9 +116,9 @@ void NodeTreeView::SetNodes(const QVector<Node *> &nodes)
|
||||
} else {
|
||||
delete node_item;
|
||||
}
|
||||
|
||||
node_item->setExpanded(true);
|
||||
}
|
||||
|
||||
expandAll();
|
||||
}
|
||||
|
||||
void NodeTreeView::changeEvent(QEvent *e)
|
||||
@@ -155,6 +157,8 @@ NodeKeyframeTrackReference NodeTreeView::GetSelectedInput()
|
||||
|
||||
if (item->data(0, kItemType).toInt() == kItemTypeInput) {
|
||||
selected_ref = item->data(0, kItemInputReference).value<NodeKeyframeTrackReference>();
|
||||
} else {
|
||||
selected_ref = NodeKeyframeTrackReference(NodeInput(Node::ValueToPtr<Node>(item->data(0, kItemNodePointer)), QString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,21 +170,27 @@ QTreeWidgetItem* NodeTreeView::CreateItem(QTreeWidgetItem *parent, const NodeKey
|
||||
QTreeWidgetItem* input_item = new QTreeWidgetItem(parent);
|
||||
|
||||
QString item_name;
|
||||
if (ref.track() == -1 || NodeValue::get_number_of_keyframe_tracks(ref.input().GetDataType()) == 1) {
|
||||
item_name = ref.input().name();
|
||||
if (ref.track() == -1
|
||||
|| NodeValue::get_number_of_keyframe_tracks(ref.input().GetDataType()) == 1
|
||||
|| (ref.input().IsArray() && ref.input().element() == -1)) {
|
||||
if (ref.input().element() == -1) {
|
||||
item_name = ref.input().name();
|
||||
} else {
|
||||
item_name = QString::number(ref.input().element());
|
||||
}
|
||||
} else {
|
||||
switch (ref.track()) {
|
||||
case 0:
|
||||
item_name = tr("X");
|
||||
item_name = UseRGBAOverXYZW(ref) ? tr("R") : tr("X");
|
||||
break;
|
||||
case 1:
|
||||
item_name = tr("Y");
|
||||
item_name = UseRGBAOverXYZW(ref) ? tr("G") : tr("Y");
|
||||
break;
|
||||
case 2:
|
||||
item_name = tr("Z");
|
||||
item_name = UseRGBAOverXYZW(ref) ? tr("B") : tr("Z");
|
||||
break;
|
||||
case 3:
|
||||
item_name = tr("W");
|
||||
item_name = UseRGBAOverXYZW(ref) ? tr("A") : tr("W");
|
||||
break;
|
||||
default:
|
||||
item_name = QString::number(ref.track());
|
||||
@@ -188,7 +198,9 @@ QTreeWidgetItem* NodeTreeView::CreateItem(QTreeWidgetItem *parent, const NodeKey
|
||||
}
|
||||
input_item->setText(0, item_name);
|
||||
|
||||
input_item->setCheckState(0, disabled_inputs_.contains(ref) ? Qt::Unchecked : Qt::Checked);
|
||||
if (checkboxes_enabled_) {
|
||||
input_item->setCheckState(0, disabled_inputs_.contains(ref) ? Qt::Unchecked : Qt::Checked);
|
||||
}
|
||||
input_item->setData(0, kItemType, kItemTypeInput);
|
||||
input_item->setData(0, kItemInputReference, QVariant::fromValue(ref));
|
||||
|
||||
@@ -208,6 +220,11 @@ void NodeTreeView::CreateItemsForTracks(QTreeWidgetItem *parent, const NodeInput
|
||||
}
|
||||
}
|
||||
|
||||
bool NodeTreeView::UseRGBAOverXYZW(const NodeKeyframeTrackReference &ref)
|
||||
{
|
||||
return ref.input().GetDataType() == NodeValue::kColor;
|
||||
}
|
||||
|
||||
void NodeTreeView::ItemCheckStateChanged(QTreeWidgetItem *item, int column)
|
||||
{
|
||||
Q_UNUSED(column)
|
||||
|
||||
@@ -37,6 +37,11 @@ public:
|
||||
|
||||
bool IsInputEnabled(const NodeKeyframeTrackReference& ref) const;
|
||||
|
||||
void SetCheckBoxesEnabled(bool e)
|
||||
{
|
||||
checkboxes_enabled_ = e;
|
||||
}
|
||||
|
||||
void SetKeyframeTrackColor(const NodeKeyframeTrackReference& ref, const QColor& color);
|
||||
|
||||
void SetOnlyShowKeyframable(bool e)
|
||||
@@ -75,6 +80,8 @@ private:
|
||||
|
||||
void CreateItemsForTracks(QTreeWidgetItem* parent, const NodeInput& input, int track_count);
|
||||
|
||||
static bool UseRGBAOverXYZW(const NodeKeyframeTrackReference &ref);
|
||||
|
||||
enum ItemType {
|
||||
kItemTypeNode,
|
||||
kItemTypeInput
|
||||
@@ -98,6 +105,8 @@ private:
|
||||
|
||||
QHash<NodeKeyframeTrackReference, QColor> keyframe_colors_;
|
||||
|
||||
bool checkboxes_enabled_;
|
||||
|
||||
private slots:
|
||||
void ItemCheckStateChanged(QTreeWidgetItem* item, int column);
|
||||
|
||||
|
||||
@@ -78,29 +78,46 @@ void FloatSlider::SetDisplayType(const FloatSlider::DisplayType &type)
|
||||
}
|
||||
}
|
||||
|
||||
QString FloatSlider::ValueToString(double val, FloatSlider::DisplayType display, int decimal_places, bool autotrim_decimal_places)
|
||||
double FloatSlider::TransformValueToDisplay(double val, DisplayType display)
|
||||
{
|
||||
switch (display) {
|
||||
case kNormal:
|
||||
// Do nothing, skip to the return string at the end
|
||||
break;
|
||||
case kDecibel:
|
||||
// Convert to decibels and return dB formatted string
|
||||
|
||||
// Return negative infinity for zero volume
|
||||
if (qIsNull(val)) {
|
||||
return tr("\xE2\x88\x9E");
|
||||
}
|
||||
|
||||
val = Decibel::fromLinear(val);
|
||||
break;
|
||||
case kPercentage:
|
||||
// Multiply value by 100 for user-friendly percentage
|
||||
val *= 100.0;
|
||||
break;
|
||||
}
|
||||
|
||||
return FloatToString(val, decimal_places, autotrim_decimal_places);
|
||||
return val;
|
||||
}
|
||||
|
||||
double FloatSlider::TransformDisplayToValue(double val, DisplayType display)
|
||||
{
|
||||
switch (display) {
|
||||
case kNormal:
|
||||
break;
|
||||
case kDecibel:
|
||||
val = Decibel::toLinear(val);
|
||||
break;
|
||||
case kPercentage:
|
||||
val *= 0.01;
|
||||
break;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
QString FloatSlider::ValueToString(double val, FloatSlider::DisplayType display, int decimal_places, bool autotrim_decimal_places)
|
||||
{
|
||||
// Return negative infinity for zero volume
|
||||
if (display == kDecibel && qIsNull(val)) {
|
||||
return tr("\xE2\x88\x9E");
|
||||
}
|
||||
|
||||
return FloatToString(TransformValueToDisplay(val, display), decimal_places, autotrim_decimal_places);
|
||||
}
|
||||
|
||||
QString FloatSlider::ValueToString(const QVariant &v) const
|
||||
@@ -110,46 +127,21 @@ QString FloatSlider::ValueToString(const QVariant &v) const
|
||||
|
||||
QVariant FloatSlider::StringToValue(const QString &s, bool *ok) const
|
||||
{
|
||||
switch (display_type_) {
|
||||
case kNormal:
|
||||
// Do nothing, skip to the return string at the end
|
||||
break;
|
||||
case kDecibel:
|
||||
{
|
||||
bool valid;
|
||||
bool valid;
|
||||
double val = s.toDouble(&valid);
|
||||
|
||||
// See if we can get a decimal number out of this
|
||||
qreal decibels = s.toDouble(&valid);
|
||||
|
||||
if (ok) *ok = valid;
|
||||
|
||||
if (valid) {
|
||||
// Convert from decibel scale to linear decimal
|
||||
return Decibel::toLinear(decibels);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case kPercentage:
|
||||
{
|
||||
bool valid;
|
||||
|
||||
// Try to get double value
|
||||
double val = s.toDouble(&valid);
|
||||
|
||||
if (ok) *ok = valid;
|
||||
|
||||
// If we could get it, convert back to a 0.0 - 1.0 value and return
|
||||
if (valid) {
|
||||
return val * 0.01;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// If we were given an `ok` pointer, set it to `valid`
|
||||
if (ok) {
|
||||
*ok = valid;
|
||||
}
|
||||
|
||||
// Just try to convert the string to a double
|
||||
return s.toDouble(ok) - GetOffset().toDouble();
|
||||
// If valid, transform it from display
|
||||
if (valid) {
|
||||
val = TransformDisplayToValue(val, display_type_);
|
||||
}
|
||||
|
||||
// Return un-offset value
|
||||
return val - GetOffset().toDouble();
|
||||
}
|
||||
|
||||
QVariant FloatSlider::AdjustDragDistanceInternal(const QVariant &start, const double &drag) const
|
||||
|
||||
@@ -49,6 +49,10 @@ public:
|
||||
|
||||
void SetDisplayType(const DisplayType& type);
|
||||
|
||||
static double TransformValueToDisplay(double val, DisplayType display);
|
||||
|
||||
static double TransformDisplayToValue(double val, DisplayType display);
|
||||
|
||||
static QString ValueToString(double val, DisplayType display, int decimal_places, bool autotrim_decimal_places);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -62,6 +62,11 @@ public slots:
|
||||
|
||||
void SetEndTime(const rational& length);
|
||||
|
||||
/**
|
||||
* @brief Slot called whenever the view resizes or the scene contents change to enforce minimum scene sizes
|
||||
*/
|
||||
void UpdateSceneRect();
|
||||
|
||||
signals:
|
||||
void TimeChanged(const rational& time);
|
||||
|
||||
@@ -101,12 +106,6 @@ protected:
|
||||
y_axis_enabled_ = e;
|
||||
}
|
||||
|
||||
protected slots:
|
||||
/**
|
||||
* @brief Slot called whenever the view resizes or the scene contents change to enforce minimum scene sizes
|
||||
*/
|
||||
void UpdateSceneRect();
|
||||
|
||||
private:
|
||||
qreal GetPlayheadX();
|
||||
|
||||
|
||||
@@ -92,7 +92,9 @@ public:
|
||||
|
||||
T *GetObjectAtPoint(const QPointF &scene_pt)
|
||||
{
|
||||
foreach (const DrawnObject &kp, drawn_objects_) {
|
||||
// Iterate in reverse order because the objects drawn later will appear on top to the user
|
||||
for (auto it=drawn_objects_.crbegin(); it!=drawn_objects_.crend(); it++) {
|
||||
const DrawnObject &kp = *it;
|
||||
if (kp.second.contains(scene_pt)) {
|
||||
return kp.first;
|
||||
}
|
||||
@@ -147,13 +149,11 @@ public:
|
||||
{
|
||||
initial_drag_item_ = initial_item;
|
||||
|
||||
dragging_.clear();
|
||||
|
||||
dragging_.resize(selected_.size());
|
||||
for (int i=0; i<selected_.size(); i++) {
|
||||
T *obj = selected_.at(i);
|
||||
|
||||
dragging_[i] = {obj->time()};
|
||||
dragging_[i] = {obj->time(), view_->TimeToScene(obj->time())};
|
||||
}
|
||||
|
||||
drag_mouse_start_ = view_->mapToScene(event->pos());
|
||||
@@ -164,15 +164,15 @@ public:
|
||||
QPointF diff = view_->mapToScene(event->pos()) - drag_mouse_start_;
|
||||
|
||||
for (int i=0; i<selected_.size(); i++) {
|
||||
const rational &old_time = dragging_.at(i).time;
|
||||
rational proposed_time = old_time + view_->SceneToTimeNoGrid(diff.x());
|
||||
rational proposed_time = view_->SceneToTimeNoGrid(dragging_.at(i).x + diff.x());
|
||||
T *sel = selected_.at(i);
|
||||
|
||||
// Magic number: use interval of 1ms to avoid collisions
|
||||
rational adj(1, 1000);
|
||||
if (old_time < proposed_time) {
|
||||
if (dragging_.at(i).time < proposed_time) {
|
||||
adj = -adj;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
NodeKeyframe *key_at_time = sel->parent()->GetKeyframeAtTimeOnTrack(sel->input(), proposed_time, sel->track(), sel->element());
|
||||
if (!key_at_time || key_at_time == sel) {
|
||||
@@ -303,6 +303,7 @@ private:
|
||||
struct DragObject
|
||||
{
|
||||
rational time;
|
||||
double x;
|
||||
};
|
||||
|
||||
QVector<DragObject> dragging_;
|
||||
|
||||
Reference in New Issue
Block a user