aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp')
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp803
1 files changed, 803 insertions, 0 deletions
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