Implement locking and pinning of animation-curves

Fixes: QDS-550
Fixes: QDS-551
Change-Id: I53a120a3753bb98fe729106b7e996f475fbda2f1
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Knud Dollereder
2020-02-10 16:21:23 +01:00
parent debc304bf8
commit 841f5be97e
23 changed files with 267 additions and 22 deletions

View File

@@ -52,7 +52,11 @@ CurveEditor::CurveEditor(CurveEditorModel *model, QWidget *parent)
box->addWidget(splitter);
setLayout(box);
connect(m_tree, &TreeView::treeItemLocked, m_view, &GraphicsView::setLocked);
connect(m_tree->selectionModel(), &SelectionModel::curvesSelected, m_view, &GraphicsView::reset);
connect(m_tree, &TreeView::treeItemLocked, model, &CurveEditorModel::curveChanged);
connect(m_tree, &TreeView::treeItemPinned, model, &CurveEditorModel::curveChanged);
}
void CurveEditor::zoomX(double zoom)

View File

@@ -52,6 +52,15 @@ void CurveEditorModel::setCurve(unsigned int id, const AnimationCurve &curve)
}
}
bool contains(const std::vector<TreeItem::Path> &selection, const TreeItem::Path &path)
{
for (auto &&sel : selection)
if (path == sel)
return true;
return false;
}
void CurveEditorModel::reset(const std::vector<TreeItem *> &items)
{
std::vector<TreeItem::Path> sel;
@@ -63,13 +72,24 @@ void CurveEditorModel::reset(const std::vector<TreeItem *> &items)
initialize();
unsigned int counter = 0;
std::vector<CurveItem *> pinned;
for (auto *item : items) {
item->setId(++counter);
root()->addChild(item);
if (auto *nti = item->asNodeItem()) {
for (auto *pti : nti->properties()) {
if (pti->pinned() && !contains(sel, pti->path()))
pinned.push_back(TreeModel::curveItem(pti));
}
}
}
endResetModel();
if (!pinned.empty())
graphicsView()->reset(pinned);
if (SelectionModel *sm = selectionModel())
sm->select(sel);
}

View File

@@ -46,6 +46,8 @@ CurveItem::CurveItem(QGraphicsItem *parent)
, m_component(PropertyTreeItem::Component::Generic)
, m_transform()
, m_keyframes()
, m_locked(false)
, m_pinned(false)
, m_underMouse(false)
, m_itemDirty(false)
{}
@@ -58,6 +60,8 @@ CurveItem::CurveItem(unsigned int id, const AnimationCurve &curve, QGraphicsItem
, m_component(PropertyTreeItem::Component::Generic)
, m_transform()
, m_keyframes()
, m_locked(false)
, m_pinned(false)
, m_underMouse(false)
, m_itemDirty(false)
{
@@ -128,7 +132,9 @@ void CurveItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidg
if (segment.interpolation() == Keyframe::Interpolation::Easing) {
pen.setColor(m_style.easingCurveColor);
} else {
if (m_underMouse)
if (m_locked)
pen.setColor(Qt::black);
else if (m_underMouse)
pen.setColor(Qt::red);
else if (hasSelection())
pen.setColor(m_style.selectionColor);
@@ -147,6 +153,16 @@ bool CurveItem::isDirty() const
return m_itemDirty;
}
bool CurveItem::locked() const
{
return m_locked;
}
bool CurveItem::pinned() const
{
return m_pinned;
}
bool CurveItem::hasSelection() const
{
for (auto *frame : m_keyframes) {
@@ -267,6 +283,20 @@ void CurveItem::restore()
}
}
void CurveItem::setLocked(bool locked)
{
m_locked = locked;
for (auto frame : m_keyframes)
frame->setLocked(locked);
setHandleVisibility(!m_locked);
}
void CurveItem::setPinned(bool pinned)
{
m_pinned = pinned;
}
void CurveItem::setDirty(bool dirty)
{
m_itemDirty = dirty;
@@ -294,6 +324,7 @@ void CurveItem::setCurve(const AnimationCurve &curve)
for (auto frame : curve.keyframes()) {
auto *item = new KeyframeItem(frame, this);
item->setLocked(m_locked);
item->setComponentTransform(m_transform);
m_keyframes.push_back(item);
QObject::connect(item, &KeyframeItem::redrawCurve, this, &CurveItem::emitCurveChanged);

View File

@@ -66,6 +66,10 @@ public:
bool isDirty() const;
bool locked() const;
bool pinned() const;
bool isUnderMouse() const;
bool hasSelection() const;
@@ -84,6 +88,10 @@ public:
void restore();
void setLocked(bool locked);
void setPinned(bool pinned);
void setDirty(bool dirty);
void setHandleVisibility(bool visible);
@@ -123,6 +131,10 @@ private:
std::vector<KeyframeItem *> m_keyframes;
bool m_locked;
bool m_pinned;
bool m_underMouse;
bool m_itemDirty;

View File

@@ -121,6 +121,32 @@ void GraphicsScene::handleMoved(KeyframeItem *frame,
}
}
void GraphicsScene::setPinned(int id, bool pinned)
{
const auto itemList = items();
for (auto *item : itemList) {
if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item)) {
if (curveItem->id() == id)
curveItem->setPinned(pinned);
}
}
}
std::vector<CurveItem *> GraphicsScene::takePinnedItems()
{
std::vector<CurveItem *> out;
const auto itemList = items();
for (auto *item : itemList) {
if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item)) {
if (curveItem->pinned()) {
removeItem(curveItem);
out.push_back(curveItem);
}
}
}
return out;
}
void GraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
QGraphicsScene::mouseMoveEvent(mouseEvent);

View File

@@ -69,6 +69,10 @@ public:
void handleMoved(KeyframeItem *frame, HandleSlot handle, double angle, double deltaLength);
void setPinned(int id, bool pinned);
std::vector<CurveItem *> takePinnedItems();
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) override;

View File

@@ -26,6 +26,7 @@
#include "graphicsview.h"
#include "curveeditormodel.h"
#include "curveitem.h"
#include "treeitem.h"
#include "utils.h"
#include <QAction>
@@ -69,6 +70,9 @@ GraphicsView::GraphicsView(CurveEditorModel *model, QWidget *parent)
connect(&m_scene, &GraphicsScene::curveChanged, itemSlot);
auto pinSlot = [this](PropertyTreeItem *pti) { m_scene.setPinned(pti->id(), pti->pinned()); };
connect(m_model, &CurveEditorModel::curveChanged, pinSlot);
applyZoom(m_zoomX, m_zoomY);
update();
}
@@ -185,6 +189,16 @@ void GraphicsView::setStyle(const CurveEditorStyle &style)
viewport()->update();
}
void GraphicsView::setLocked(PropertyTreeItem *item)
{
const auto itemList = items();
for (auto *gitem : itemList) {
if (auto *citem = qgraphicsitem_cast<CurveItem *>(gitem))
if (item->id() == citem->id())
citem->setLocked(item->locked());
}
}
void GraphicsView::setZoomX(double zoom, const QPoint &pivot)
{
applyZoom(zoom, m_zoomY, pivot);
@@ -217,8 +231,22 @@ void GraphicsView::scrollContent(double x, double y)
void GraphicsView::reset(const std::vector<CurveItem *> &items)
{
const std::vector<CurveItem *> pinnedItems = m_scene.takePinnedItems();
auto notPinned = [pinnedItems](CurveItem *item) {
for (auto *pinned : pinnedItems) {
if (pinned->id() == item->id())
return false;
}
return true;
};
m_scene.clear();
for (auto *item : items)
for (auto *item : items) {
if (notPinned(item))
m_scene.addCurveItem(item);
}
for (auto *item : pinnedItems)
m_scene.addCurveItem(item);
applyZoom(m_zoomX, m_zoomY);

View File

@@ -38,6 +38,7 @@ namespace DesignTools {
class CurveItem;
class CurveEditorModel;
class Playhead;
class PropertyTreeItem;
class GraphicsView : public QGraphicsView
{
@@ -93,6 +94,8 @@ public:
QRectF defaultRasterRect() const;
void setLocked(PropertyTreeItem *item);
void setStyle(const CurveEditorStyle &style);
void setZoomX(double zoom, const QPoint &pivot = QPoint());

View File

@@ -76,6 +76,9 @@ QRectF HandleItem::boundingRect() const
void HandleItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
if (locked())
return;
Q_UNUSED(option)
Q_UNUSED(widget)

View File

@@ -69,12 +69,23 @@ void KeyframeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
painter->save();
painter->setPen(pen);
painter->setBrush(selected() ? Qt::red : m_style.color);
painter->setBrush(locked() ? Qt::black : (selected() ? Qt::red : m_style.color));
painter->drawEllipse(boundingRect());
painter->restore();
}
void KeyframeItem::setLocked(bool locked)
{
SelectableItem::setLocked(locked);
if (m_left)
m_left->setLocked(locked);
if (m_right)
m_right->setLocked(locked);
}
KeyframeItem::~KeyframeItem() {}
Keyframe KeyframeItem::keyframe() const

View File

@@ -63,6 +63,8 @@ public:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void setLocked(bool locked) override;
Keyframe keyframe() const;
bool hasLeftHandle() const;

View File

@@ -32,6 +32,7 @@ SelectableItem::SelectableItem(QGraphicsItem *parent)
: QGraphicsObject(parent)
, m_active(false)
, m_selected(false)
, m_locked(false)
, m_preSelected(SelectionMode::Undefined)
{
setFlag(QGraphicsItem::ItemIsSelectable, false);
@@ -43,6 +44,13 @@ SelectableItem::SelectableItem(QGraphicsItem *parent)
SelectableItem::~SelectableItem() {}
void SelectableItem::setLocked(bool locked)
{
setPreselected(SelectionMode::Clear);
applyPreselection();
m_locked = locked;
}
bool SelectableItem::activated() const
{
return m_active;
@@ -68,6 +76,11 @@ bool SelectableItem::selected() const
return false;
}
bool SelectableItem::locked() const
{
return m_locked;
}
void SelectableItem::setActivated(bool active)
{
m_active = active;
@@ -75,6 +88,9 @@ void SelectableItem::setActivated(bool active)
void SelectableItem::setPreselected(SelectionMode mode)
{
if (m_locked)
return;
m_preSelected = mode;
selectionCallback();
}
@@ -89,12 +105,18 @@ void SelectableItem::selectionCallback() {}
void SelectableItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (m_locked)
return;
m_active = true;
QGraphicsObject::mousePressEvent(event);
}
void SelectableItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (m_locked)
return;
if (type() == KeyframeItem::Type && !selected())
return;
@@ -103,6 +125,9 @@ void SelectableItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
void SelectableItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (m_locked)
return;
m_active = false;
QGraphicsObject::mouseReleaseEvent(event);
}

View File

@@ -29,15 +29,13 @@
namespace DesignTools {
enum ItemType
{
enum ItemType {
ItemTypeKeyframe = QGraphicsItem::UserType + 1,
ItemTypeHandle = QGraphicsItem::UserType + 2,
ItemTypeCurve = QGraphicsItem::UserType + 3
};
enum class SelectionMode : unsigned int
{
enum class SelectionMode : unsigned int {
Undefined,
Clear,
New,
@@ -55,10 +53,14 @@ public:
~SelectableItem() override;
virtual void setLocked(bool locked);
bool activated() const;
bool selected() const;
bool locked() const;
void setActivated(bool active);
void setPreselected(SelectionMode mode);
@@ -79,6 +81,8 @@ private:
bool m_selected;
bool m_locked;
SelectionMode m_preSelected;
};

View File

@@ -24,6 +24,7 @@
****************************************************************************/
#include "selectionmodel.h"
#include "curveitem.h"
#include "treemodel.h"
namespace DesignTools {
@@ -34,6 +35,17 @@ SelectionModel::SelectionModel(QAbstractItemModel *model)
connect(this, &QItemSelectionModel::selectionChanged, this, &SelectionModel::changeSelection);
}
void SelectionModel::select(const QItemSelection &selection,
QItemSelectionModel::SelectionFlags command)
{
for (auto &&index : selection.indexes()) {
if (index.column() == 0) {
QItemSelectionModel::select(selection, command);
return;
}
}
}
std::vector<TreeItem::Path> SelectionModel::selectedPaths() const
{
std::vector<TreeItem::Path> out;
@@ -90,9 +102,12 @@ void SelectionModel::select(const std::vector<TreeItem::Path> &selection)
{
for (auto &&path : selection) {
if (auto *treeModel = qobject_cast<TreeModel *>(model())) {
QModelIndex index = treeModel->indexOf(path);
if (index.isValid())
QItemSelectionModel::select(index, QItemSelectionModel::Select);
QModelIndex left = treeModel->indexOf(path);
QModelIndex right = left.siblingAtColumn(2);
if (left.isValid() && right.isValid()) {
auto is = QItemSelection(left, right);
QItemSelectionModel::select(is, QItemSelectionModel::Select);
}
}
}
}

View File

@@ -46,6 +46,9 @@ signals:
public:
SelectionModel(QAbstractItemModel *model = nullptr);
void select(const QItemSelection &selection,
QItemSelectionModel::SelectionFlags command) override;
std::vector<TreeItem::Path> selectedPaths() const;
std::vector<CurveItem *> selectedCurveItems() const;

View File

@@ -59,10 +59,20 @@ PropertyTreeItem *TreeModel::propertyItem(const QModelIndex &index)
CurveItem *TreeModel::curveItem(const QModelIndex &index)
{
if (auto *pti = propertyItem(index)) {
if (auto *ti = treeItem(index))
return curveItem(ti);
return nullptr;
}
CurveItem *TreeModel::curveItem(TreeItem *item)
{
if (auto *pti = item->asPropertyItem()) {
auto *citem = new CurveItem(pti->id(), pti->curve());
citem->setValueType(pti->valueType());
citem->setComponent(pti->component());
citem->setLocked(pti->locked());
citem->setPinned(pti->pinned());
return citem;
}

View File

@@ -53,6 +53,8 @@ public:
static CurveItem *curveItem(const QModelIndex &index);
static CurveItem *curveItem(TreeItem *item);
TreeModel(QObject *parent = nullptr);
~TreeModel() override;

View File

@@ -105,10 +105,15 @@ void TreeView::mousePressEvent(QMouseEvent *event)
QModelIndex index = indexAt(event->pos());
if (index.isValid()) {
auto *treeItem = static_cast<TreeItem *>(index.internalPointer());
if (index.column() == 1)
if (index.column() == 1) {
treeItem->setLocked(!treeItem->locked());
else if (index.column() == 2)
if (auto *propertyItem = treeItem->asPropertyItem())
emit treeItemLocked(propertyItem);
} else if (index.column() == 2) {
treeItem->setPinned(!treeItem->pinned());
if (auto *propertyItem = treeItem->asPropertyItem())
emit treeItemPinned(propertyItem);
}
}
QTreeView::mousePressEvent(event);
}

View File

@@ -44,6 +44,10 @@ class TreeView : public QTreeView
signals:
void curvesSelected(const std::vector<CurveItem *> &curves);
void treeItemLocked(PropertyTreeItem *item);
void treeItemPinned(PropertyTreeItem *item);
public:
TreeView(CurveEditorModel *model, QWidget *parent = nullptr);
@@ -57,9 +61,6 @@ protected:
QSize sizeHint() const override;
void mousePressEvent(QMouseEvent *event) override;
private:
void changeSelection(const QItemSelection &selected, const QItemSelection &deselected);
};
} // End namespace DesignTools.

View File

@@ -246,6 +246,17 @@ QIcon NodeTreeItem::icon() const
return m_icon;
}
std::vector<PropertyTreeItem *> NodeTreeItem::properties() const
{
std::vector<PropertyTreeItem *> out;
for (auto *child : m_children) {
if (auto *pti = child->asPropertyItem())
out.push_back(pti);
}
return out;
}
std::string toString(ValueType type)
{
switch (type) {

View File

@@ -122,6 +122,8 @@ public:
QIcon icon() const override;
std::vector<PropertyTreeItem *> properties() const;
private:
QIcon m_icon;
};

View File

@@ -94,9 +94,10 @@ void AnimationCurveEditorModel::setTimeline(const QmlTimeline &timeline)
m_maxTime = timeline.endKeyframe();
std::vector<DesignTools::TreeItem *> items;
for (auto &&target : timeline.allTargets())
for (auto &&target : timeline.allTargets()) {
if (DesignTools::TreeItem *item = createTopLevelItem(timeline, target))
items.push_back(item);
}
reset(items);
}
@@ -113,8 +114,7 @@ void AnimationCurveEditorModel::setMaximumTime(double time)
DesignTools::ValueType typeFrom(const QmlTimelineKeyframeGroup &group)
{
if (group.valueType() == TypeName("double")
|| group.valueType() == TypeName("real")
if (group.valueType() == TypeName("double") || group.valueType() == TypeName("real")
|| group.valueType() == TypeName("float"))
return DesignTools::ValueType::Double;
@@ -140,7 +140,16 @@ DesignTools::TreeItem *AnimationCurveEditorModel::createTopLevelItem(const QmlTi
DesignTools::AnimationCurve curve = createAnimationCurve(grp);
if (curve.isValid()) {
QString name = QString::fromUtf8(grp.propertyName());
nodeItem->addChild(new DesignTools::PropertyTreeItem(name, curve, typeFrom(grp)));
auto propertyItem = new DesignTools::PropertyTreeItem(name, curve, typeFrom(grp));
ModelNode target = grp.modelNode();
if (target.hasAuxiliaryData("locked"))
propertyItem->setLocked(true);
if (target.hasAuxiliaryData("pinned"))
propertyItem->setPinned(true);
nodeItem->addChild(propertyItem);
}
}
}

View File

@@ -328,6 +328,20 @@ void TimelineWidget::updateAnimationCurve(DesignTools::PropertyTreeItem *item)
QmlTimelineKeyframeGroup group = timelineKeyframeGroup(currentTimeline, item);
if (group.isValid()) {
ModelNode groupNode = group.modelNode();
if (groupNode.isValid()) {
if (item->locked())
groupNode.setAuxiliaryData("locked", true);
else
groupNode.removeAuxiliaryData("locked");
if (item->pinned())
groupNode.setAuxiliaryData("pinned", true);
else
groupNode.removeAuxiliaryData("pinned");
}
auto replaceKeyframes = [&group, item, this]() {
m_toolbar->setBlockReflection(true);
for (auto frame : group.keyframes())
@@ -407,8 +421,8 @@ void TimelineWidget::init()
QmlTimeline currentTimeline = m_timelineView->timelineForState(m_timelineView->currentState());
if (currentTimeline.isValid()) {
setTimelineId(currentTimeline.modelNode().id());
m_statusBar->setText(tr(TimelineConstants::statusBarPlayheadFrame)
.arg(getcurrentFrame(currentTimeline)));
m_statusBar->setText(
tr(TimelineConstants::statusBarPlayheadFrame).arg(getcurrentFrame(currentTimeline)));
} else {
setTimelineId({});
m_statusBar->clear();