diff options
author | Thomas Hartmann <thomas.hartmann@qt.io> | 2020-06-17 19:10:35 +0200 |
---|---|---|
committer | Thomas Hartmann <thomas.hartmann@qt.io> | 2020-06-26 09:44:08 +0000 |
commit | 454ff4c46bbfd0a068813bd6a0bb6929f1986e76 (patch) | |
tree | 617bec977ecc0626b5662db0ecbc67e58c433e5b | |
parent | 4e9b6d68c180f3df76fb30a0d9b6fb5bebabc7f5 (diff) |
QmlDesigner: First implementation of TransitionEditor
Change-Id: I14391e872f6a257a2cdf75e7d577de64c384c1fd
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
Reviewed-by: Henning Gründl <henning.gruendl@qt.io>
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
27 files changed, 4282 insertions, 0 deletions
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 866ca57cbc..94a4f449a7 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -669,6 +669,22 @@ extend_qtc_plugin(QmlDesigner ) extend_qtc_plugin(QmlDesigner + SOURCES_PREFIX components/transitioneditor + SOURCES + transitioneditorview.cpp transitioneditorview.h + transitioneditorwidget.cpp transitioneditorwidget.h + transitioneditortoolbar.cpp transitioneditortoolbar.h + transitioneditorgraphicsscene.cpp transitioneditorgraphicsscene.h + transitioneditorgraphicslayout.cpp transitioneditorgraphicslayout.h + transitioneditorsectionitem.cpp transitioneditorsectionitem.h + transitioneditorpropertyitem.cpp transitioneditorpropertyitem.h + transitioneditorsettingsdialog.cpp transitioneditorsettingsdialog.h + transitioneditorsettingsdialog.ui + transitionform.cpp transitionform.h + transitioneditor.qrc +) + +extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/curveeditor SOURCES animationcurve.cpp animationcurve.h diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.pri b/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.pri new file mode 100644 index 0000000000..8f9a9dec9e --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.pri @@ -0,0 +1,35 @@ +QT *= qml quick core + +VPATH += $$PWD + +INCLUDEPATH += $$PWD + +SOURCES += \ + transitioneditorview.cpp \ + transitioneditorwidget.cpp \ + transitioneditortoolbar.cpp \ + transitioneditorgraphicsscene.cpp \ + transitioneditorgraphicslayout.cpp \ + transitioneditorsectionitem.cpp \ + transitioneditorpropertyitem.cpp \ + transitioneditorsettingsdialog.cpp \ + transitionform.cpp + +HEADERS += \ + transitioneditorconstants \ + transitioneditorview.h \ + transitioneditorwidget.h \ + transitioneditortoolbar.h \ + transitioneditorgraphicsscene.h \ + transitioneditorgraphicslayout.h \ + transitioneditorsectionitem.h \ + transitioneditorpropertyitem.h \ + transitioneditorsettingsdialog.h \ + transitionform.h + +RESOURCES += \ + transitioneditor.qrc + +FORMS += \ + transitioneditorsettingsdialog.ui \ + transitionform.ui diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.qrc b/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.qrc new file mode 100644 index 0000000000..a2a962a6b8 --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.qrc @@ -0,0 +1,4 @@ +<RCC> + <qresource prefix="/transitioneditor"> + </qresource> +</RCC> diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorconstants.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorconstants.h new file mode 100644 index 0000000000..c06249eacb --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorconstants.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QGraphicsItem> + +namespace QmlDesigner { +namespace TransitionEditorConstants { + +const int transitionEditorSectionItemUserType = QGraphicsItem::UserType + 6; +const int transitionEditorPropertyItemUserType = QGraphicsItem::UserType + 7; + +} // namespace TransitionEditorConstants +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp new file mode 100644 index 0000000000..02e1258dfd --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "transitioneditorgraphicslayout.h" + +#include "timelinegraphicsscene.h" +#include "timelineplaceholder.h" +#include "timelinesectionitem.h" +#include "timelineview.h" +#include "transitioneditorsectionitem.h" + +#include <QGraphicsLinearLayout> + +#include <cmath> + +namespace QmlDesigner { + +TransitionEditorGraphicsLayout::TransitionEditorGraphicsLayout(QGraphicsScene *scene, + TimelineItem *parent) + : TimelineItem(parent) + , m_layout(new QGraphicsLinearLayout) + , m_rulerItem(TimelineRulerSectionItem::create(scene, this)) + , m_placeholder1(TimelinePlaceholder::create(scene, this)) + , m_placeholder2(TimelinePlaceholder::create(scene, this)) +{ + m_layout->setOrientation(Qt::Vertical); + m_layout->setSpacing(0); + m_layout->setContentsMargins(0, 0, 0, 0); + + m_layout->addItem(m_rulerItem); + m_layout->addItem(m_placeholder1); + m_layout->addItem(m_placeholder2); + + setLayout(m_layout); + + setPos(QPointF(0, 0)); + + connect(m_rulerItem, + &TimelineRulerSectionItem::rulerClicked, + this, + &TransitionEditorGraphicsLayout::rulerClicked); +} + +TransitionEditorGraphicsLayout::~TransitionEditorGraphicsLayout() = default; + +double TransitionEditorGraphicsLayout::rulerWidth() const +{ + return m_rulerItem->preferredWidth(); +} + +double TransitionEditorGraphicsLayout::rulerScaling() const +{ + return m_rulerItem->rulerScaling(); +} + +double TransitionEditorGraphicsLayout::rulerDuration() const +{ + return m_rulerItem->rulerDuration(); +} + +double TransitionEditorGraphicsLayout::endFrame() const +{ + return m_rulerItem->endFrame(); +} + +void TransitionEditorGraphicsLayout::setWidth(int width) +{ + m_rulerItem->setSizeHints(width); + m_placeholder1->setMinimumWidth(width); + m_placeholder2->setMinimumWidth(width); + setPreferredWidth(width); + setMaximumWidth(width); +} + +void TransitionEditorGraphicsLayout::setTransition(const ModelNode &transition) +{ + m_layout->removeItem(m_rulerItem); + m_layout->removeItem(m_placeholder1); + m_layout->removeItem(m_placeholder2); + + m_rulerItem->setParentItem(nullptr); + m_placeholder1->setParentItem(nullptr); + m_placeholder2->setParentItem(nullptr); + + qDeleteAll(this->childItems()); + + m_rulerItem->setParentItem(this); + + qreal duration = 2000; + if (transition.isValid() && transition.hasAuxiliaryData("transitionDuration")) + duration = transition.auxiliaryData("transitionDuration").toDouble(); + + setDuration(duration); + m_layout->addItem(m_rulerItem); + + m_placeholder1->setParentItem(this); + m_layout->addItem(m_placeholder1); + + m_layout->invalidate(); + + if (transition.isValid() && !transition.directSubModelNodes().isEmpty()) { + for (const ModelNode ¶llel : transition.directSubModelNodes()) { + auto item = TransitionEditorSectionItem::create(parallel, this); + m_layout->addItem(item); + } + } + + m_placeholder2->setParentItem(this); + m_layout->addItem(m_placeholder2); + + if (auto *scene = timelineScene()) + if (auto *view = scene->timelineView()) + if (!transition.isValid() && view->isAttached()) + emit scaleFactorChanged(0); +} + +void TransitionEditorGraphicsLayout::setDuration(qreal duration) +{ + m_rulerItem->invalidateRulerSize(duration); +} + +void TransitionEditorGraphicsLayout::setRulerScaleFactor(int factor) +{ + m_rulerItem->setRulerScaleFactor(factor); +} + +void TransitionEditorGraphicsLayout::invalidate() +{ + m_layout->invalidate(); +} + +int TransitionEditorGraphicsLayout::maximumScrollValue() const +{ + const qreal w = this->geometry().width() - qreal(TimelineConstants::sectionWidth); + const qreal duration = m_rulerItem->rulerDuration() + m_rulerItem->rulerDuration() * 0.1; + const qreal maxr = m_rulerItem->rulerScaling() * duration - w; + return std::round(qMax(maxr, 0.0)); +} + +void TransitionEditorGraphicsLayout::activate() +{ + m_layout->activate(); +} + +TimelineRulerSectionItem *TransitionEditorGraphicsLayout::ruler() const +{ + return m_rulerItem; +} + +} // End namespace QmlDesigner. diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h new file mode 100644 index 0000000000..9362abffdf --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelineitem.h" + +QT_FORWARD_DECLARE_CLASS(QGraphicsLinearLayout) + +namespace QmlDesigner { + +class TimelineItem; +class TimelineRulerSectionItem; +class TimelinePlaceholder; + +class ModelNode; + +class TransitionEditorGraphicsLayout : public TimelineItem +{ + Q_OBJECT + +signals: + void rulerClicked(const QPointF &pos); + + void scaleFactorChanged(int factor); + +public: + TransitionEditorGraphicsLayout(QGraphicsScene *scene, TimelineItem *parent = nullptr); + + ~TransitionEditorGraphicsLayout() override; + +public: + double rulerWidth() const; + + double rulerScaling() const; + + double rulerDuration() const; + + double endFrame() const; + + void setWidth(int width); + + void setTransition(const ModelNode &transition); + + void setDuration(qreal duration); + + void setRulerScaleFactor(int factor); + + void invalidate(); + + int maximumScrollValue() const; + + void activate(); + + TimelineRulerSectionItem *ruler() const; + +private: + QGraphicsLinearLayout *m_layout = nullptr; + + TimelineRulerSectionItem *m_rulerItem = nullptr; + + TimelinePlaceholder *m_placeholder1 = nullptr; + + TimelinePlaceholder *m_placeholder2 = nullptr; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp new file mode 100644 index 0000000000..036fe173f5 --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp @@ -0,0 +1,431 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "transitioneditorgraphicsscene.h" + +#include "transitioneditorgraphicslayout.h" +#include "transitioneditorpropertyitem.h" +#include "transitioneditorsectionitem.h" +#include "transitioneditortoolbar.h" +#include "transitioneditorview.h" +#include "transitioneditorwidget.h" + +#include "timelineactions.h" +#include "timelineitem.h" +#include "timelinemovableabstractitem.h" +#include "timelinemovetool.h" +#include "timelineplaceholder.h" +#include "timelinepropertyitem.h" +#include "timelinesectionitem.h" + +#include <designdocumentview.h> +#include <exception.h> +#include <rewritertransaction.h> +#include <rewriterview.h> +#include <viewmanager.h> +#include <qmldesignerplugin.h> +#include <qmlobjectnode.h> +#include <qmltimelinekeyframegroup.h> + +#include <bindingproperty.h> + +#include <nodeabstractproperty.h> +#include <nodelistproperty.h> +#include <variantproperty.h> + +#include <utils/algorithm.h> +#include <utils/qtcassert.h> + +#include <utils/hostosinfo.h> + +#include <QApplication> +#include <QComboBox> +#include <QGraphicsLinearLayout> +#include <QGraphicsProxyWidget> +#include <QGraphicsSceneMouseEvent> +#include <QGraphicsView> +#include <QKeyEvent> + +#include <cmath> + +namespace QmlDesigner { + +static int deleteKey() +{ + if (Utils::HostOsInfo::isMacHost()) + return Qt::Key_Backspace; + + return Qt::Key_Delete; +} + +TransitionEditorGraphicsScene::TransitionEditorGraphicsScene(TransitionEditorWidget *parent) + : AbstractScrollGraphicsScene(parent) + , m_parent(parent) + , m_layout(new TransitionEditorGraphicsLayout(this)) + , m_tools(this) +{ + addItem(m_layout); + + setSceneRect(m_layout->geometry()); + + connect(m_layout, &QGraphicsWidget::geometryChanged, this, [this]() { + auto rect = m_layout->geometry(); + + setSceneRect(rect); + + if (auto *gview = graphicsView()) + gview->setSceneRect(rect.adjusted(0, TimelineConstants::rulerHeight, 0, 0)); + + if (auto *rview = rulerView()) + rview->setSceneRect(rect); + }); + + auto changeScale = [this](int factor) { + transitionEditorWidget()->changeScaleFactor(factor); + setRulerScaling(qreal(factor)); + }; + connect(m_layout, &TransitionEditorGraphicsLayout::scaleFactorChanged, changeScale); +} + +TransitionEditorGraphicsScene::~TransitionEditorGraphicsScene() +{ + QSignalBlocker block(this); + qDeleteAll(items()); +} + +void TransitionEditorGraphicsScene::invalidateScrollbar() +{ + double max = m_layout->maximumScrollValue(); + transitionEditorWidget()->setupScrollbar(0, max, scrollOffset()); + if (scrollOffset() > max) + setScrollOffset(max); +} + +void TransitionEditorGraphicsScene::onShow() +{ + emit m_layout->scaleFactorChanged(0); +} + +void TransitionEditorGraphicsScene::setTransition(const ModelNode &transition) +{ + clearSelection(); + m_layout->setTransition(transition); +} + +void TransitionEditorGraphicsScene::clearTransition() +{ + m_transition = {}; + m_layout->setTransition({}); +} + +void TransitionEditorGraphicsScene::setWidth(int width) +{ + m_layout->setWidth(width); + invalidateScrollbar(); +} + +void TransitionEditorGraphicsScene::invalidateLayout() +{ + m_layout->invalidate(); +} + +void TransitionEditorGraphicsScene::setDuration(int duration) +{ + if (m_transition.isValid()) + m_transition.setAuxiliaryData("transitionDuration", duration); + m_layout->setDuration(duration); + qreal scaling = m_layout->rulerScaling(); + setRulerScaling(scaling); +} + +qreal TransitionEditorGraphicsScene::rulerScaling() const +{ + return m_layout->rulerScaling(); +} + +int TransitionEditorGraphicsScene::rulerWidth() const +{ + return m_layout->rulerWidth(); +} + +qreal TransitionEditorGraphicsScene::rulerDuration() const +{ + return m_layout->rulerDuration(); +} + +qreal TransitionEditorGraphicsScene::endFrame() const +{ + return m_layout->endFrame(); +} + +qreal TransitionEditorGraphicsScene::startFrame() const +{ + return 0; +} + +qreal TransitionEditorGraphicsScene::mapToScene(qreal x) const +{ + return TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset + + (x - startFrame()) * rulerScaling() - scrollOffset(); +} + +qreal TransitionEditorGraphicsScene::mapFromScene(qreal x) const +{ + auto xPosOffset = (x - TimelineConstants::sectionWidth - TimelineConstants::timelineLeftOffset) + + scrollOffset(); + + return xPosOffset / rulerScaling() + startFrame(); +} + +void TransitionEditorGraphicsScene::setRulerScaling(int scaleFactor) +{ + m_layout->setRulerScaleFactor(scaleFactor); + + setScrollOffset(0); + invalidateSections(); + invalidateScrollbar(); + update(); +} + +void TransitionEditorGraphicsScene::invalidateSectionForTarget(const ModelNode &target) +{ + if (!target.isValid()) + return; + + bool found = false; + + const QList<QGraphicsItem *> items = m_layout->childItems(); + for (auto child : items) + TimelineSectionItem::updateDataForTarget(child, target, &found); + + if (!found) + invalidateScene(); + + clearSelection(); + invalidateLayout(); +} + +void TransitionEditorGraphicsScene::invalidateScene() +{ + invalidateScrollbar(); +} + +void TransitionEditorGraphicsScene::invalidateCurrentValues() +{ + const QList<QGraphicsItem *> constItems = items(); + for (auto item : constItems) + TimelinePropertyItem::updateTextEdit(item); +} + +QGraphicsView *TransitionEditorGraphicsScene::graphicsView() const +{ + const QList<QGraphicsView *> constViews = views(); + for (auto *v : constViews) + if (v->objectName() == "SceneView") + return v; + + return nullptr; +} + +QGraphicsView *TransitionEditorGraphicsScene::rulerView() const +{ + const QList<QGraphicsView *> constViews = views(); + for (auto *v : constViews) + if (v->objectName() == "RulerView") + return v; + + return nullptr; +} + +QRectF TransitionEditorGraphicsScene::selectionBounds() const +{ + QRectF bbox; + + return bbox; +} + +void TransitionEditorGraphicsScene::clearSelection() +{ + if (m_selectedProperty) + m_selectedProperty->update(); + + m_selectedProperty = nullptr; + AbstractScrollGraphicsScene::clearSelection(); +} + +QList<QGraphicsItem *> TransitionEditorGraphicsScene::itemsAt(const QPointF &pos) +{ + QTransform transform; + + if (auto *gview = graphicsView()) + transform = gview->transform(); + + return items(pos, Qt::IntersectsItemShape, Qt::DescendingOrder, transform); +} + +void TransitionEditorGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos())); + + m_tools.mousePressEvent(topItem, event); + QGraphicsScene::mousePressEvent(event); +} + +void TransitionEditorGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos())); + m_tools.mouseMoveEvent(topItem, event); + QGraphicsScene::mouseMoveEvent(event); +} + +void TransitionEditorGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos())); + /* The tool has handle the event last. */ + QGraphicsScene::mouseReleaseEvent(event); + m_tools.mouseReleaseEvent(topItem, event); +} + +void TransitionEditorGraphicsScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos())); + m_tools.mouseDoubleClickEvent(topItem, event); + QGraphicsScene::mouseDoubleClickEvent(event); +} + +void TransitionEditorGraphicsScene::keyPressEvent(QKeyEvent *keyEvent) +{ + if (qgraphicsitem_cast<QGraphicsProxyWidget *>(focusItem())) { + keyEvent->ignore(); + QGraphicsScene::keyPressEvent(keyEvent); + return; + } + + if (keyEvent->modifiers().testFlag(Qt::ControlModifier)) { + QGraphicsScene::keyPressEvent(keyEvent); + } else { + switch (keyEvent->key()) { + case Qt::Key_Left: + emit scroll(TimelineUtils::Side::Left); + keyEvent->accept(); + break; + + case Qt::Key_Right: + emit scroll(TimelineUtils::Side::Right); + keyEvent->accept(); + break; + + default: + QGraphicsScene::keyPressEvent(keyEvent); + break; + } + } +} + +void TransitionEditorGraphicsScene::keyReleaseEvent(QKeyEvent *keyEvent) +{ + if (qgraphicsitem_cast<QGraphicsProxyWidget *>(focusItem())) { + keyEvent->ignore(); + QGraphicsScene::keyReleaseEvent(keyEvent); + return; + } + + QGraphicsScene::keyReleaseEvent(keyEvent); +} + +void TransitionEditorGraphicsScene::invalidateSections() +{ + const QList<QGraphicsItem *> children = m_layout->childItems(); + for (auto child : children) + TransitionEditorSectionItem::updateData(child); + + clearSelection(); + invalidateLayout(); +} + +TransitionEditorView *TransitionEditorGraphicsScene::transitionEditorView() const +{ + return m_parent->transitionEditorView(); +} + +TransitionEditorWidget *TransitionEditorGraphicsScene::transitionEditorWidget() const +{ + return m_parent; +} + +TransitionEditorToolBar *TransitionEditorGraphicsScene::toolBar() const +{ + return transitionEditorWidget()->toolBar(); +} + +void TransitionEditorGraphicsScene::activateLayout() +{ + m_layout->activate(); +} + +AbstractView *TransitionEditorGraphicsScene::abstractView() const +{ + return transitionEditorView(); +} + +bool TransitionEditorGraphicsScene::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::ShortcutOverride: + if (static_cast<QKeyEvent *>(event)->key() == deleteKey()) { + QGraphicsScene::keyPressEvent(static_cast<QKeyEvent *>(event)); + event->accept(); + return true; + } + Q_FALLTHROUGH(); + default: + return QGraphicsScene::event(event); + } +} + +ModelNode TransitionEditorGraphicsScene::transitionModelNode() const +{ + if (transitionEditorView()->isAttached()) { + const QString timelineId = transitionEditorWidget()->toolBar()->currentTransitionId(); + return transitionEditorView()->modelNodeForId(timelineId); + } + + return ModelNode(); +} + +TransitionEditorPropertyItem *TransitionEditorGraphicsScene::selectedPropertyItem() const +{ + return m_selectedProperty; +} + +void TransitionEditorGraphicsScene::setSelectedPropertyItem(TransitionEditorPropertyItem *item) +{ + if (m_selectedProperty) + m_selectedProperty->update(); + m_selectedProperty = item; + emit selectionChanged(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h new file mode 100644 index 0000000000..2f04c5b729 --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + + +#include <timelineeditor/timelinegraphicsscene.h> + +#include <qmltimeline.h> + +#include <QGraphicsScene> + +#include <memory> + +QT_FORWARD_DECLARE_CLASS(QGraphicsLinearLayout) +QT_FORWARD_DECLARE_CLASS(QComboBox) + +namespace QmlDesigner { + +class TransitionEditorView; +class TransitionEditorWidget; +class TransitionEditorToolBar; +class TransitionEditorGraphicsLayout; + +class TimelineRulerSectionItem; +class TimelineFrameHandle; +class TimelineAbstractTool; +class TimelineMoveTool; +class TimelineKeyframeItem; +class TimelinePlaceholder; +class TimelineToolBar; +class TransitionEditorPropertyItem; + +class TransitionEditorGraphicsScene : public AbstractScrollGraphicsScene +{ + Q_OBJECT + +signals: + void selectionChanged(); + + void scroll(const TimelineUtils::Side &side); + +public: + explicit TransitionEditorGraphicsScene(TransitionEditorWidget *parent); + + ~TransitionEditorGraphicsScene() override; + + void onShow(); + + void setTransition(const ModelNode &transition); + void clearTransition(); + + void setWidth(int width); + + void invalidateLayout(); + void setDuration(int duration); + + TransitionEditorView *transitionEditorView() const; + TransitionEditorWidget *transitionEditorWidget() const; + TransitionEditorToolBar *toolBar() const; + + qreal rulerScaling() const override; + int rulerWidth() const override; + qreal rulerDuration() const override; + qreal endFrame() const override; + qreal startFrame() const override; + + qreal mapToScene(qreal x) const; + qreal mapFromScene(qreal x) const; + + void setRulerScaling(int scaling); + + void invalidateSectionForTarget(const ModelNode &modelNode); + + void invalidateScene(); + void invalidateCurrentValues(); + void invalidateRecordButtonsStatus(); + + QGraphicsView *graphicsView() const; + QGraphicsView *rulerView() const; + + QRectF selectionBounds() const; + + void selectKeyframes(const SelectionMode &mode, const QList<TimelineKeyframeItem *> &items); + void clearSelection() override; + + void activateLayout(); + + AbstractView *abstractView() const override; + ModelNode transitionModelNode() const; + + TransitionEditorPropertyItem *selectedPropertyItem() const; + void setSelectedPropertyItem(TransitionEditorPropertyItem *item); + + void invalidateScrollbar() override; + +signals: + void statusBarMessageChanged(const QString &message); + +protected: + bool event(QEvent *event) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; + + void keyPressEvent(QKeyEvent *keyEvent) override; + void keyReleaseEvent(QKeyEvent *keyEvent) override; + +private: + void invalidateSections(); + QList<QGraphicsItem *> itemsAt(const QPointF &pos); + +private: + TransitionEditorWidget *m_parent = nullptr; + TransitionEditorGraphicsLayout *m_layout = nullptr; + ModelNode m_transition; + + int m_scrollOffset = 0; + TimelineToolDelegate m_tools; + TransitionEditorPropertyItem *m_selectedProperty = nullptr; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.cpp new file mode 100644 index 0000000000..f5b7b45b05 --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.cpp @@ -0,0 +1,237 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "transitioneditorpropertyitem.h" + +#include "abstractview.h" +#include "timelineconstants.h" +#include "timelineicons.h" +#include "transitioneditorgraphicsscene.h" + +#include <bindingproperty.h> +#include <nodeabstractproperty.h> +#include <rewritertransaction.h> +#include <rewritingexception.h> +#include <theme.h> +#include <variantproperty.h> +#include <qmlobjectnode.h> + +#include <coreplugin/icore.h> +#include <utils/qtcassert.h> +#include <utils/utilsicons.h> + +#include <utils/algorithm.h> +#include <utils/fileutils.h> + +#include <coreplugin/icore.h> + +#include <QCursor> +#include <QGraphicsProxyWidget> +#include <QGraphicsSceneMouseEvent> +#include <QGraphicsView> +#include <QLineEdit> +#include <QMenu> +#include <QPainter> + +#include <algorithm> + +namespace QmlDesigner { + +TransitionEditorPropertyItem *TransitionEditorPropertyItem::create( + const ModelNode &animation, TransitionEditorSectionItem *parent) +{ + auto item = new TransitionEditorPropertyItem(parent); + item->m_animation = animation; + + auto sectionItem = new QGraphicsWidget(item); + + sectionItem->setGeometry(0, + 0, + TimelineConstants::sectionWidth, + TimelineConstants::sectionHeight); + + sectionItem->setZValue(10); + sectionItem->setCursor(Qt::ArrowCursor); + + item->setToolTip(item->propertyName()); + item->resize(parent->size()); + + item->m_barItem = new TransitionEditorBarItem(item); + item->invalidateBar(); + + return item; +} + +int TransitionEditorPropertyItem::type() const +{ + return Type; +} + +void TransitionEditorPropertyItem::updateData() +{ + invalidateBar(); +} + +void TransitionEditorPropertyItem::updateParentData() +{ + TransitionEditorSectionItem::invalidateBar(parentItem()); +} + +bool TransitionEditorPropertyItem::isSelected() const +{ + return transitionEditorGraphicsScene()->selectedPropertyItem() == this; +} + +QString TransitionEditorPropertyItem::propertyName() const +{ + if (m_animation.isValid()) { + const QString propertyName = m_animation.variantProperty("property").value().toString(); + if (!propertyName.isEmpty()) + return propertyName; + return m_animation.variantProperty("properties").value().toString(); + } + return QString(); +} + +void TransitionEditorPropertyItem::paint(QPainter *painter, + const QStyleOptionGraphicsItem *, + QWidget *) +{ + painter->save(); + + static const QColor penColor = Theme::instance()->qmlDesignerBackgroundColorDarker(); + static const QColor textColor = Theme::getColor(Theme::PanelTextColorLight); + static const QColor backgroundColor = Theme::instance() + ->qmlDesignerBackgroundColorDarkAlternate(); + + painter->fillRect(0, 0, TimelineConstants::sectionWidth, size().height(), backgroundColor); + painter->fillRect(TimelineConstants::textIndentationProperties - 4, + 0, + TimelineConstants::sectionWidth - TimelineConstants::textIndentationProperties + + 4, + size().height(), + backgroundColor.darker(110)); + + painter->setPen(penColor); + + drawLine(painter, + TimelineConstants::sectionWidth - 1, + 0, + TimelineConstants::sectionWidth - 1, + size().height()); + + drawLine(painter, + TimelineConstants::textIndentationProperties - 4, + TimelineConstants::sectionHeight - 1, + size().width(), + TimelineConstants::sectionHeight - 1); + + painter->setPen(textColor); + + const QFontMetrics metrics(font()); + + const QString elidedText = metrics.elidedText(propertyName(), + Qt::ElideMiddle, + qreal(TimelineConstants::sectionWidth) * 2.0 / 3 + - TimelineConstants::textIndentationProperties, + 0); + + painter->drawText(TimelineConstants::textIndentationProperties, 12, elidedText); + + painter->restore(); +} + +void TransitionEditorPropertyItem::contextMenuEvent(QGraphicsSceneContextMenuEvent * /*event */) {} + +TransitionEditorPropertyItem::TransitionEditorPropertyItem(TransitionEditorSectionItem *parent) + : TimelineItem(parent) +{ + setPreferredHeight(TimelineConstants::sectionHeight); + setMinimumHeight(TimelineConstants::sectionHeight); + setMaximumHeight(TimelineConstants::sectionHeight); +} + +TransitionEditorGraphicsScene *TransitionEditorPropertyItem::transitionEditorGraphicsScene() const +{ + return qobject_cast<TransitionEditorGraphicsScene *>(scene()); +} + +void TransitionEditorPropertyItem::invalidateBar() +{ + qreal min = 0; + qreal max = 0; + + QTC_ASSERT(m_animation.isValid(), return ); + QTC_ASSERT(m_animation.hasParentProperty(), return ); + + const ModelNode parent = m_animation.parentProperty().parentModelNode(); + + for (const ModelNode &child : parent.directSubModelNodes()) + if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PauseAnimation")) + min = child.variantProperty("duration").value().toDouble(); + + max = m_animation.variantProperty("duration").value().toDouble() + min; + + const qreal sceneMin = m_barItem->mapFromFrameToScene(min); + + QRectF barRect(sceneMin, + 0, + (max - min) * m_barItem->rulerScaling(), + TimelineConstants::sectionHeight - 1); + + m_barItem->setRect(barRect); +} + +AbstractView *TransitionEditorPropertyItem::view() const +{ + return m_animation.view(); +} + +ModelNode TransitionEditorPropertyItem::propertyAnimation() const +{ + return m_animation; +} + +ModelNode TransitionEditorPropertyItem::pauseAnimation() const +{ + QTC_ASSERT(m_animation.isValid(), return {}); + QTC_ASSERT(m_animation.hasParentProperty(), return {}); + + const ModelNode parent = m_animation.parentProperty().parentModelNode(); + + for (const ModelNode &child : parent.directSubModelNodes()) + if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PauseAnimation")) + return child; + + return {}; +} + +void TransitionEditorPropertyItem::select() +{ + transitionEditorGraphicsScene()->setSelectedPropertyItem(this); + m_barItem->update(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.h new file mode 100644 index 0000000000..aba42b599d --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "transitioneditorsectionitem.h" + +#include <modelnode.h> + +#include <QGraphicsRectItem> + +QT_FORWARD_DECLARE_CLASS(QLineEdit) + +namespace QmlDesigner { + +class TransitionEditorGraphicsScene; + +class TransitionEditorPropertyItem : public TimelineItem +{ + Q_OBJECT + +public: + enum { Type = TransitionEditorConstants::transitionEditorPropertyItemUserType }; + + static TransitionEditorPropertyItem *create(const ModelNode &animation, + TransitionEditorSectionItem *parent = nullptr); + int type() const override; + void updateData(); + void updateParentData(); + + bool isSelected() const; + QString propertyName() const; + void invalidateBar(); + AbstractView *view() const; + ModelNode propertyAnimation() const; + ModelNode pauseAnimation() const; + void select(); + +protected: + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; + +private: + TransitionEditorPropertyItem(TransitionEditorSectionItem *parent = nullptr); + TransitionEditorGraphicsScene *transitionEditorGraphicsScene() const; + + ModelNode m_animation; + TransitionEditorBarItem *m_barItem; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp new file mode 100644 index 0000000000..86442059d9 --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp @@ -0,0 +1,803 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "transitioneditorsectionitem.h" +#include "transitioneditorgraphicsscene.h" +#include "transitioneditorpropertyitem.h" + +#include "timelineactions.h" +#include "timelineconstants.h" +#include "timelineicons.h" +#include "timelinepropertyitem.h" +#include "timelineutils.h" + +#include <abstractview.h> +#include <bindingproperty.h> +#include <variantproperty.h> +#include <qmltimeline.h> +#include <qmltimelinekeyframegroup.h> + +#include <rewritingexception.h> + +#include <theme.h> + +#include <utils/qtcassert.h> + +#include <QAction> +#include <QApplication> +#include <QColorDialog> +#include <QGraphicsScene> +#include <QGraphicsSceneMouseEvent> +#include <QGraphicsView> +#include <QHBoxLayout> +#include <QMenu> +#include <QPainter> +#include <QPainterPath> + +#include <QGraphicsView> + +#include <QDebug> + +#include <cmath> +#include <limits> + +namespace QmlDesigner { + +static void scaleDuration(const ModelNode &node, qreal s) +{ + if (node.hasVariantProperty("duration")) { + qreal old = node.variantProperty("duration").value().toDouble(); + node.variantProperty("duration").setValue(qRound(old * s)); + } +} + +static void moveDuration(const ModelNode &node, qreal s) +{ + if (node.hasVariantProperty("duration")) { + qreal old = node.variantProperty("duration").value().toDouble(); + node.variantProperty("duration").setValue(old + s); + } +} + +class ClickDummy : public TimelineItem +{ +public: + explicit ClickDummy(TransitionEditorSectionItem *parent) + : TimelineItem(parent) + { + setGeometry(0, 0, TimelineConstants::sectionWidth, TimelineConstants::sectionHeight); + + setZValue(10); + setCursor(Qt::ArrowCursor); + } + +protected: + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override + { + scene()->sendEvent(parentItem(), event); + } + void mousePressEvent(QGraphicsSceneMouseEvent *event) override + { + scene()->sendEvent(parentItem(), event); + } + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override + { + scene()->sendEvent(parentItem(), event); + } + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override + { + scene()->sendEvent(parentItem(), event); + } +}; + +TransitionEditorSectionItem::TransitionEditorSectionItem(TimelineItem *parent) + : TimelineItem(parent) +{} + +TransitionEditorSectionItem *TransitionEditorSectionItem::create(const ModelNode &animation, + TimelineItem *parent) +{ + auto item = new TransitionEditorSectionItem(parent); + + ModelNode target; + + if (animation.isValid()) { + const QList<ModelNode> propertyAnimations = animation.subModelNodesOfType( + "QtQuick.PropertyAnimation"); + + for (const ModelNode &child : propertyAnimations) { + if (child.hasBindingProperty("target")) + target = child.bindingProperty("target").resolveToModelNode(); + } + } + + item->m_targetNode = target; + item->m_animationNode = animation; + item->createPropertyItems(); + + if (target.isValid()) + item->setToolTip(target.id()); + + item->m_dummyItem = new ClickDummy(item); + item->m_dummyItem->update(); + + item->m_barItem = new TransitionEditorBarItem(item); + item->invalidateBar(); + item->invalidateHeight(); + + return item; +} + +void TransitionEditorSectionItem::invalidateBar() +{ + qreal min = std::numeric_limits<qreal>::max(); + qreal max = 0; + + if (!m_animationNode.isValid()) + return; + + for (const ModelNode &sequential : m_animationNode.directSubModelNodes()) { + qreal locMin = 0; + qreal locMax = 0; + + for (const ModelNode &child : sequential.directSubModelNodes()) { + if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PropertyAnimation")) + locMax = child.variantProperty("duration").value().toDouble(); + else if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PauseAnimation")) + locMin = child.variantProperty("duration").value().toDouble(); + } + + locMax = locMax + locMin; + + min = qMin(min, locMin); + max = qMax(max, locMax); + } + + const qreal sceneMin = m_barItem->mapFromFrameToScene(min); + + QRectF barRect(sceneMin, + 0, + (max - min) * m_barItem->rulerScaling(), + TimelineConstants::sectionHeight - 1); + + m_barItem->setRect(barRect); +} + +int TransitionEditorSectionItem::type() const +{ + return Type; +} + +void TransitionEditorSectionItem::updateData(QGraphicsItem *item) +{ + if (auto sectionItem = qgraphicsitem_cast<TransitionEditorSectionItem *>(item)) + sectionItem->updateData(); +} + +void TransitionEditorSectionItem::invalidateBar(QGraphicsItem *item) +{ + if (auto sectionItem = qgraphicsitem_cast<TransitionEditorSectionItem *>(item)) + sectionItem->invalidateBar(); +} + +void TransitionEditorSectionItem::updateDataForTarget(QGraphicsItem *item, + const ModelNode &target, + bool *b) +{ + if (!target.isValid()) + return; + + if (auto sectionItem = qgraphicsitem_cast<TransitionEditorSectionItem *>(item)) { + if (sectionItem->m_targetNode == target) { //TODO update animation node + sectionItem->updateData(); + if (b) + *b = true; + } + } +} + +void TransitionEditorSectionItem::moveAllDurations(qreal offset) +{ + for (const ModelNode &sequential : m_animationNode.directSubModelNodes()) { + for (const ModelNode &child : sequential.directSubModelNodes()) { + if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PauseAnimation")) + moveDuration(child, offset); + } + } +} + +void TransitionEditorSectionItem::scaleAllDurations(qreal scale) +{ + for (const ModelNode &sequential : m_animationNode.directSubModelNodes()) { + for (const ModelNode &child : sequential.directSubModelNodes()) { + if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PropertyAnimation")) + scaleDuration(child, scale); + } + } +} + +qreal TransitionEditorSectionItem::firstFrame() +{ + return 0; + //if (!m_timeline.isValid()) + //return 0; + + //return m_timeline.minActualKeyframe(m_targetNode); +} + +AbstractView *TransitionEditorSectionItem::view() const +{ + return m_animationNode.view(); +} + +bool TransitionEditorSectionItem::isSelected() const +{ + return m_targetNode.isValid() && m_targetNode.isSelected(); +} + +ModelNode TransitionEditorSectionItem::targetNode() const +{ + return m_targetNode; +} + +static QPixmap rotateby90(const QPixmap &pixmap) +{ + QImage sourceImage = pixmap.toImage(); + QImage destImage(pixmap.height(), pixmap.width(), sourceImage.format()); + + for (int x = 0; x < pixmap.width(); x++) + for (int y = 0; y < pixmap.height(); y++) + destImage.setPixel(y, x, sourceImage.pixel(x, y)); + + QPixmap result = QPixmap::fromImage(destImage); + + result.setDevicePixelRatio(pixmap.devicePixelRatio()); + + return result; +} + +static int devicePixelHeight(const QPixmap &pixmap) +{ + return pixmap.height() / pixmap.devicePixelRatioF(); +} + +void TransitionEditorSectionItem::paint(QPainter *painter, + const QStyleOptionGraphicsItem * /*option*/, + QWidget *) +{ + if (m_targetNode.isValid()) { + painter->save(); + + const QColor textColor = Theme::getColor(Theme::PanelTextColorLight); + const QColor penColor = Theme::instance()->qmlDesignerBackgroundColorDarker(); + QColor brushColor = Theme::getColor(Theme::BackgroundColorDark); + + int fillOffset = 0; + if (isSelected()) { + brushColor = Theme::getColor(Theme::QmlDesigner_HighlightColor); + fillOffset = 1; + } + + painter->fillRect(0, + 0, + TimelineConstants::sectionWidth, + TimelineConstants::sectionHeight - fillOffset, + brushColor); + painter->fillRect(TimelineConstants::sectionWidth, + 0, + size().width() - TimelineConstants::sectionWidth, + size().height(), + Theme::instance()->qmlDesignerBackgroundColorDarkAlternate()); + + painter->setPen(penColor); + drawLine(painter, + TimelineConstants::sectionWidth - 1, + 0, + TimelineConstants::sectionWidth - 1, + size().height() - 1); + drawLine(painter, + TimelineConstants::sectionWidth, + TimelineConstants::sectionHeight - 1, + size().width(), + TimelineConstants::sectionHeight - 1); + + static const QPixmap arrow = Theme::getPixmap("down-arrow"); + + static const QPixmap arrow90 = rotateby90(arrow); + + const QPixmap rotatedArrow = collapsed() ? arrow90 : arrow; + + const int textOffset = QFontMetrics(font()).ascent() + + (TimelineConstants::sectionHeight - QFontMetrics(font()).height()) + / 2; + + painter->drawPixmap(collapsed() ? 6 : 4, + (TimelineConstants::sectionHeight - devicePixelHeight(rotatedArrow)) / 2, + rotatedArrow); + + painter->setPen(textColor); + + QFontMetrics fm(painter->font()); + const QString elidedId = fm.elidedText(m_targetNode.id(), + Qt::ElideMiddle, + TimelineConstants::sectionWidth + - TimelineConstants::textIndentationSections); + painter->drawText(TimelineConstants::textIndentationSections, textOffset, elidedId); + + painter->restore(); + } +} + +void TransitionEditorSectionItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->pos().y() > TimelineConstants::sectionHeight + || event->pos().x() < TimelineConstants::textIndentationSections) { + TimelineItem::mouseDoubleClickEvent(event); + return; + } + + if (event->button() == Qt::LeftButton) { + event->accept(); + toggleCollapsed(); + } +} + +void TransitionEditorSectionItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->pos().y() > TimelineConstants::sectionHeight) { + TimelineItem::mousePressEvent(event); + return; + } + + if (event->button() == Qt::LeftButton) + event->accept(); +} + +void TransitionEditorSectionItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->pos().y() > TimelineConstants::sectionHeight) { + TimelineItem::mouseReleaseEvent(event); + return; + } + + if (event->button() != Qt::LeftButton) + return; + + event->accept(); + + if (event->pos().x() > TimelineConstants::textIndentationSections + && event->button() == Qt::LeftButton) { + if (m_targetNode.isValid()) + m_targetNode.view()->setSelectedModelNode(m_targetNode); + } else { + toggleCollapsed(); + } + update(); +} + +void TransitionEditorSectionItem::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + TimelineItem::resizeEvent(event); + + for (auto child : propertyItems()) { + TransitionEditorPropertyItem *item = static_cast<TransitionEditorPropertyItem *>(child); + item->resize(size().width(), TimelineConstants::sectionHeight); + } +} + +void TransitionEditorSectionItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *) {} + +void TransitionEditorSectionItem::updateData() +{ + invalidateBar(); + resize(rulerWidth(), size().height()); + invalidateProperties(); + update(); +} + +const QList<QGraphicsItem *> TransitionEditorSectionItem::propertyItems() const +{ + QList<QGraphicsItem *> list; + + const QList<QGraphicsItem *> children = childItems(); + for (auto child : children) { + if (m_barItem != child && m_dummyItem != child) + list.append(child); + } + + return list; +} + +void TransitionEditorSectionItem::invalidateHeight() +{ + int height = 0; + bool visible = true; + + if (collapsed()) { + height = TimelineConstants::sectionHeight; + visible = false; + } else { + const QList<ModelNode> propertyAnimations = m_animationNode.subModelNodesOfType( + "QtQuick.PropertyAnimation"); + + height = TimelineConstants::sectionHeight + + propertyAnimations.count() * TimelineConstants::sectionHeight; + visible = true; + } + + for (auto child : propertyItems()) + child->setVisible(visible); + + setPreferredHeight(height); + setMinimumHeight(height); + setMaximumHeight(height); + + auto transitionScene = qobject_cast<TransitionEditorGraphicsScene *>(scene()); + transitionScene->activateLayout(); +} + +void TransitionEditorSectionItem::createPropertyItems() +{ + int yPos = TimelineConstants::sectionHeight; + const QList<ModelNode> propertyAnimations = m_animationNode.subModelNodesOfType( + "QtQuick.PropertyAnimation"); + for (const auto &anim : propertyAnimations) { + auto item = TransitionEditorPropertyItem::create(anim, this); + item->setY(yPos); + yPos = yPos + TimelineConstants::sectionHeight; + } +} + +void TransitionEditorSectionItem::invalidateProperties() +{ + for (auto child : propertyItems()) { + delete child; + } + + createPropertyItems(); + + for (auto child : propertyItems()) { + TransitionEditorPropertyItem *item = static_cast<TransitionEditorPropertyItem *>(child); + item->updateData(); + item->resize(size().width(), TimelineConstants::sectionHeight); + } + invalidateHeight(); +} + +bool TransitionEditorSectionItem::collapsed() const +{ + return m_targetNode.isValid() && !m_targetNode.hasAuxiliaryData("timeline_expanded"); +} + +qreal TransitionEditorSectionItem::rulerWidth() const +{ + return static_cast<TimelineGraphicsScene *>(scene())->rulerWidth(); +} + +void TransitionEditorSectionItem::toggleCollapsed() +{ + QTC_ASSERT(m_targetNode.isValid(), return ); + + if (collapsed()) + m_targetNode.setAuxiliaryData("timeline_expanded", true); + else + m_targetNode.removeAuxiliaryData("timeline_expanded"); + + invalidateHeight(); +} + +TransitionEditorBarItem::TransitionEditorBarItem(TransitionEditorSectionItem *parent) + : TimelineMovableAbstractItem(parent) +{ + setAcceptHoverEvents(true); + setPen(Qt::NoPen); +} + +TransitionEditorBarItem::TransitionEditorBarItem(TransitionEditorPropertyItem *parent) + : TimelineMovableAbstractItem(parent) +{ + setAcceptHoverEvents(true); + setPen(Qt::NoPen); +} + +void TransitionEditorBarItem::itemMoved(const QPointF &start, const QPointF &end) +{ + if (isActiveHandle(Location::Undefined)) + dragInit(rect(), start); + + qreal min = qreal(TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset + - scrollOffset()); + qreal max = qreal(abstractScrollGraphicsScene()->rulerWidth() - TimelineConstants::sectionWidth + + rect().width()); + + const qreal minFrameX = mapFromFrameToScene(abstractScrollGraphicsScene()->startFrame()); + const qreal maxFrameX = mapFromFrameToScene(abstractScrollGraphicsScene()->endFrame()); + + if (min < minFrameX) + min = minFrameX; + + if (max > maxFrameX) + max = maxFrameX; + + if (isActiveHandle(Location::Center)) + dragCenter(rect(), end, min, max); + else + dragHandle(rect(), end, min, max); + + emit abstractScrollGraphicsScene()->statusBarMessageChanged( + tr("Range from %1 to %2") + .arg(qRound(mapFromSceneToFrame(rect().x()))) + .arg(qRound(mapFromSceneToFrame(rect().width() + rect().x())))); +} + +void TransitionEditorBarItem::commitPosition(const QPointF & /*point*/) +{ + if (sectionItem() && sectionItem()->view()) { + if (m_handle != Location::Undefined) { + sectionItem() + ->view() + ->executeInTransaction("TransitionEditorBarItem::commitPosition", [this]() { + qreal scaleFactor = rect().width() / m_oldRect.width(); + + qreal moved = (rect().topLeft().x() - m_oldRect.topLeft().x()) / rulerScaling(); + qreal supposedFirstFrame = qRound(moved); + + sectionItem()->scaleAllDurations(scaleFactor); + sectionItem()->moveAllDurations(supposedFirstFrame); + sectionItem()->updateData(); + }); + } + } else if (propertyItem() && propertyItem()->view()) { + if (m_handle != Location::Undefined) { + propertyItem() + ->view() + ->executeInTransaction("TransitionEditorBarItem::commitPosition", [this]() { + qreal scaleFactor = rect().width() / m_oldRect.width(); + qreal moved = (rect().topLeft().x() - m_oldRect.topLeft().x()) / rulerScaling(); + qreal supposedFirstFrame = qRound(moved); + scaleDuration(propertyItem()->propertyAnimation(), scaleFactor); + moveDuration(propertyItem()->pauseAnimation(), supposedFirstFrame); + propertyItem()->updateData(); + propertyItem()->updateParentData(); + }); + } + } + + m_handle = Location::Undefined; + m_bounds = Location::Undefined; + m_pivot = 0.0; + m_oldRect = QRectF(); + scrollOffsetChanged(); +} + +void TransitionEditorBarItem::scrollOffsetChanged() +{ + if (sectionItem()) + sectionItem()->invalidateBar(); + else if (propertyItem()) + propertyItem()->invalidateBar(); +} + +void TransitionEditorBarItem::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + QColor brushColor = Theme::getColor(Theme::QmlDesigner_HighlightColor); + QColor brushColorSection = Theme::getColor(Theme::QmlDesigner_HighlightColor).darker(120); + QColor penColor = Theme::getColor(Theme::QmlDesigner_HighlightColor).lighter(140); + + const QRectF itemRect = rect(); + + painter->save(); + painter->setClipRect(TimelineConstants::sectionWidth, + 0, + itemRect.width() + itemRect.x(), + itemRect.height()); + + if (sectionItem()) + painter->fillRect(itemRect, brushColorSection); + else + painter->fillRect(itemRect, brushColor); + + if (propertyItem() && propertyItem()->isSelected()) { + painter->setPen(penColor); + painter->drawRect(itemRect.adjusted(0, 0, 0, -1)); + } + + painter->restore(); +} + +void TransitionEditorBarItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + const auto p = event->pos(); + + QRectF left, right; + if (handleRects(rect(), left, right)) { + if (left.contains(p) || right.contains(p)) { + if (cursor().shape() != Qt::SizeHorCursor) + setCursor(QCursor(Qt::SizeHorCursor)); + } else if (rect().contains(p)) { + if (cursor().shape() != Qt::ClosedHandCursor) + setCursor(QCursor(Qt::ClosedHandCursor)); + } + } else { + if (rect().contains(p)) + setCursor(QCursor(Qt::ClosedHandCursor)); + } +} + +void TransitionEditorBarItem::contextMenuEvent(QGraphicsSceneContextMenuEvent * /*event*/) {} + +void TransitionEditorBarItem::mousePressEvent(QGraphicsSceneMouseEvent * /*event*/) +{ + if (propertyItem()) + propertyItem()->select(); +} + +TransitionEditorSectionItem *TransitionEditorBarItem::sectionItem() const +{ + return qgraphicsitem_cast<TransitionEditorSectionItem *>(parentItem()); +} + +TransitionEditorPropertyItem *TransitionEditorBarItem::propertyItem() const +{ + return qgraphicsitem_cast<TransitionEditorPropertyItem *>(parentItem()); +} + +void TransitionEditorBarItem::dragInit(const QRectF &rect, const QPointF &pos) +{ + QRectF left, right; + m_oldRect = rect; + if (handleRects(rect, left, right)) { + if (left.contains(pos)) { + m_handle = Location::Left; + m_pivot = pos.x() - left.topLeft().x(); + } else if (right.contains(pos)) { + m_handle = Location::Right; + m_pivot = pos.x() - right.topRight().x(); + } else if (rect.contains(pos)) { + m_handle = Location::Center; + m_pivot = pos.x() - rect.topLeft().x(); + } + + } else { + if (rect.contains(pos)) { + m_handle = Location::Center; + m_pivot = pos.x() - rect.topLeft().x(); + } + } +} + +void TransitionEditorBarItem::dragCenter(QRectF rect, const QPointF &pos, qreal min, qreal max) +{ + if (validateBounds(pos.x() - rect.topLeft().x())) { + qreal targetX = pos.x() - m_pivot; + + if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { // snapping + qreal snappedTargetFrame = abstractScrollGraphicsScene()->snap(mapFromSceneToFrame(targetX)); + targetX = mapFromFrameToScene(snappedTargetFrame); + } + rect.moveLeft(targetX); + if (rect.topLeft().x() < min) { + rect.moveLeft(min); + setOutOfBounds(Location::Left); + } else if (rect.topRight().x() > max) { + rect.moveRight(max); + setOutOfBounds(Location::Right); + } + setRect(rect); + } +} + +void TransitionEditorBarItem::dragHandle(QRectF rect, const QPointF &pos, qreal min, qreal max) +{ + QRectF left, right; + handleRects(rect, left, right); + + if (isActiveHandle(Location::Left)) { + if (validateBounds(pos.x() - left.topLeft().x())) { + qreal targetX = pos.x() - m_pivot; + if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { // snapping + qreal snappedTargetFrame = abstractScrollGraphicsScene()->snap(mapFromSceneToFrame(targetX)); + targetX = mapFromFrameToScene(snappedTargetFrame); + } + rect.setLeft(targetX); + if (rect.left() < min) { + rect.setLeft(min); + setOutOfBounds(Location::Left); + } else if (rect.left() >= rect.right() - minimumBarWidth) + rect.setLeft(rect.right() - minimumBarWidth); + + setRect(rect); + } + } else if (isActiveHandle(Location::Right)) { + if (validateBounds(pos.x() - right.topRight().x())) { + qreal targetX = pos.x() - m_pivot; + if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { // snapping + qreal snappedTargetFrame = abstractScrollGraphicsScene()->snap(mapFromSceneToFrame(targetX)); + targetX = mapFromFrameToScene(snappedTargetFrame); + } + rect.setRight(targetX); + if (rect.right() > max) { + rect.setRight(max); + setOutOfBounds(Location::Right); + } else if (rect.right() <= rect.left() + minimumBarWidth) + rect.setRight(rect.left() + minimumBarWidth); + + setRect(rect); + } + } +} + +bool TransitionEditorBarItem::handleRects(const QRectF &rect, QRectF &left, QRectF &right) const +{ + if (rect.width() < minimumBarWidth) + return false; + + const qreal handleSize = rect.height(); + + auto handleRect = QRectF(0, 0, handleSize, handleSize); + handleRect.moveCenter(rect.center()); + + handleRect.moveLeft(rect.left()); + left = handleRect; + + handleRect.moveRight(rect.right()); + right = handleRect; + + return true; +} + +bool TransitionEditorBarItem::isActiveHandle(Location location) const +{ + return m_handle == location; +} + +void TransitionEditorBarItem::setOutOfBounds(Location location) +{ + m_bounds = location; + update(); +} + +bool TransitionEditorBarItem::validateBounds(qreal distance) +{ + update(); + if (m_bounds == Location::Left) { + if (distance > m_pivot) + m_bounds = Location::Center; + return false; + + } else if (m_bounds == Location::Right) { + if (distance < m_pivot) + m_bounds = Location::Center; + return false; + } + return true; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h new file mode 100644 index 0000000000..22aa21dd28 --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "transitioneditorconstants.h" + +#include <timelineeditor/timelineitem.h> +#include <timelineeditor/timelinemovableabstractitem.h> + +#include <modelnode.h> +#include <qmltimeline.h> + +QT_FORWARD_DECLARE_CLASS(QComboBox) +QT_FORWARD_DECLARE_CLASS(QPainter) + +namespace QmlDesigner { + +class TransitionEditorSectionItem; +class TransitionEditorPropertyItem; + +class TransitionEditorBarItem : public TimelineMovableAbstractItem +{ + Q_DECLARE_TR_FUNCTIONS(TimelineBarItem) + + enum class Location { Undefined, Center, Left, Right }; + +public: + explicit TransitionEditorBarItem(TransitionEditorSectionItem *parent); + explicit TransitionEditorBarItem(TransitionEditorPropertyItem *parent); + + void itemMoved(const QPointF &start, const QPointF &end) override; + void commitPosition(const QPointF &point) override; + +protected: + void scrollOffsetChanged() override; + void paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget = nullptr) override; + void hoverMoveEvent(QGraphicsSceneHoverEvent *) override; + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + +private: + TransitionEditorSectionItem *sectionItem() const; + TransitionEditorPropertyItem *propertyItem() const; + + void dragInit(const QRectF &rect, const QPointF &pos); + void dragCenter(QRectF rect, const QPointF &pos, qreal min, qreal max); + void dragHandle(QRectF rect, const QPointF &pos, qreal min, qreal max); + bool handleRects(const QRectF &rect, QRectF &left, QRectF &right) const; + bool isActiveHandle(Location location) const; + + void setOutOfBounds(Location location); + bool validateBounds(qreal pivot); + +private: + Location m_handle = Location::Undefined; + + Location m_bounds = Location::Undefined; + + qreal m_pivot = 0.0; + + QRectF m_oldRect; + + static constexpr qreal minimumBarWidth = 2.0 + * static_cast<qreal>(TimelineConstants::sectionHeight); +}; + +class TransitionEditorSectionItem : public TimelineItem +{ + Q_OBJECT + +public: + enum { Type = TransitionEditorConstants::transitionEditorSectionItemUserType }; + + static TransitionEditorSectionItem *create(const ModelNode &animation, + TimelineItem *parent); + + void invalidateBar(); + + int type() const override; + + static void updateData(QGraphicsItem *item); + static void invalidateBar(QGraphicsItem *item); + static void updateDataForTarget(QGraphicsItem *item, const ModelNode &target, bool *b); + + void moveAllDurations(qreal offset); + void scaleAllDurations(qreal scale); + qreal firstFrame(); + AbstractView *view() const; + bool isSelected() const; + + ModelNode targetNode() const; + void updateData(); + +protected: + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + void resizeEvent(QGraphicsSceneResizeEvent *event) override; + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; + +private: + void invalidateHeight(); + void invalidateProperties(); + bool collapsed() const; + qreal rulerWidth() const; + void toggleCollapsed(); + void createPropertyItems(); + const QList<QGraphicsItem *> propertyItems() const; + + TransitionEditorSectionItem(TimelineItem *parent = nullptr); + ModelNode m_targetNode; + ModelNode m_animationNode; + + TransitionEditorBarItem *m_barItem; + TimelineItem *m_dummyItem; + +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.cpp new file mode 100644 index 0000000000..57993d7ac2 --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.cpp @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "transitioneditorsettingsdialog.h" +#include "timelinesettingsdialog.h" +#include "transitioneditorview.h" +#include "ui_transitioneditorsettingsdialog.h" + +#include "timelineicons.h" +#include "timelinesettingsmodel.h" +#include "transitionform.h" + +#include <abstractview.h> +#include <bindingproperty.h> +#include <exception> +#include <nodelistproperty.h> +#include <nodemetainfo.h> +#include <rewritertransaction.h> +#include <variantproperty.h> + +#include <utils/algorithm.h> +#include <utils/qtcassert.h> + +#include <QKeyEvent> +#include <QToolBar> + +namespace QmlDesigner { + +static void deleteAllTabs(QTabWidget *tabWidget) +{ + while (tabWidget->count() > 0) { + QWidget *w = tabWidget->widget(0); + tabWidget->removeTab(0); + delete w; + } +} + +static ModelNode getTransitionFromTabWidget(QTabWidget *tabWidget) +{ + QWidget *w = tabWidget->currentWidget(); + if (w) + return qobject_cast<TransitionForm *>(w)->transition(); + return QmlTimeline(); +} + +static void setTabForTransition(QTabWidget *tabWidget, const ModelNode &timeline) +{ + for (int i = 0; i < tabWidget->count(); ++i) { + QWidget *w = tabWidget->widget(i); + if (qobject_cast<TransitionForm *>(w)->transition() == timeline) { + tabWidget->setCurrentIndex(i); + return; + } + } +} + +TransitionEditorSettingsDialog::TransitionEditorSettingsDialog(QWidget *parent, + class TransitionEditorView *view) + : QDialog(parent) + , ui(new Ui::TransitionEditorSettingsDialog) + , m_transitionEditorView(view) +{ + //m_timelineSettingsModel = new TimelineSettingsModel(this, view); + + ui->setupUi(this); + + auto *transitionCornerWidget = new QToolBar; + + auto *transitionAddAction = new QAction(TimelineIcons::ADD_TIMELINE.icon(), + tr("Add Transition")); + auto *transitionRemoveAction = new QAction(TimelineIcons::REMOVE_TIMELINE.icon(), + tr("Remove Transition")); + + connect(transitionAddAction, &QAction::triggered, this, [this]() { + setupTransitions(m_transitionEditorView->addNewTransition()); + }); + + connect(transitionRemoveAction, &QAction::triggered, this, [this]() { + ModelNode transition = getTransitionFromTabWidget(ui->timelineTab); + if (transition.isValid()) { + transition.destroy(); + setupTransitions({}); + } + }); + + transitionCornerWidget->addAction(transitionAddAction); + transitionCornerWidget->addAction(transitionRemoveAction); + + ui->timelineTab->setCornerWidget(transitionCornerWidget, Qt::TopRightCorner); + + setupTransitions({}); + + connect(ui->timelineTab, &QTabWidget::currentChanged, this, [this]() { + m_currentTransition = getTransitionFromTabWidget(ui->timelineTab); + }); +} + +void TransitionEditorSettingsDialog::setCurrentTransition(const ModelNode &timeline) +{ + m_currentTransition = timeline; + setTabForTransition(ui->timelineTab, m_currentTransition); +} + +TransitionEditorSettingsDialog::~TransitionEditorSettingsDialog() +{ + delete ui; +} + +void TransitionEditorSettingsDialog::keyPressEvent(QKeyEvent *event) +{ + switch (event->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + /* ignore */ + break; + + default: + QDialog::keyPressEvent(event); + } +} + +void TransitionEditorSettingsDialog::setupTransitions(const ModelNode &newTransition) +{ + deleteAllTabs(ui->timelineTab); + + const QList<ModelNode> &transitions = m_transitionEditorView->allTransitions(); + + if (transitions.isEmpty()) { + m_currentTransition = {}; + auto transitionForm = new TransitionForm(this); + transitionForm->setDisabled(true); + ui->timelineTab->addTab(transitionForm, tr("No Transition")); + return; + } + + for (const auto &transition : transitions) + addTransitionTab(transition); + + if (newTransition.isValid()) { + m_currentTransition = newTransition; + } else { + m_currentTransition = transitions.constFirst(); + } + + setTabForTransition(ui->timelineTab, m_currentTransition); +} + +void TransitionEditorSettingsDialog::addTransitionTab(const QmlTimeline &node) +{ + auto transitionForm = new TransitionForm(this); + ui->timelineTab->addTab(transitionForm, node.modelNode().displayName()); + transitionForm->setTransition(node); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.h new file mode 100644 index 0000000000..ef9c6ad0c4 --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <qmltimeline.h> + +#include <QDialog> + +QT_FORWARD_DECLARE_CLASS(QSpinBox) + +namespace QmlDesigner { + +class TransitionForm; +class TransitionEditorView; + +namespace Ui { +class TransitionEditorSettingsDialog; +} + +class TransitionEditorSettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit TransitionEditorSettingsDialog(QWidget *parent, class TransitionEditorView *view); + void setCurrentTransition(const ModelNode &timeline); + ~TransitionEditorSettingsDialog() override; + +protected: + void keyPressEvent(QKeyEvent *event) override; + +private: + void setupTransitions(const ModelNode &node); + + void addTransitionTab(const QmlTimeline &node); + + Ui::TransitionEditorSettingsDialog *ui; + + TransitionEditorView *m_transitionEditorView; + ModelNode m_currentTransition; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.ui b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.ui new file mode 100644 index 0000000000..282c2c475e --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.ui @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QmlDesigner::TransitionEditorSettingsDialog</class> + <widget class="QDialog" name="QmlDesigner::TransitionEditorSettingsDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>519</width> + <height>582</height> + </rect> + </property> + <property name="windowTitle"> + <string>Transition Settings</string> + </property> + <property name="modal"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTabWidget" name="timelineTab"> + <property name="currentIndex"> + <number>-1</number> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>QmlDesigner::TransitionEditorSettingsDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>QmlDesigner::TransitionEditorSettingsDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp new file mode 100644 index 0000000000..af19629234 --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp @@ -0,0 +1,342 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "transitioneditortoolbar.h" +#include "transitioneditorgraphicsscene.h" + +#include "timelineconstants.h" + +#include "timelineicons.h" + +#include "timelineview.h" +#include "timelinewidget.h" + +#include <designeractionmanager.h> +#include <nodelistproperty.h> +#include <theme.h> +#include <variantproperty.h> +#include <qmlstate.h> +#include <qmltimeline.h> + +#include <coreplugin/actionmanager/actionmanager.h> +#include <coreplugin/actionmanager/command.h> +#include <coreplugin/icore.h> + +#include <utils/algorithm.h> + +#include <QApplication> +#include <QComboBox> +#include <QIntValidator> +#include <QLineEdit> +#include <QResizeEvent> +#include <QSlider> + +#include <cmath> + +namespace QmlDesigner { + +static bool isSpacer(QObject *object) +{ + return object->property("spacer_widget").toBool(); +} + +static QWidget *createSpacer() +{ + QWidget *spacer = new QWidget(); + spacer->setProperty("spacer_widget", true); + return spacer; +} + +static int controlWidth(QToolBar *bar, QObject *control) +{ + QWidget *widget = nullptr; + + if (auto *action = qobject_cast<QAction *>(control)) + widget = bar->widgetForAction(action); + + if (widget == nullptr) + widget = qobject_cast<QWidget *>(control); + + if (widget) + return widget->width(); + + return 0; +} + +static QAction *createAction(const Core::Id &id, + const QIcon &icon, + const QString &name, + const QKeySequence &shortcut) +{ + QString text = QString("%1 (%2)").arg(name).arg(shortcut.toString()); + + Core::Context context(TimelineConstants::C_QMLTIMELINE); + + auto *action = new QAction(icon, text); + auto *command = Core::ActionManager::registerAction(action, id, context); + command->setDefaultKeySequence(shortcut); + + return action; +} + +TransitionEditorToolBar::TransitionEditorToolBar(QWidget *parent) + : QToolBar(parent) + , m_grp() +{ + setContentsMargins(0, 0, 0, 0); + createLeftControls(); + createCenterControls(); + createRightControls(); +} + +void TransitionEditorToolBar::reset() {} + +int TransitionEditorToolBar::scaleFactor() const +{ + if (m_scale) + return m_scale->value(); + return 0; +} + +QString TransitionEditorToolBar::currentTransitionId() const +{ + return m_transitionComboBox->currentText(); +} + +void TransitionEditorToolBar::setBlockReflection(bool block) +{ + m_blockReflection = block; +} + +void TransitionEditorToolBar::updateComboBox(const ModelNode &root) +{ + if (root.isValid() && root.hasProperty("transitions")) { + NodeAbstractProperty transitions = root.nodeAbstractProperty("transitions"); + if (transitions.isValid()) + for (const ModelNode &transition : transitions.directSubNodes()) + m_transitionComboBox->addItem(transition.id()); + } +} + +void TransitionEditorToolBar::setCurrentTransition(const ModelNode &transition) +{ + if (m_blockReflection) + return; + + if (transition.isValid()) { + m_transitionComboBox->clear(); + const ModelNode root = transition.view()->rootModelNode(); + updateComboBox(root); + m_transitionComboBox->setCurrentText(transition.id()); + } else { + m_transitionComboBox->clear(); + m_transitionComboBox->setCurrentText(""); + } +} + +void TransitionEditorToolBar::setDuration(qreal frame) +{ + auto text = QString::number(frame, 'f', 0); + m_duration->setText(text); +} + +void TransitionEditorToolBar::setScaleFactor(int factor) +{ + const QSignalBlocker blocker(m_scale); + m_scale->setValue(factor); +} + +void TransitionEditorToolBar::setActionEnabled(const QString &name, bool enabled) +{ + for (auto *action : actions()) + if (action->objectName() == name) + action->setEnabled(enabled); +} + +void TransitionEditorToolBar::createLeftControls() +{ + auto addActionToGroup = [&](QAction *action) { + addAction(action); + m_grp << action; + }; + + auto addWidgetToGroup = [&](QWidget *widget) { + addWidget(widget); + m_grp << widget; + }; + + auto addSpacingToGroup = [&](int width) { + auto *widget = new QWidget; + widget->setFixedWidth(width); + addWidget(widget); + m_grp << widget; + }; + + addSpacingToGroup(5); + + auto *settingsAction = createAction(TimelineConstants::C_SETTINGS, + TimelineIcons::ANIMATION.icon(), + tr("Transition Settings"), + QKeySequence(Qt::Key_S)); + connect(settingsAction, + &QAction::triggered, + this, + &TransitionEditorToolBar::settingDialogClicked); + + addActionToGroup(settingsAction); + + addWidgetToGroup(createSpacer()); + + m_transitionComboBox = new QComboBox(this); + addWidgetToGroup(m_transitionComboBox); + + connect(m_transitionComboBox, &QComboBox::currentTextChanged, this, [this]() { + emit currentTransitionChanged(m_transitionComboBox->currentText()); + }); +} + +static QLineEdit *createToolBarLineEdit(QWidget *parent) +{ + auto lineEdit = new QLineEdit(parent); + lineEdit->setStyleSheet("* { background-color: rgba(0, 0, 0, 0); }"); + lineEdit->setFixedWidth(48); + lineEdit->setAlignment(Qt::AlignCenter); + + QPalette pal = parent->palette(); + pal.setColor(QPalette::Text, Theme::instance()->color(Utils::Theme::PanelTextColorLight)); + lineEdit->setPalette(pal); + QValidator *validator = new QIntValidator(-100000, 100000, lineEdit); + lineEdit->setValidator(validator); + + return lineEdit; +} + +void TransitionEditorToolBar::createCenterControls() +{ + addSpacing(10); + + auto *curvePicker = createAction(TimelineConstants::C_CURVE_PICKER, + TimelineIcons::CURVE_EDITOR.icon(), + tr("Easing Curve Editor"), + QKeySequence(Qt::Key_C)); + + curvePicker->setObjectName("Easing Curve Editor"); + connect(curvePicker, &QAction::triggered, this, &TransitionEditorToolBar::openEasingCurveEditor); + addAction(curvePicker); + + addSpacing(10); + +#if 0 + addSeparator(); + + addSpacing(10); + + auto *curveEditor = new QAction(TimelineIcons::CURVE_PICKER.icon(), tr("Curve Editor")); + addAction(curveEditor); +#endif +} + +void TransitionEditorToolBar::createRightControls() +{ + auto *spacer = createSpacer(); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + addWidget(spacer); + + addSeparator(); + addSpacing(10); + + auto *zoomOut = createAction(TimelineConstants::C_ZOOM_OUT, + TimelineIcons::ZOOM_SMALL.icon(), + tr("Zoom Out"), + QKeySequence(QKeySequence::ZoomOut)); + + connect(zoomOut, &QAction::triggered, [this]() { + m_scale->setValue(m_scale->value() - m_scale->pageStep()); + }); + addAction(zoomOut); + + addSpacing(10); + + m_scale = new QSlider(this); + m_scale->setOrientation(Qt::Horizontal); + m_scale->setMaximumWidth(200); + m_scale->setMinimumWidth(100); + m_scale->setMinimum(0); + m_scale->setMaximum(100); + m_scale->setValue(0); + + connect(m_scale, &QSlider::valueChanged, this, &TransitionEditorToolBar::scaleFactorChanged); + addWidget(m_scale); + + addSpacing(10); + + auto *zoomIn = createAction(TimelineConstants::C_ZOOM_IN, + TimelineIcons::ZOOM_BIG.icon(), + tr("Zoom In"), + QKeySequence(QKeySequence::ZoomIn)); + + connect(zoomIn, &QAction::triggered, [this]() { + m_scale->setValue(m_scale->value() + m_scale->pageStep()); + }); + addAction(zoomIn); + + addSpacing(10); + + addSeparator(); + + m_duration = createToolBarLineEdit(this); + addWidget(m_duration); + + auto emitEndChanged = [this]() { emit durationChanged(m_duration->text().toInt()); }; + connect(m_duration, &QLineEdit::editingFinished, emitEndChanged); +} + +void TransitionEditorToolBar::addSpacing(int width) +{ + auto *widget = new QWidget; + widget->setFixedWidth(width); + addWidget(widget); +} + +void TransitionEditorToolBar::resizeEvent(QResizeEvent *event) +{ + Q_UNUSED(event) + + int width = 0; + QWidget *spacer = nullptr; + for (auto *object : qAsConst(m_grp)) { + if (isSpacer(object)) + spacer = qobject_cast<QWidget *>(object); + else + width += controlWidth(this, object); + } + + if (spacer) { + int spacerWidth = TimelineConstants::sectionWidth - width - 12; + spacer->setFixedWidth(spacerWidth > 0 ? spacerWidth : 0); + } +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.h new file mode 100644 index 0000000000..eba74021ca --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "animationcurvedialog.h" +#include "animationcurveeditormodel.h" + +#include <QToolBar> + +QT_FORWARD_DECLARE_CLASS(QComboBox) +QT_FORWARD_DECLARE_CLASS(QLineEdit) +QT_FORWARD_DECLARE_CLASS(QObject) +QT_FORWARD_DECLARE_CLASS(QResizeEvent) +QT_FORWARD_DECLARE_CLASS(QSlider) +QT_FORWARD_DECLARE_CLASS(QWidget) + +namespace QmlDesigner { + +class TimelineWidget; + +class QmlTimeline; + +class TransitionEditorToolBar : public QToolBar +{ + Q_OBJECT + +signals: + void settingDialogClicked(); + + void scaleFactorChanged(int value); + void durationChanged(int value); + void currentTransitionChanged(const QString &name); + void openEasingCurveEditor(); + +public: + explicit TransitionEditorToolBar(QWidget *parent = nullptr); + + void reset(); + + int scaleFactor() const; + QString currentTransitionId() const; + + void setBlockReflection(bool block); + void setCurrentTransition(const ModelNode &transition); + void setDuration(qreal frame); + void setScaleFactor(int factor); + + void setActionEnabled(const QString &name, bool enabled); + + void updateComboBox(const ModelNode &root); +protected: + void resizeEvent(QResizeEvent *event) override; + +private: + void createLeftControls(); + void createCenterControls(); + void createRightControls(); + void addSpacing(int width); + + QList<QObject *> m_grp; + + QComboBox *m_transitionComboBox = nullptr; + QSlider *m_scale = nullptr; + QLineEdit *m_duration = nullptr; + + bool m_blockReflection = false; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp new file mode 100644 index 0000000000..a7ead0afd2 --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp @@ -0,0 +1,348 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "transitioneditorview.h" + +#include "transitioneditortoolbar.h" +#include "transitioneditorwidget.h" + +#include "transitioneditorgraphicsscene.h" +#include "transitioneditorsettingsdialog.h" + +#include <bindingproperty.h> +#include <exception.h> +#include <modelnodecontextmenu_helper.h> +#include <nodeabstractproperty.h> +#include <nodelistproperty.h> +#include <nodemetainfo.h> +#include <rewritertransaction.h> +#include <variantproperty.h> +#include <viewmanager.h> +#include <qmldesignericons.h> +#include <qmldesignerplugin.h> +#include <qmlitemnode.h> +#include <qmlobjectnode.h> +#include <qmlstate.h> +#include <qmltimeline.h> +#include <qmltimelinekeyframegroup.h> + +#include <designmodecontext.h> + +#include <coreplugin/icore.h> +#include <coreplugin/messagebox.h> + +#include <utils/algorithm.h> +#include <utils/qtcassert.h> + +#include <QTimer> + +namespace QmlDesigner { + +TransitionEditorView::TransitionEditorView(QObject *parent) + : AbstractView(parent) + , m_transitionEditorWidget(nullptr) +{ + +} + +TransitionEditorView::~TransitionEditorView() = default; + +void TransitionEditorView::modelAttached(Model *model) +{ + AbstractView::modelAttached(model); + if (m_transitionEditorWidget) + m_transitionEditorWidget->init(); +} + +void TransitionEditorView::modelAboutToBeDetached(Model *model) +{ + m_transitionEditorWidget->reset(); + + AbstractView::modelAboutToBeDetached(model); +} + +void TransitionEditorView::nodeCreated(const ModelNode & /*createdNode*/) {} + +void TransitionEditorView::nodeAboutToBeRemoved(const ModelNode & /*removedNode*/) {} + +void TransitionEditorView::nodeRemoved(const ModelNode & removedNode, + const NodeAbstractProperty &parentProperty, + PropertyChangeFlags /*propertyChange*/) +{ + if (parentProperty.name() == "transitions") + widget()->updateData(removedNode); +} + +void TransitionEditorView::nodeReparented(const ModelNode &node, + const NodeAbstractProperty &newPropertyParent, + const NodeAbstractProperty & /*oldPropertyParent*/, + AbstractView::PropertyChangeFlags /*propertyChange*/) +{ + if (newPropertyParent.name() == "transitions") + asyncUpdate(node); + + const ModelNode parent = newPropertyParent.parentModelNode(); + + qDebug() << Q_FUNC_INFO << parent; + if (parent.isValid() && parent.metaInfo().isValid() + && parent.metaInfo().isSubclassOf("QtQuick.Transition")) { + asyncUpdate(parent); + } +} + +void TransitionEditorView::instancePropertyChanged( + const QList<QPair<ModelNode, PropertyName>> & /*propertyList*/) +{ + +} + +void TransitionEditorView::variantPropertiesChanged( + const QList<VariantProperty> & /* propertyList */, + AbstractView::PropertyChangeFlags /*propertyChange*/) +{ + +} + +void TransitionEditorView::bindingPropertiesChanged( + const QList<BindingProperty> & /*propertyList */, + AbstractView::PropertyChangeFlags /* propertyChange */) +{ + +} + +void TransitionEditorView::selectedNodesChanged(const QList<ModelNode> & /*selectedNodeList*/, + const QList<ModelNode> & /*lastSelectedNodeList*/) +{ + +} + +void TransitionEditorView::propertiesAboutToBeRemoved( + const QList<AbstractProperty> & /*propertyList */) +{ + +} + +void TransitionEditorView::propertiesRemoved(const QList<AbstractProperty> &propertyList) +{ + for (const AbstractProperty &property : propertyList) { + if (property.name() == "transitions") + widget()->init(); + } +} + +bool TransitionEditorView::hasWidget() const +{ + return true; +} + +void TransitionEditorView::nodeIdChanged(const ModelNode &node, const QString &, const QString &) +{ + if (node.metaInfo().isValid() && node.metaInfo().isSubclassOf("QtQuick.Transition")) + widget()->init(); +} + +void TransitionEditorView::currentStateChanged(const ModelNode &) +{ + +} + +TransitionEditorWidget *TransitionEditorView::widget() const +{ + return m_transitionEditorWidget; +} + +void TransitionEditorView::registerActions() +{ + +} + +ModelNode TransitionEditorView::addNewTransition() +{ + QList<QmlModelState> states; + const ModelNode root = rootModelNode(); + + if (QmlVisualNode::isValidQmlVisualNode(root)) { + states = QmlVisualNode(root).states().allStates(); + } + + if (states.isEmpty()) { + Core::AsynchronousMessageBox::warning(tr("No States Defined"), + tr("There are no states defined in this component.")); + return {}; + } + + QHash<QString, QStringList> idPropertyList; + + const QVector<TypeName> validProperties = {"int", "real", "double", "qreal", "color", "QColor"}; + + for (const QmlModelState &state : qAsConst(states)) { + for (const QmlPropertyChanges & change : state.propertyChanges()) { + QStringList locList; + const ModelNode target = change.target(); + const QString targetId = target.id(); + if (target.isValid() && target.hasMetaInfo()) { + for (const VariantProperty &property : change.modelNode().variantProperties()) { + TypeName typeName = target.metaInfo().propertyTypeName(property.name()); + + if (validProperties.contains(typeName)) + locList.append(QString::fromUtf8(property.name())); + } + if (idPropertyList.contains(targetId)) { + QStringList newlist = idPropertyList.value(targetId); + for (const QString &str :locList) + if (!newlist.contains(str)) + newlist.append(str); + idPropertyList.insert(targetId, newlist); + } else { + if (!locList.isEmpty()) + idPropertyList.insert(targetId, locList); + } + } + } + } + + ModelNode transition; + + if (!idPropertyList.isEmpty()) { + executeInTransaction( + " TransitionEditorView::addNewTransition", [&transition, idPropertyList, root, this]() { + transition = createModelNode("QtQuick.Transition", + 2, + 0, + {{ + "from", + "*", + }, + { + "to", + "*", + }}); + transition.setAuxiliaryData("transitionDuration", 2000); + transition.validId(); + root.nodeListProperty("transitions").reparentHere(transition); + + for (const QString &id : idPropertyList.keys()) { + ModelNode parallelAnimation = createModelNode("QtQuick.ParallelAnimation", + 2, + 12); + transition.defaultNodeAbstractProperty().reparentHere(parallelAnimation); + for (const QString &property : idPropertyList.value(id)) { + ModelNode sequentialAnimation + = createModelNode("QtQuick.SequentialAnimation", 2, 12); + parallelAnimation.defaultNodeAbstractProperty().reparentHere( + sequentialAnimation); + + ModelNode pauseAnimation = createModelNode("QtQuick.PauseAnimation", + 2, + 12, + {{"duration", 50}}); + sequentialAnimation.defaultNodeAbstractProperty().reparentHere( + pauseAnimation); + + ModelNode propertyAnimation = createModelNode("QtQuick.PropertyAnimation", + 2, + 12, + {{"property", property}, + {"duration", 150}}); + propertyAnimation.bindingProperty("target").setExpression(id); + sequentialAnimation.defaultNodeAbstractProperty().reparentHere( + propertyAnimation); + } + } + }); + } + + if (m_transitionEditorWidget) + m_transitionEditorWidget->init(); + + return transition; +} + +TransitionEditorWidget *TransitionEditorView::createWidget() +{ + if (!m_transitionEditorWidget) + m_transitionEditorWidget = new TransitionEditorWidget(this); + + //auto *timelineContext = new TimelineContext(m_timelineWidget); + //Core::ICore::addContextObject(timelineContext); + + return m_transitionEditorWidget; +} + +WidgetInfo TransitionEditorView::widgetInfo() +{ + return createWidgetInfo(createWidget(), + nullptr, + "TransitionEditor", + WidgetInfo::BottomPane, + 0, + tr("Transition Editor")); +} + +void TransitionEditorView::openSettingsDialog() +{ + auto dialog = new TransitionEditorSettingsDialog(Core::ICore::dialogParent(), this); + + auto transition = widget()->graphicsScene()->transitionModelNode(); + if (transition.isValid()) + dialog->setCurrentTransition(transition); + + QObject::connect(dialog, &TransitionEditorSettingsDialog::rejected, [this, dialog]() { + widget()->init(); + dialog->deleteLater(); + }); + + QObject::connect(dialog, &TransitionEditorSettingsDialog::accepted, [this, dialog]() { + widget()->init(); + dialog->deleteLater(); + }); + + dialog->show(); +} + +const QList<ModelNode> TransitionEditorView::allTransitions() const +{ + if (rootModelNode().isValid() && rootModelNode().hasProperty("transitions")) { + NodeAbstractProperty transitions = rootModelNode().nodeAbstractProperty("transitions"); + if (transitions.isValid()) + return transitions.directSubNodes(); + } + return {}; +} + +void TransitionEditorView::asyncUpdate(const ModelNode &transition) +{ + static bool updateTriggered = false; + + if (!updateTriggered && (transition.id() == widget()->toolBar()->currentTransitionId())) { + updateTriggered = true; + QTimer::singleShot(0, [this, transition]() { + widget()->updateData(transition); + updateTriggered = false; + }); + } +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h new file mode 100644 index 0000000000..faa383c621 --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "animationcurvedialog.h" +#include "animationcurveeditormodel.h" +#include "treeitem.h" + +#include <abstractview.h> + +#include <QPointer> + +namespace QmlDesigner { + +class TransitionEditorWidget; + +class TransitionEditorView : public AbstractView +{ + Q_OBJECT + +public: + explicit TransitionEditorView(QObject *parent = nullptr); + ~TransitionEditorView() override; + //Abstract View + WidgetInfo widgetInfo() override; + void modelAttached(Model *model) override; + void modelAboutToBeDetached(Model *model) override; + void nodeCreated(const ModelNode &createdNode) override; + void nodeAboutToBeRemoved(const ModelNode &removedNode) override; + void nodeRemoved(const ModelNode &removedNode, + const NodeAbstractProperty &parentProperty, + PropertyChangeFlags propertyChange) override; + void nodeReparented(const ModelNode &node, + const NodeAbstractProperty &newPropertyParent, + const NodeAbstractProperty &oldPropertyParent, + PropertyChangeFlags propertyChange) override; + void instancePropertyChanged(const QList<QPair<ModelNode, PropertyName>> &propertyList) override; + void variantPropertiesChanged(const QList<VariantProperty> &propertyList, + PropertyChangeFlags propertyChange) override; + void bindingPropertiesChanged(const QList<BindingProperty> &propertyList, + PropertyChangeFlags propertyChange) override; + void selectedNodesChanged(const QList<ModelNode> &selectedNodeList, + const QList<ModelNode> &lastSelectedNodeList) override; + + void propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList) override; + void propertiesRemoved(const QList<AbstractProperty> &propertyList) override; + + bool hasWidget() const override; + + void nodeIdChanged(const ModelNode &node, const QString &, const QString &) override; + + void currentStateChanged(const ModelNode &node) override; + + TransitionEditorWidget *widget() const; + + void insertKeyframe(const ModelNode &target, const PropertyName &propertyName); + + void registerActions(); + + ModelNode addNewTransition(); + + void openSettingsDialog(); + + const QList<ModelNode> allTransitions() const; + + void asyncUpdate(const ModelNode &transition); + +private: + TransitionEditorWidget *createWidget(); + + TransitionEditorWidget *m_transitionEditorWidget = nullptr; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp new file mode 100644 index 0000000000..5d27b4c969 --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp @@ -0,0 +1,414 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "transitioneditorwidget.h" + +#include "transitioneditorgraphicsscene.h" +#include "transitioneditorpropertyitem.h" +#include "transitioneditortoolbar.h" +#include "transitioneditorview.h" + +#include <timelineeditor/easingcurvedialog.h> +#include <timelineeditor/timelineconstants.h> +#include <timelineeditor/timelineicons.h> + +#include <bindingproperty.h> +#include <nodeabstractproperty.h> +#include <nodemetainfo.h> + +#include <qmldesignerplugin.h> +#include <qmlstate.h> +#include <qmltimeline.h> + +#include <coreplugin/icore.h> + +#include <theme.h> +#include <utils/algorithm.h> +#include <utils/fileutils.h> + +#include <QApplication> +#include <QComboBox> +#include <QGraphicsView> +#include <QHBoxLayout> +#include <QLabel> +#include <QMargins> +#include <QPushButton> +#include <QResizeEvent> +#include <QScrollBar> +#include <QShowEvent> +#include <QSlider> +#include <QSpacerItem> +#include <QVBoxLayout> +#include <QtGlobal> + +namespace QmlDesigner { + +class Eventfilter : public QObject +{ +public: + Eventfilter(QObject *parent) + : QObject(parent) + {} + + bool eventFilter(QObject *, QEvent *event) override + { + if (event->type() == QEvent::Wheel) { + event->accept(); + return true; + } + return false; + } +}; + +TransitionEditorWidget::TransitionEditorWidget(TransitionEditorView *view) + : QWidget() + , m_toolbar(new TransitionEditorToolBar(this)) + , m_rulerView(new QGraphicsView(this)) + , m_graphicsView(new QGraphicsView(this)) + , m_scrollbar(new QScrollBar(this)) + , m_statusBar(new QLabel(this)) + , m_transitionEditorView(view) + , m_graphicsScene(new TransitionEditorGraphicsScene(this)) + , m_addButton(new QPushButton(this)) + , m_onboardingContainer(new QWidget(this)) +{ + setWindowTitle(tr("Transition", "Title of transition view")); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + const QString css = Theme::replaceCssColors(QString::fromUtf8( + Utils::FileReader::fetchQrc(QLatin1String(":/qmldesigner/scrollbar.css")))); + + m_scrollbar->setStyleSheet(css); + m_scrollbar->setOrientation(Qt::Horizontal); + + QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Preferred); + sizePolicy1.setHorizontalStretch(0); + sizePolicy1.setVerticalStretch(0); + sizePolicy1.setHeightForWidth(m_graphicsView->sizePolicy().hasHeightForWidth()); + + m_rulerView->setObjectName("RulerView"); + m_rulerView->setFixedHeight(TimelineConstants::rulerHeight); + m_rulerView->setAlignment(Qt::AlignLeft | Qt::AlignTop); + m_rulerView->viewport()->installEventFilter(new Eventfilter(this)); + m_rulerView->viewport()->setFocusPolicy(Qt::NoFocus); + m_rulerView->setStyleSheet(css); + m_rulerView->setFrameShape(QFrame::NoFrame); + m_rulerView->setFrameShadow(QFrame::Plain); + m_rulerView->setLineWidth(0); + m_rulerView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_rulerView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_rulerView->setScene(graphicsScene()); + + m_graphicsView->setStyleSheet(css); + m_graphicsView->setObjectName("SceneView"); + m_graphicsView->setFrameShape(QFrame::NoFrame); + m_graphicsView->setFrameShadow(QFrame::Plain); + m_graphicsView->setLineWidth(0); + m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + m_graphicsView->setSizePolicy(sizePolicy1); + m_graphicsView->setScene(graphicsScene()); + m_graphicsView->setAlignment(Qt::AlignLeft | Qt::AlignTop); + m_graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); + + auto *scrollBarLayout = new QHBoxLayout; + scrollBarLayout->addSpacing(TimelineConstants::sectionWidth); + scrollBarLayout->addWidget(m_scrollbar); + + QMargins margins(0, 0, 0, QApplication::style()->pixelMetric(QStyle::PM_LayoutBottomMargin)); + + auto *contentLayout = new QVBoxLayout; + contentLayout->setContentsMargins(margins); + contentLayout->addWidget(m_rulerView); + contentLayout->addWidget(m_graphicsView); + contentLayout->addLayout(scrollBarLayout); + contentLayout->addWidget(m_statusBar); + m_statusBar->setIndent(2); + m_statusBar->setFixedHeight(TimelineConstants::rulerHeight); + + auto *widgetLayout = new QVBoxLayout; + widgetLayout->setContentsMargins(0, 0, 0, 0); + widgetLayout->setSpacing(0); + widgetLayout->addWidget(m_toolbar); + widgetLayout->addWidget(m_addButton); + + m_addButton->setIcon(TimelineIcons::ADD_TIMELINE_TOOLBAR.icon()); + m_addButton->setToolTip(tr("Add Transition")); + m_addButton->setFlat(true); + m_addButton->setFixedSize(32, 32); + + widgetLayout->addWidget(m_onboardingContainer); + + auto *onboardingTopLabel = new QLabel(m_onboardingContainer); + auto *onboardingBottomLabel = new QLabel(m_onboardingContainer); + auto *onboardingBottomIcon = new QLabel(m_onboardingContainer); + + auto *onboardingLayout = new QVBoxLayout; + auto *onboardingSublayout = new QHBoxLayout; + auto *leftSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + auto *rightSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + auto *topSpacer = new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); + auto *bottomSpacer = new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); + + QString labelText = tr("This file does not contain transitions. <br><br> \ + To create an animation, add a transition by clicking the + button."); + onboardingTopLabel->setText(labelText); + onboardingTopLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + + m_onboardingContainer->setLayout(onboardingLayout); + onboardingLayout->setContentsMargins(0, 0, 0, 0); + onboardingLayout->setSpacing(0); + onboardingLayout->addSpacerItem(topSpacer); + onboardingLayout->addWidget(onboardingTopLabel); + onboardingLayout->addLayout(onboardingSublayout); + + onboardingSublayout->setContentsMargins(0, 0, 0, 0); + onboardingSublayout->setSpacing(0); + onboardingSublayout->addSpacerItem(leftSpacer); + + onboardingBottomLabel->setAlignment(Qt::AlignRight | Qt::AlignTop); + onboardingBottomLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + onboardingSublayout->addWidget(onboardingBottomLabel); + onboardingBottomLabel->setText(tr("To edit the transition settings, click ")); + + onboardingBottomIcon->setAlignment(Qt::AlignLeft | Qt::AlignTop); + onboardingBottomIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + onboardingSublayout->addWidget(onboardingBottomIcon); + onboardingBottomIcon->setPixmap(TimelineIcons::ANIMATION.pixmap()); + + onboardingSublayout->addSpacerItem(rightSpacer); + onboardingLayout->addSpacerItem(bottomSpacer); + + widgetLayout->addLayout(contentLayout); + this->setLayout(widgetLayout); + + connectToolbar(); + + auto setScrollOffset = [this]() { graphicsScene()->setScrollOffset(m_scrollbar->value()); }; + connect(m_scrollbar, &QSlider::valueChanged, this, setScrollOffset); + + connect(graphicsScene(), + &TransitionEditorGraphicsScene::statusBarMessageChanged, + this, + [this](const QString &message) { m_statusBar->setText(message); }); + + connect(m_addButton, &QPushButton::clicked, this, [this]() { + m_transitionEditorView->addNewTransition(); + }); +} + +void TransitionEditorWidget::setTransitionActive(bool b) +{ + if (b) { + m_toolbar->setVisible(true); + m_graphicsView->setVisible(true); + m_rulerView->setVisible(true); + m_scrollbar->setVisible(true); + m_addButton->setVisible(false); + m_onboardingContainer->setVisible(false); + m_graphicsView->update(); + m_rulerView->update(); + } else { + m_toolbar->setVisible(false); + m_graphicsView->setVisible(false); + m_rulerView->setVisible(false); + m_scrollbar->setVisible(false); + m_addButton->setVisible(true); + m_onboardingContainer->setVisible(true); + } +} + +void TransitionEditorWidget::connectToolbar() +{ + connect(graphicsScene(), + &TransitionEditorGraphicsScene::selectionChanged, + this, + &TransitionEditorWidget::selectionChanged); + + connect(m_toolbar, + &TransitionEditorToolBar::openEasingCurveEditor, + this, + &TransitionEditorWidget::openEasingCurveEditor); + + connect(graphicsScene(), + &TransitionEditorGraphicsScene::scroll, + this, + &TransitionEditorWidget::scroll); + + auto setRulerScaling = [this](int val) { m_graphicsScene->setRulerScaling(val); }; + connect(m_toolbar, &TransitionEditorToolBar::scaleFactorChanged, setRulerScaling); + + auto setDuration = [this](int end) { graphicsScene()->setDuration(end); }; + connect(m_toolbar, &TransitionEditorToolBar::durationChanged, setDuration); + + connect(m_toolbar, + &TransitionEditorToolBar::settingDialogClicked, + transitionEditorView(), + &TransitionEditorView::openSettingsDialog); + + connect(m_toolbar, + &TransitionEditorToolBar::currentTransitionChanged, + this, + [this](const QString &transitionName) { + const ModelNode transition = transitionEditorView()->modelNodeForId(transitionName); + if (transition.isValid()) { + m_graphicsScene->setTransition(transition); + } + }); +} + +void TransitionEditorWidget::changeScaleFactor(int factor) +{ + m_toolbar->setScaleFactor(factor); +} + +void TransitionEditorWidget::scroll(const TimelineUtils::Side &side) +{ + if (side == TimelineUtils::Side::Left) + m_scrollbar->setValue(m_scrollbar->value() - m_scrollbar->singleStep()); + else if (side == TimelineUtils::Side::Right) + m_scrollbar->setValue(m_scrollbar->value() + m_scrollbar->singleStep()); +} + +void TransitionEditorWidget::selectionChanged() +{ + if (graphicsScene()->selectedPropertyItem() != nullptr) + m_toolbar->setActionEnabled("Curve Picker", true); + else + m_toolbar->setActionEnabled("Curve Picker", false); +} + +void TransitionEditorWidget::contextHelp(const Core::IContext::HelpCallback &callback) const +{ + if (transitionEditorView()) + transitionEditorView()->contextHelp(callback); + else + callback({}); +} + +void TransitionEditorWidget::init() +{ + ModelNode root = transitionEditorView()->rootModelNode(); + ModelNode transition; + + if (root.isValid() && root.hasProperty("transitions")) { + NodeAbstractProperty transitions = root.nodeAbstractProperty("transitions"); + if (transitions.isValid()) + transition = transitions.directSubNodes().first(); + } + + m_graphicsScene->setTransition(transition); + setTransitionActive(transition.isValid()); + + m_graphicsScene->setWidth(m_graphicsView->viewport()->width()); + + m_toolbar->setScaleFactor(0); + + m_toolbar->setCurrentTransition(transition); + + qreal duration = 2000; + if (transition.isValid() && transition.hasAuxiliaryData("transitionDuration")) + duration = transition.auxiliaryData("transitionDuration").toDouble(); + + m_toolbar->setDuration(duration); + + m_graphicsScene->setRulerScaling(0); +} + +void TransitionEditorWidget::updateData(const ModelNode &transition) +{ + if (!transition.isValid()) { + init(); + return; + } + + if (transition.metaInfo().isValid() + && transition.metaInfo().isSubclassOf("QtQuick.Transition")) { + if (transition.id() == m_toolbar->currentTransitionId()) { + m_graphicsScene->setTransition(transition); + } else { + m_toolbar->updateComboBox(transition.view()->rootModelNode()); + } + } +} + +void TransitionEditorWidget::reset() +{ + graphicsScene()->clearTransition(); + m_toolbar->reset(); + m_statusBar->clear(); +} + +TransitionEditorGraphicsScene *TransitionEditorWidget::graphicsScene() const +{ + return m_graphicsScene; +} + +TransitionEditorToolBar *TransitionEditorWidget::toolBar() const +{ + return m_toolbar; +} + +void TransitionEditorWidget::setupScrollbar(int min, int max, int current) +{ + bool b = m_scrollbar->blockSignals(true); + m_scrollbar->setMinimum(min); + m_scrollbar->setMaximum(max); + m_scrollbar->setValue(current); + m_scrollbar->setSingleStep((max - min) / 10); + m_scrollbar->blockSignals(b); +} + +void TransitionEditorWidget::showEvent(QShowEvent *event) +{ + Q_UNUSED(event) + graphicsScene()->setWidth(m_graphicsView->viewport()->width()); + graphicsScene()->invalidateLayout(); + graphicsScene()->invalidate(); + graphicsScene()->onShow(); +} + +void TransitionEditorWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + graphicsScene()->setWidth(m_graphicsView->viewport()->width()); +} + +TransitionEditorView *TransitionEditorWidget::transitionEditorView() const +{ + return m_transitionEditorView; +} + +void TransitionEditorWidget::openEasingCurveEditor() +{ + if (TransitionEditorPropertyItem *item = graphicsScene()->selectedPropertyItem()) { + QList<ModelNode> animations; + animations.append(item->propertyAnimation()); + EasingCurveDialog::runDialog(animations, Core::ICore::dialogParent()); + } +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h new file mode 100644 index 0000000000..97f57f1c2f --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelineeditor/timelineutils.h" + +#include <coreplugin/icontext.h> + +#include <QWidget> + +#include <functional> + +QT_FORWARD_DECLARE_CLASS(QComboBox) +QT_FORWARD_DECLARE_CLASS(QGraphicsView) +QT_FORWARD_DECLARE_CLASS(QLabel) +QT_FORWARD_DECLARE_CLASS(QResizeEvent) +QT_FORWARD_DECLARE_CLASS(QScrollBar) +QT_FORWARD_DECLARE_CLASS(QShowEvent) +QT_FORWARD_DECLARE_CLASS(QString) +QT_FORWARD_DECLARE_CLASS(QPushButton) + +namespace QmlDesigner { + +class TransitionEditorView; +class TransitionEditorToolBar; +class TransitionEditorGraphicsScene; +class ModelNode; + +class TransitionEditorWidget : public QWidget +{ + Q_OBJECT + +public: + explicit TransitionEditorWidget(TransitionEditorView *view); + void contextHelp(const Core::IContext::HelpCallback &callback) const; + + TransitionEditorGraphicsScene *graphicsScene() const; + TransitionEditorView *transitionEditorView() const; + TransitionEditorToolBar *toolBar() const; + + void init(); + void reset(); + + void setupScrollbar(int min, int max, int current); + void changeScaleFactor(int factor); + + void updateData(const ModelNode &transition); +public slots: + void selectionChanged(); + void scroll(const TimelineUtils::Side &side); + +protected: + void showEvent(QShowEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + +private: + void connectToolbar(); + void setTransitionActive(bool b); + void openEasingCurveEditor(); + + TransitionEditorToolBar *m_toolbar = nullptr; + + QGraphicsView *m_rulerView = nullptr; + + QGraphicsView *m_graphicsView = nullptr; + + QScrollBar *m_scrollbar = nullptr; + + QLabel *m_statusBar = nullptr; + + TransitionEditorView *m_transitionEditorView = nullptr; + + TransitionEditorGraphicsScene *m_graphicsScene; + + QPushButton *m_addButton = nullptr; + + QWidget *m_onboardingContainer = nullptr; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp new file mode 100644 index 0000000000..239817197b --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "transitionform.h" +#include "timelineform.h" +#include "ui_transitionform.h" + +#include <abstractview.h> +#include <bindingproperty.h> +#include <exception> +#include <nodelistproperty.h> +#include <nodemetainfo.h> +#include <rewritertransaction.h> +#include <variantproperty.h> +#include <qmlitemnode.h> + +#include <coreplugin/messagebox.h> + +#include <utils/algorithm.h> +#include <utils/qtcassert.h> + +namespace QmlDesigner { + +TransitionForm::TransitionForm(QWidget *parent) + : QWidget(parent) + , ui(new Ui::TransitionForm) +{ + ui->setupUi(this); + + connect(ui->idLineEdit, &QLineEdit::editingFinished, [this]() { + QTC_ASSERT(m_transition.isValid(), return ); + + static QString lastString; + + const QString newId = ui->idLineEdit->text(); + + if (newId == lastString) + return; + + lastString = newId; + + if (newId == m_transition.id()) + return; + + bool error = false; + + if (!ModelNode::isValidId(newId)) { + Core::AsynchronousMessageBox::warning(tr("Invalid Id"), + tr("%1 is an invalid id.").arg(newId)); + error = true; + } else if (m_transition.view()->hasId(newId)) { + Core::AsynchronousMessageBox::warning(tr("Invalid Id"), + tr("%1 already exists.").arg(newId)); + error = true; + } else { + m_transition.setIdWithRefactoring(newId); + } + + if (error) { + lastString.clear(); + ui->idLineEdit->setText(m_transition.id()); + } + }); + + connect(ui->listWidgetTo, &QListWidget::itemChanged, this, [this]() { + QTC_ASSERT(m_transition.isValid(), return ); + const QmlItemNode root(m_transition.view()->rootModelNode()); + QTC_ASSERT(root.isValid(), return ); + const int stateCount = root.states().names().count(); + + QStringList stateNames; + + for (const QListWidgetItem *item : ui->listWidgetTo->findItems("*", Qt::MatchWildcard)) { + if (item->checkState() == Qt::Checked) + stateNames.append(item->text()); + } + + QString toValue; + if (stateCount == stateNames.count()) + toValue = "*"; + else + toValue = stateNames.join(","); + + m_transition.view()->executeInTransaction("TransitionForm::Set To", [this, toValue]() { + m_transition.variantProperty("to").setValue(toValue); + }); + }); + + connect(ui->listWidgetFrom, &QListWidget::itemChanged, this, [this]() { + QTC_ASSERT(m_transition.isValid(), return ); + const QmlItemNode root(m_transition.view()->rootModelNode()); + QTC_ASSERT(root.isValid(), return ); + const int stateCount = root.states().names().count(); + + QStringList stateNames; + + for (const QListWidgetItem *item : ui->listWidgetFrom->findItems("*", Qt::MatchWildcard)) { + if (item->checkState() == Qt::Checked) + stateNames.append(item->text()); + } + + QString fromValue; + if (stateCount == stateNames.count()) + fromValue = "*"; + else + fromValue = stateNames.join(","); + + m_transition.view()->executeInTransaction("TransitionForm::Set To", [this, fromValue]() { + m_transition.variantProperty("from").setValue(fromValue); + }); + }); +} + +TransitionForm::~TransitionForm() +{ + delete ui; +} + +void TransitionForm::setTransition(const ModelNode &transition) +{ + m_transition = transition; + + if (m_transition.isValid()) { + ui->idLineEdit->setText(m_transition.displayName()); + } + setupStatesLists(); +} + +ModelNode TransitionForm::transition() const +{ + return m_transition; +} + +void TransitionForm::setupStatesLists() +{ + bool bTo = ui->listWidgetTo->blockSignals(true); + bool bFrom = ui->listWidgetFrom->blockSignals(true); + QAbstractItemModel *modelTo = ui->listWidgetTo->model(); + modelTo->removeRows(0, modelTo->rowCount()); + + QAbstractItemModel *modelFrom = ui->listWidgetFrom->model(); + modelFrom->removeRows(0, modelFrom->rowCount()); + + bool starFrom = true; + bool starTo = true; + + QStringList fromList; + QStringList toList; + + if (m_transition.hasVariantProperty("from") + && m_transition.variantProperty("from").value().toString().trimmed() != "*") { + starFrom = false; + fromList = m_transition.variantProperty("from").value().toString().split(","); + } + + if (m_transition.hasVariantProperty("to") + && m_transition.variantProperty("to").value().toString().trimmed() != "*") { + starTo = false; + toList = m_transition.variantProperty("to").value().toString().split(","); + } + + if (m_transition.isValid()) { + const QmlItemNode root(m_transition.view()->rootModelNode()); + if (root.isValid()) { + const QmlModelStateGroup states = root.states(); + for (const QString &stateName : states.names()) { + auto itemTo = new QListWidgetItem(stateName, ui->listWidgetTo); + ui->listWidgetTo->addItem(itemTo); + itemTo->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); + if (starTo || toList.contains(stateName)) + itemTo->setCheckState(Qt::Checked); + else + itemTo->setCheckState(Qt::Unchecked); + + auto itemFrom = new QListWidgetItem(stateName, ui->listWidgetFrom); + ui->listWidgetFrom->addItem(itemFrom); + itemFrom->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); + if (starFrom || fromList.contains(stateName)) + itemFrom->setCheckState(Qt::Checked); + else + itemFrom->setCheckState(Qt::Unchecked); + } + } + } + ui->listWidgetTo->blockSignals(bTo); + ui->listWidgetFrom->blockSignals(bFrom); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitionform.h b/src/plugins/qmldesigner/components/transitioneditor/transitionform.h new file mode 100644 index 0000000000..9999557769 --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitionform.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <qmltimeline.h> + +#include <QWidget> + +QT_FORWARD_DECLARE_CLASS(QSpinBox) + +namespace QmlDesigner { + +namespace Ui { +class TransitionForm; +} + +class TransitionForm : public QWidget +{ + Q_OBJECT + +public: + explicit TransitionForm(QWidget *parent); + ~TransitionForm() override; + void setTransition(const ModelNode &transition); + ModelNode transition() const; + +private: + void setupStatesLists(); + + Ui::TransitionForm *ui; + ModelNode m_transition; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitionform.ui b/src/plugins/qmldesigner/components/transitioneditor/transitionform.ui new file mode 100644 index 0000000000..560c2065af --- /dev/null +++ b/src/plugins/qmldesigner/components/transitioneditor/transitionform.ui @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QmlDesigner::TransitionForm</class> + <widget class="QWidget" name="QmlDesigner::TransitionForm"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>641</width> + <height>170</height> + </rect> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="minimumSize"> + <size> + <width>160</width> + <height>0</height> + </size> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Timeline Settings</string> + </property> + </widget> + </item> + <item row="3" column="1" colspan="2"> + <widget class="QListWidget" name="listWidgetTo"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Transition ID:</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="3" colspan="2"> + <spacer name="horizontalSpacer_11"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>49</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="3" column="0"> + <widget class="QListWidget" name="listWidgetFrom"/> + </item> + <item row="1" column="1" colspan="2"> + <widget class="QLineEdit" name="idLineEdit"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>From</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>To</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/qmldesigner/qmldesignerplugin.cpp b/src/plugins/qmldesigner/qmldesignerplugin.cpp index e7d3e0a23d..f6fc86dacd 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.cpp +++ b/src/plugins/qmldesigner/qmldesignerplugin.cpp @@ -41,6 +41,7 @@ #include <formeditor/transitiontool.h> #include <texttool/texttool.h> #include <timelineeditor/timelineview.h> +#include <transitioneditor/transitioneditorview.h> #include <pathtool/pathtool.h> #include <qmljseditor/qmljseditor.h> @@ -243,6 +244,10 @@ bool QmlDesignerPlugin::delayedInitialize() timelineView->registerActions(); } + auto transitionEditorView = new QmlDesigner::TransitionEditorView; + d->viewManager.registerViewTakingOwnership(transitionEditorView); + transitionEditorView->registerActions(); + d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::SourceTool); d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::ColorTool); d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::AnnotationTool); diff --git a/src/plugins/qmldesigner/qmldesignerplugin.pro b/src/plugins/qmldesigner/qmldesignerplugin.pro index 0095aa8f10..bc0da99050 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.pro +++ b/src/plugins/qmldesigner/qmldesignerplugin.pro @@ -32,6 +32,7 @@ include(components/curveeditor/curveeditor.pri) include(components/bindingeditor/bindingeditor.pri) include(components/annotationeditor/annotationeditor.pri) include(components/richtexteditor/richtexteditor.pri) +include(components/transitioneditor/transitioneditor.pri) BUILD_PUPPET_IN_CREATOR_BINPATH = $$(BUILD_PUPPET_IN_CREATOR_BINPATH) diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index c3db90d2e0..b1129a9bf3 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -843,6 +843,26 @@ Project { "timelineeditor/timelineview.h", "timelineeditor/timelinewidget.cpp", "timelineeditor/timelinewidget.h", + "transitioneditor/transitioneditorview.cpp", + "transitioneditor/transitioneditorview.h", + "transitioneditor/transitioneditorwidget.cpp", + "transitioneditor/transitioneditorwidget.h", + "transitioneditor/transitioneditortoolbar.cpp", + "transitioneditor/transitioneditortoolbar.h", + "transitioneditor/transitioneditorgraphicsscene.cpp", + "transitioneditor/transitioneditorgraphicsscene.h", + "transitioneditor/transitioneditorgraphicslayout.cpp", + "transitioneditor/transitioneditorgraphicslayout.h", + "transitioneditor/transitioneditorsectionitem.cpp", + "transitioneditor/transitioneditorsectionitem.h", + "transitioneditor/transitioneditorpropertyitem.cpp", + "transitioneditor/transitioneditorpropertyitem.h", + "transitioneditor/transitioneditorsettingsdialog.cpp", + "transitioneditor/transitioneditorsettingsdialog.h", + "transitioneditor/transitioneditorsettingsdialog.ui" + "transitioneditor/transitionform.cpp", + "transitioneditor/transitionform.h", + "transitioneditor/transitioneditor.qrc" ] } |