Commit 1de9399b authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

Refactor keyframe selection, now in sync between timeline and effect stack.

Related to #1118 #1227
parent eeaec722
Pipeline #92667 canceled with stage
......@@ -65,13 +65,18 @@ bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant value,
QVariant oldValue = m_keyframeList[pos].second;
local_undo = updateKeyframe_lambda(pos, oldType, oldValue, notify);
local_redo = updateKeyframe_lambda(pos, type, value, notify);
if (local_redo()) {
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
} else {
local_redo = addKeyframe_lambda(pos, type, value, notify);
local_undo = deleteKeyframe_lambda(pos, notify);
}
if (local_redo()) {
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
Fun redo_first = addKeyframe_lambda(pos, type, value, notify);
if (redo_first()) {
local_redo = addKeyframe_lambda(pos, type, value, true);
local_undo = deleteKeyframe_lambda(pos, true);
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
}
return false;
}
......@@ -100,7 +105,7 @@ bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant value)
return res;
}
bool KeyframeModel::removeKeyframe(GenTime pos, Fun &undo, Fun &redo, bool notify)
bool KeyframeModel::removeKeyframe(GenTime pos, Fun &undo, Fun &redo, bool notify, bool updateSelection)
{
qDebug() << "Going to remove keyframe at " << pos.frames(pCore->getCurrentFps()) << " NOTIFY: " << notify;
qDebug() << "before" << getAnimProperty();
......@@ -108,11 +113,46 @@ bool KeyframeModel::removeKeyframe(GenTime pos, Fun &undo, Fun &redo, bool notif
Q_ASSERT(m_keyframeList.count(pos) > 0);
KeyframeType oldType = m_keyframeList[pos].first;
QVariant oldValue = m_keyframeList[pos].second;
Fun local_undo = addKeyframe_lambda(pos, oldType, oldValue, notify);
Fun local_redo = deleteKeyframe_lambda(pos, notify);
if (local_redo()) {
Fun select_undo = []() { return true; };
Fun select_redo = []() { return true; };
if (updateSelection) {
if (auto ptr = m_model.lock()) {
if (!ptr->m_selectedKeyframes.isEmpty()) {
int ix = getIndexForPos(pos);
QVector<int> selection;
QVector<int> prevSelection = ptr->m_selectedKeyframes;
for (auto &kf : prevSelection) {
if (kf == ix) {
continue;
}
if (kf < ix) {
selection << kf;
} else {
selection << (kf - 1);
}
}
setActiveKeyframe(-1);
std::sort(selection.begin(), selection.end());
select_redo = [this, selection]() {
setSelectedKeyframes(selection);
return true;
};
select_undo = [this, prevSelection]() {
setSelectedKeyframes(prevSelection);
return true;
};
}
}
}
Fun redo_first = deleteKeyframe_lambda(pos, notify);
if (redo_first()) {
Fun local_undo = addKeyframe_lambda(pos, oldType, oldValue, true);
Fun local_redo = deleteKeyframe_lambda(pos, true);
select_redo();
qDebug() << "after" << getAnimProperty();
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
UPDATE_UNDO_REDO(select_redo, select_undo, undo, redo);
return true;
}
return false;
......@@ -156,7 +196,96 @@ bool KeyframeModel::removeKeyframe(GenTime pos)
return res;
}
bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, Fun &undo, Fun &redo)
GenTime KeyframeModel::getPosAtIndex(int ix) const
{
QList<GenTime> positions = getKeyframePos();
std::sort(positions.begin(), positions.end());
if (ix < 0 || ix >= positions.count()) {
return GenTime();
}
return positions.at(ix);
}
bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, Fun &undo, Fun &redo, bool updateView)
{
qDebug() << "starting to move keyframe" << oldPos.frames(pCore->getCurrentFps()) << pos.frames(pCore->getCurrentFps());
QWriteLocker locker(&m_lock);
// Check if we have several selected keyframes
if (oldPos == pos) {
if (!newVal.isValid()) {
// no change
return true;
}
}
if (auto ptr = m_model.lock()) {
if (ptr->m_selectedKeyframes.size() > 1) {
// We have several selected keyframes, move them all
QVector<GenTime> positions;
for (auto &kf : ptr->m_selectedKeyframes) {
if (kf > 0) {
positions << getPosAtIndex(kf);
}
}
GenTime delta = pos - oldPos;
if (pos > oldPos) {
// Moving right, reverse sort
std::sort(positions.rbegin(), positions.rend());
// Check max pos
bool ok = false;
GenTime test = positions.first();
auto next = getNextKeyframe(test, &ok);
if (ok) {
delta = qMin(delta, next.first - GenTime(1, pCore->getCurrentFps()) - test);
}
} else {
// Moving left
std::sort(positions.begin(), positions.end());
// Check min pos
bool ok = false;
GenTime test = positions.first();
auto next = getPrevKeyframe(test, &ok);
if (ok) {
delta = qMax(delta, (next.first + GenTime(1, pCore->getCurrentFps())) - test);
}
}
if (delta == GenTime()) {
if (!newVal.isValid()) {
// no change
return true;
}
}
bool res = true;
for (auto &p : positions) {
if (p == oldPos) {
res = res && moveOneKeyframe(oldPos, oldPos + delta, newVal, undo, redo, updateView);
} else {
res = res && moveOneKeyframe(p, p + delta, QVariant(), undo, redo, updateView);
}
}
return res;
} else {
if (pos > oldPos) {
// Moving right
bool ok = false;
auto next = getNextKeyframe(oldPos, &ok);
if (ok) {
pos = qMin(pos, next.first - GenTime(1, pCore->getCurrentFps()));
}
} else {
// Moving left
bool ok = false;
auto next = getPrevKeyframe(oldPos, &ok);
if (ok) {
pos = qMax(pos, next.first + GenTime(1, pCore->getCurrentFps()));
}
}
return moveOneKeyframe(oldPos, pos, newVal, undo, redo, updateView);
}
}
return false;
}
bool KeyframeModel::moveOneKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, Fun &undo, Fun &redo, bool updateView)
{
qDebug() << "starting to move keyframe" << oldPos.frames(pCore->getCurrentFps()) << pos.frames(pCore->getCurrentFps());
QWriteLocker locker(&m_lock);
......@@ -184,7 +313,7 @@ bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, F
Fun local_redo = []() { return true; };
qDebug() << getAnimProperty();
// TODO: use the new Animation::key_set_frame to move a keyframe
bool res = removeKeyframe(oldPos, local_undo, local_redo);
bool res = removeKeyframe(oldPos, local_undo, local_redo, true, false);
qDebug() << "Move keyframe finished deletion:" << res;
qDebug() << getAnimProperty();
if (res) {
......@@ -192,14 +321,14 @@ bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, F
if (!newVal.isValid()) {
newVal = oldValue;
}
res = addKeyframe(pos, oldType, newVal, true, local_undo, local_redo);
res = addKeyframe(pos, oldType, newVal, updateView, local_undo, local_redo);
} else if (newVal.isValid()) {
QVariant result = getNormalizedValue(newVal.toDouble());
if (result.isValid()) {
res = addKeyframe(pos, oldType, result, true, local_undo, local_redo);
res = addKeyframe(pos, oldType, result, updateView, local_undo, local_redo);
}
} else {
res = addKeyframe(pos, oldType, oldValue, true, local_undo, local_redo);
res = addKeyframe(pos, oldType, oldValue, updateView, local_undo, local_redo);
}
qDebug() << "Move keyframe finished insertion:" << res;
qDebug() << getAnimProperty();
......@@ -427,6 +556,8 @@ QHash<int, QByteArray> KeyframeModel::roleNames() const
roles[FrameRole] = "frame";
roles[TypeRole] = "type";
roles[ValueRole] = "value";
roles[SelectedRole] = "selected";
roles[ActiveRole] = "active";
roles[NormalizedValueRole] = "normalizedValue";
return roles;
}
......@@ -496,6 +627,16 @@ QVariant KeyframeModel::data(const QModelIndex &index, int role) const
return it->first.frames(pCore->getCurrentFps());
case TypeRole:
return QVariant::fromValue<KeyframeType>(it->second.first);
case SelectedRole:
if (auto ptr = m_model.lock()) {
return ptr->m_selectedKeyframes.contains(index.row());
}
break;
case ActiveRole:
if (auto ptr = m_model.lock()) {
return ptr->m_activeKeyframe == index.row();
}
break;
}
return QVariant();
}
......@@ -591,6 +732,10 @@ bool KeyframeModel::removeAllKeyframes(Fun &undo, Fun &redo)
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
int kfrCount = int(m_keyframeList.size()) - 1;
// Clear selection
if (auto ptr = m_model.lock()) {
ptr->m_selectedKeyframes = {};
}
if (kfrCount <= 0) {
// Nothing to do
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
......@@ -1225,6 +1370,16 @@ bool KeyframeModel::removeNextKeyframes(GenTime pos, Fun &undo, Fun &redo)
all_pos.push_back(m.first);
}
int kfrCount = int(all_pos.size());
// Remove deleted keyframes from selection
if (auto ptr = m_model.lock()) {
QVector <int> selection;
for (auto &ix : ptr->m_selectedKeyframes) {
if (ix < kfrCount) {
selection << ix;
}
}
ptr->m_selectedKeyframes = selection;
}
// we trigger only one global remove/insertrow event
Fun update_redo_start = [this, firstPos, kfrCount]() {
beginRemoveRows(QModelIndex(), firstPos, kfrCount);
......@@ -1260,3 +1415,83 @@ bool KeyframeModel::removeNextKeyframes(GenTime pos, Fun &undo, Fun &redo)
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
void KeyframeModel::setSelectedKeyframe(int ix, bool add)
{
QVector<int> previous;
if (auto ptr = m_model.lock()) {
if (add) {
if (ptr->m_selectedKeyframes.contains(ix)) {
// remove from selection
ptr->m_selectedKeyframes.removeAll(ix);
} else {
ptr->m_selectedKeyframes << ix;
}
} else {
previous = ptr->m_selectedKeyframes;
ptr->m_selectedKeyframes = {ix};
}
}
if (!add) {
for (auto &ix2 : previous) {
if (ix2 > -1) {
emit requestModelUpdate(index(ix2), index(ix2), {SelectedRole});
}
}
}
if (ix > -1) {
emit requestModelUpdate(index(ix), index(ix), {SelectedRole});
}
}
void KeyframeModel::setSelectedKeyframes(QVector<int> selection)
{
QVector<int> previous;
selection.removeAll(-1);
std::sort(selection.begin(), selection.end());
if (auto ptr = m_model.lock()) {
previous = ptr->m_selectedKeyframes;
ptr->m_selectedKeyframes = selection;
}
if (!selection.isEmpty()) {
emit requestModelUpdate(index(selection.first()), index(selection.last()), {SelectedRole});
}
for (auto &ix : previous) {
if (ix > -1 && !selection.contains(ix)) {
emit requestModelUpdate(index(ix), index(ix), {SelectedRole});
}
}
}
int KeyframeModel::activeKeyframe() const
{
if (auto ptr = m_model.lock()) {
return ptr->m_activeKeyframe;
}
return -1;
}
void KeyframeModel::setActiveKeyframe(int ix)
{
int oldActive = -1;
if (auto ptr = m_model.lock()) {
oldActive = ptr->m_activeKeyframe;
if (oldActive == ix) {
// Keyframe already active
return;
}
ptr->m_activeKeyframe = ix;
}
emit requestModelUpdate(index(ix), index(ix), {ActiveRole});
if (oldActive > -1) {
emit requestModelUpdate(index(oldActive), index(oldActive), {ActiveRole});
}
}
int KeyframeModel::getIndexForPos(const GenTime pos) const
{
if (m_keyframeList.count(pos) == 0) {
return -1;
}
return static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
}
......@@ -44,7 +44,7 @@ public:
explicit KeyframeModel(std::weak_ptr<AssetParameterModel> model, const QModelIndex &index, std::weak_ptr<DocUndoStack> undo_stack,
QObject *parent = nullptr);
enum { TypeRole = Qt::UserRole + 1, PosRole, FrameRole, ValueRole, NormalizedValueRole };
enum { TypeRole = Qt::UserRole + 1, PosRole, FrameRole, ValueRole, NormalizedValueRole, SelectedRole, ActiveRole };
friend class KeyframeModelList;
friend class KeyframeWidget;
friend class KeyframeImport;
......@@ -77,7 +77,7 @@ protected:
protected:
/** @brief Same function but accumulates undo/redo */
bool removeKeyframe(GenTime pos, Fun &undo, Fun &redo, bool notify = true);
bool removeKeyframe(GenTime pos, Fun &undo, Fun &redo, bool notify = true, bool updateSelection = true);
public:
/** @brief moves a keyframe
......@@ -88,7 +88,7 @@ public:
bool moveKeyframe(int oldPos, int pos, bool logUndo);
bool offsetKeyframes(int oldPos, int pos, bool logUndo);
bool moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, bool logUndo);
bool moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, Fun &undo, Fun &redo);
bool moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, Fun &undo, Fun &redo, bool updateView = true);
/** @brief updates the value of a keyframe
@param old is the position of the keyframe
......@@ -147,6 +147,14 @@ public:
QVariant updateInterpolated(const QVariant &interpValue, double val);
/** @brief Return the real value from a normalized one */
QVariant getNormalizedValue(double newVal) const;
/** @brief Set or add a keyframe to selection */
Q_INVOKABLE void setSelectedKeyframe(int ix, bool add);
void setSelectedKeyframes(QVector<int> selection);
Q_INVOKABLE int activeKeyframe() const;
Q_INVOKABLE void setActiveKeyframe(int ix);
int getIndexForPos(const GenTime pos) const;
GenTime getPosAtIndex(int ix) const;
// Mandatory overloads
Q_INVOKABLE QVariant data(const QModelIndex &index, int role) const override;
......@@ -197,9 +205,11 @@ private:
mutable QReadWriteLock m_lock;
std::map<GenTime, std::pair<KeyframeType, QVariant>> m_keyframeList;
bool moveOneKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, Fun &undo, Fun &redo, bool updateView = true);
signals:
void modelChanged();
void requestModelUpdate(const QModelIndex &, const QModelIndex &, const QVector<int>&);
public:
// this is to enable for range loops
......
......@@ -40,6 +40,42 @@ const QString KeyframeModelList::getAssetId()
return {};
}
QVector<int> KeyframeModelList::selectedKeyframes() const
{
if (auto ptr = m_model.lock()) {
return ptr->m_selectedKeyframes;
}
return {};
}
int KeyframeModelList::activeKeyframe() const
{
if (auto ptr = m_model.lock()) {
return ptr->m_activeKeyframe;
}
return -1;
}
void KeyframeModelList::setActiveKeyframe(int ix)
{
m_parameters.begin()->second->setActiveKeyframe(ix);
}
void KeyframeModelList::removeFromSelected(int ix)
{
m_parameters.begin()->second->setSelectedKeyframe(ix, true);
}
void KeyframeModelList::setSelectedKeyframes(QVector<int> list)
{
m_parameters.begin()->second->setSelectedKeyframes(list);
}
void KeyframeModelList::appendSelectedKeyframe(int ix)
{
m_parameters.begin()->second->setSelectedKeyframe(ix, true);
}
const QString KeyframeModelList::getAssetRow()
{
if (auto ptr = m_model.lock()) {
......@@ -52,9 +88,19 @@ void KeyframeModelList::addParameter(const QModelIndex &index)
{
std::shared_ptr<KeyframeModel> parameter(new KeyframeModel(m_model, index, m_undoStack));
connect(parameter.get(), &KeyframeModel::modelChanged, this, &KeyframeModelList::modelChanged);
connect(parameter.get(), &KeyframeModel::requestModelUpdate, this, &KeyframeModelList::slotUpdateModels);
m_parameters.insert({index, std::move(parameter)});
}
void KeyframeModelList::slotUpdateModels(const QModelIndex &ix1, const QModelIndex &ix2, const QVector<int> &roles)
{
// Propagate change to all keyframe models
for (const auto &param : m_parameters) {
param.second->dataChanged(ix1, ix2, roles);
}
emit modelDisplayChanged();
}
bool KeyframeModelList::applyOperation(const std::function<bool(std::shared_ptr<KeyframeModel>, Fun &, Fun &)> &op, const QString &undoString)
{
QWriteLocker locker(&m_lock);
......@@ -169,11 +215,11 @@ bool KeyframeModelList::removeNextKeyframes(GenTime pos)
return applyOperation(op, i18n("Delete keyframes"));
}
bool KeyframeModelList::moveKeyframe(GenTime oldPos, GenTime pos, bool logUndo)
bool KeyframeModelList::moveKeyframe(GenTime oldPos, GenTime pos, bool logUndo, bool updateView)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
auto op = [oldPos, pos](std::shared_ptr<KeyframeModel> param, Fun &undo, Fun &redo) { return param->moveKeyframe(oldPos, pos, QVariant(), undo, redo); };
auto op = [oldPos, pos, updateView](std::shared_ptr<KeyframeModel> param, Fun &undo, Fun &redo) { return param->moveKeyframe(oldPos, pos, QVariant(), undo, redo, updateView); };
return applyOperation(op, logUndo ? i18nc("@action", "Move keyframe") : QString());
}
......@@ -558,12 +604,12 @@ void KeyframeModelList::checkConsistency()
GenTime KeyframeModelList::getPosAtIndex(int ix)
{
QList<GenTime> positions = m_parameters.begin()->second->getKeyframePos();
std::sort(positions.begin(), positions.end());
if (ix < 0 || ix >= positions.count()) {
return GenTime();
}
return positions.at(ix);
return m_parameters.begin()->second->getPosAtIndex(ix);
}
int KeyframeModelList::getIndexForPos(GenTime pos)
{
return m_parameters.begin()->second->getIndexForPos(pos);
}
QModelIndex KeyframeModelList::getIndexAtRow(int row)
......
......@@ -62,7 +62,7 @@ public:
@param pos defines the new position of the keyframe, relative to the clip
@param logUndo if true, then an undo object is created
*/
bool moveKeyframe(GenTime oldPos, GenTime pos, bool logUndo);
bool moveKeyframe(GenTime oldPos, GenTime pos, bool logUndo, bool updateView = true);
bool moveKeyframeWithUndo(GenTime oldPos, GenTime pos, Fun &undo, Fun &redo);
/** @brief updates the value of a keyframe
......@@ -128,6 +128,20 @@ public:
const QString getAssetId();
const QString getAssetRow();
/** @brief Returns the list of selected keyframes */
QVector<int> selectedKeyframes() const;
/** @brief Remove a position from selected keyframes */
void removeFromSelected(int pos);
/** @brief Replace list of selected keyframes */
void setSelectedKeyframes(QVector<int> list);
/** @brief Append a keyframe to selection */
void appendSelectedKeyframe(int frame);
/** @brief Get the currently active keyframe */
int activeKeyframe() const;
/** @brief Set the currently active keyframe */
void setActiveKeyframe(int pos);
/** @brief Parent item size change, update keyframes*/
void resizeKeyframes(int oldIn, int oldOut, int in, int out, int offset, bool adjustFromEnd, Fun &undo, Fun &redo);
......@@ -136,6 +150,7 @@ public:
/** @brief Return position of the nth keyframe (ix = nth)*/
GenTime getPosAtIndex(int ix);
int getIndexForPos(GenTime pos);
QModelIndex getIndexAtRow(int row);
/** @brief Check that all keyframable parameters have the same keyframes on loading
......@@ -148,6 +163,7 @@ protected:
signals:
void modelChanged();
void modelDisplayChanged();
private:
std::weak_ptr<AssetParameterModel> m_model;
......@@ -157,6 +173,9 @@ private:
QModelIndex m_inTimelineIndex;
mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access
private slots:
void slotUpdateModels(const QModelIndex &ix1, const QModelIndex &ix2, const QVector<int> &roles);
public:
// this is to enable for range loops
auto begin() -> decltype(m_parameters.begin()->second->begin()) { return m_parameters.begin()->second->begin(); }
......
This diff is collapsed.
......@@ -19,7 +19,7 @@ class KeyframeView : public QWidget
Q_OBJECT
public:
explicit KeyframeView(std::shared_ptr<KeyframeModelList> model, int duration, int inPoint, QWidget *parent = nullptr);
explicit KeyframeView(std::shared_ptr<KeyframeModelList> model, int duration, QWidget *parent = nullptr);
void setDuration(int dur, int inPoint);
const QString getAssetId();
/** @brief Copy a keyframe parameter to selected keyframes. */
......@@ -46,6 +46,7 @@ public slots:
void slotGoToNext();
void slotGoToPrev();
void slotModelChanged();
void slotModelDisplayChanged();
void slotEditType(int type, const QPersistentModelIndex &index);
/** @brief Emit initial info for monitor. */
void initKeyframePos();
......@@ -63,9 +64,7 @@ protected:
private:
std::shared_ptr<KeyframeModelList> m_model;
int m_duration;
int m_inPoint;
int m_position;
int m_currentKeyframe;
int m_currentKeyframeOriginal;
QVector <int>m_selectedKeyframes;
int m_hoverKeyframe;
......
......@@ -27,6 +27,7 @@ AssetParameterModel::AssetParameterModel(std::unique_ptr<Mlt::Properties> asset,
, m_active(false)
, m_asset(std::move(asset))
, m_keyframes(nullptr)
, m_activeKeyframe(-1)
, m_filterProgress(0)
{
Q_ASSERT(m_asset->is_valid());
......