From 57ba4e600551b13ac98b19ba08c0a6e6223ec6c6 Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Oct 18 2022 07:46:40 +0000 Subject: snap schematic node Ctrl+drag to snap to neighbor node Shift+drag to move horizontally / vertically --- diff --git a/toonz/sources/include/toonzqt/fxschematicnode.h b/toonz/sources/include/toonzqt/fxschematicnode.h index aec312a..571d2e6 100644 --- a/toonz/sources/include/toonzqt/fxschematicnode.h +++ b/toonz/sources/include/toonzqt/fxschematicnode.h @@ -346,6 +346,7 @@ public: void toggleNormalIconView() { m_isNormalIconView = !m_isNormalIconView; } bool isNormalIconView() { return m_isNormalIconView; } + signals: void switchCurrentFx(TFx *fx); diff --git a/toonz/sources/include/toonzqt/fxschematicscene.h b/toonz/sources/include/toonzqt/fxschematicscene.h index 346388e..e8c2443 100644 --- a/toonz/sources/include/toonzqt/fxschematicscene.h +++ b/toonz/sources/include/toonzqt/fxschematicscene.h @@ -190,6 +190,8 @@ private: bool isAnEmptyZone_withParentFx(const QRectF &rect, const TFx *parent); + // snap to neighbor nodes on dragging + void updateSnapTarget(QGraphicsItem *item) override; signals: void showPreview(TFxP); void cacheFx(TFxP); diff --git a/toonz/sources/include/toonzqt/schematicnode.h b/toonz/sources/include/toonzqt/schematicnode.h index e456440..465e139 100644 --- a/toonz/sources/include/toonzqt/schematicnode.h +++ b/toonz/sources/include/toonzqt/schematicnode.h @@ -35,7 +35,7 @@ public: bool eventFilter(QObject *object, QEvent *event) override; - void setName(const QString &name); // Act as default name + void setName(const QString &name); // Act as default name void acceptName(const QString &name); protected: @@ -471,4 +471,23 @@ signals: void nodeChangedSize(); }; +//======================================================== +// +// class SnapTargetItem +// +//======================================================== + +class SnapTargetItem : public QGraphicsItem { + QRectF m_rect; + QPointF m_theOtherEndPos, m_portEndOffset; + +public: + SnapTargetItem(const QPointF &pos, const QRectF &rect, + const QPointF &theOtherEndPos, const QPointF &portEndOffset); + + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget = 0) override; +}; + #endif // SCHEMATICNODE_H diff --git a/toonz/sources/include/toonzqt/schematicviewer.h b/toonz/sources/include/toonzqt/schematicviewer.h index 896abfa..811f45d 100644 --- a/toonz/sources/include/toonzqt/schematicviewer.h +++ b/toonz/sources/include/toonzqt/schematicviewer.h @@ -57,6 +57,7 @@ class QTouchEvent; class QGestureEvent; class FxSelection; class StageObjectSelection; +class SnapTargetItem; //==================================================== namespace { @@ -72,6 +73,8 @@ enum CursorMode { Select, Zoom, Hand }; class DVAPI SchematicScene : public QGraphicsScene { Q_OBJECT + QPointF m_mousePos, m_clickedPos; + public: SchematicScene(QWidget *parent); ~SchematicScene(); @@ -82,9 +85,20 @@ public: virtual void reorderScene() = 0; virtual void updateScene() = 0; + QPointF mousePos() { return m_mousePos; } + void setMousePos(QPointF pos) { m_mousePos = pos; } + virtual void updateSnapTarget(QGraphicsItem *item){}; + QPointF clickedPos() { return m_clickedPos; } + void setClickedPos(QPointF pos) { m_clickedPos = pos; } + void computeSnap(SchematicNode *node, QPointF &delta, bool enable); + protected: QList m_highlightedLinks; enum GridDimension { eLarge, eSmall }; + QList m_snapTargets; + + static int snapVInterval; + static int snapHSpacing; protected: //! Returns \b true if no nodes intersects \b rect. @@ -97,6 +111,9 @@ protected: void showEvent(QShowEvent *se); void hideEvent(QHideEvent *se); + void addSnapTarget(const QPointF &pos, const QRectF &rect, + const QPointF &theOtherEndPos, const QPointF &endPos); + void clearSnapTargets(); protected slots: virtual void onSelectionSwitched(TSelection *, TSelection *) {} diff --git a/toonz/sources/include/toonzqt/stageschematicscene.h b/toonz/sources/include/toonzqt/stageschematicscene.h index cf67805..1f4a4bc 100644 --- a/toonz/sources/include/toonzqt/stageschematicscene.h +++ b/toonz/sources/include/toonzqt/stageschematicscene.h @@ -186,6 +186,9 @@ private: void updateSplinePositionOnResize(TStageObjectSpline *spl, bool maximizedNode); + // snap to neighbor nodes on dragging + void updateSnapTarget(QGraphicsItem *item) override; + protected: void contextMenuEvent(QGraphicsSceneContextMenuEvent *cme) override; void mousePressEvent(QGraphicsSceneMouseEvent *me) override; diff --git a/toonz/sources/toonzqt/fxschematicnode.cpp b/toonz/sources/toonzqt/fxschematicnode.cpp index 0037e16..49110cd 100644 --- a/toonz/sources/toonzqt/fxschematicnode.cpp +++ b/toonz/sources/toonzqt/fxschematicnode.cpp @@ -1038,7 +1038,7 @@ void FxOutputPainter::paint(QPainter *painter, SchematicViewer *viewer = sceneFx->getSchematicViewer(); QColor outputColor = m_isActive ? viewer->getActiveOutputColor() - : viewer->getOtherOutputColor(); + : viewer->getOtherOutputColor(); painter->setBrush(outputColor); painter->setPen(Qt::NoPen); @@ -1279,7 +1279,7 @@ void FxSchematicPort::paint(QPainter *painter, } break; case eFxLinkPort: // LinkPort - default: { //ここから!!! + default: { QRect sourceRect = scene()->views()[0]->matrix().mapRect(boundingRect()).toRect(); QPixmap linkPm = @@ -3000,8 +3000,8 @@ FxSchematicColumnNode::FxSchematicColumnNode(FxSchematicScene *scene, m_name = QString::fromStdString(name); m_resizeItem = new SchematicThumbnailToggle( - this, fx->getAttributes()->isOpened()); //サムネイル矢印 - m_nameItem = new SchematicName(this, 54, 20); //リネーム部分 + this, fx->getAttributes()->isOpened()); // サムネイル矢印 + m_nameItem = new SchematicName(this, 54, 20); // リネーム部分 m_outDock = new FxSchematicDock(this, "", 0, eFxOutputPort); // Outポート m_renderToggle = new SchematicToggle(this, viewer->getSchematicPreviewButtonOnImage(), @@ -3069,7 +3069,7 @@ FxSchematicColumnNode::FxSchematicColumnNode(FxSchematicScene *scene, bool ret = true; ret = ret && connect(m_resizeItem, SIGNAL(toggled(bool)), this, - SLOT(onChangedSize(bool))); + SLOT(onChangedSize(bool))); ret = ret && connect(m_nameItem, SIGNAL(focusOut()), this, SLOT(onNameChanged())); ret = ret && connect(m_renderToggle, SIGNAL(toggled(bool)), this, diff --git a/toonz/sources/toonzqt/fxschematicscene.cpp b/toonz/sources/toonzqt/fxschematicscene.cpp index 8f820b1..a4b12fd 100644 --- a/toonz/sources/toonzqt/fxschematicscene.cpp +++ b/toonz/sources/toonzqt/fxschematicscene.cpp @@ -2208,4 +2208,56 @@ bool FxSchematicScene::isAnEmptyZone_withParentFx(const QRectF &rect, } } return true; +} + +//------------------------------------------------------------------ +// update snap targets on click node +void FxSchematicScene::updateSnapTarget(QGraphicsItem *item) { + clearSnapTargets(); + FxSchematicNode *node = dynamic_cast(item); + if (!node) return; + + // find input connections + int portCount = node->getInputPortCount(); + for (int i = 0; i < portCount; i++) { + FxSchematicPort *port = node->getInputPort(i); + int j, linkCount = port->getLinkCount(); + for (j = 0; j < linkCount; j++) { + SchematicLink *link = port->getLink(j); + if (!link) continue; + if (m_disconnectionLinks.isABridgeLink(link)) continue; + SchematicNode *otherNode = link->getOtherNode(node); + if (otherNode && !otherNode->isSelected()) { + QPointF targetPos = + otherNode->scenePos() + QPointF(otherNode->boundingRect().width() + + SchematicScene::snapHSpacing, + 0); + + addSnapTarget(targetPos, node->boundingRect(), + link->getOtherPort(port)->getLinkEndPoint(), + port->getLinkEndPoint() - node->scenePos()); + } + } + } + + // find output connections + FxSchematicPort *port = node->getOutputPort(); + if (port) { + int linkCount = port->getLinkCount(); + for (int i = 0; i < linkCount; i++) { + SchematicLink *link = port->getLink(i); + if (!link) continue; + if (m_disconnectionLinks.isABridgeLink(link)) continue; + SchematicNode *otherNode = link->getOtherNode(node); + if (otherNode && !otherNode->isSelected()) { + QPointF targetPos = + otherNode->scenePos() - + QPointF(node->boundingRect().width() + SchematicScene::snapHSpacing, + 0); + addSnapTarget(targetPos, node->boundingRect(), + link->getOtherPort(port)->getLinkEndPoint(), + port->getLinkEndPoint() - node->scenePos()); + } + } + } } \ No newline at end of file diff --git a/toonz/sources/toonzqt/schematicnode.cpp b/toonz/sources/toonzqt/schematicnode.cpp index 73fb596..9a334e9 100644 --- a/toonz/sources/toonzqt/schematicnode.cpp +++ b/toonz/sources/toonzqt/schematicnode.cpp @@ -1124,7 +1124,25 @@ void SchematicNode::paint(QPainter *painter, void SchematicNode::mouseMoveEvent(QGraphicsSceneMouseEvent *me) { QList items = scene()->selectedItems(); if (items.empty()) return; - QPointF delta = me->scenePos() - me->lastScenePos(); + QPointF delta = me->scenePos() - m_scene->mousePos(); + // QPointF delta = me->scenePos() - me->lastScenePos(); + + if (me->modifiers() & Qt::ShiftModifier) { + QPointF deltaFromStart = me->scenePos() - m_scene->clickedPos(); + if (std::abs(deltaFromStart.x()) > std::abs(deltaFromStart.y())) + deltaFromStart.setY(0.0); + else + deltaFromStart.setX(0.0); + delta = m_scene->clickedPos() + deltaFromStart - m_scene->mousePos(); + } + + // snap to neighbor nodes + bool ctrlPressed = me->modifiers() & Qt::ControlModifier; + dynamic_cast(scene())->computeSnap(this, delta, + ctrlPressed); + + m_scene->setMousePos(m_scene->mousePos() + delta); + QGraphicsView *viewer = scene()->views()[0]; for (auto const &item : items) { SchematicNode *node = dynamic_cast(item); @@ -1152,6 +1170,11 @@ void SchematicNode::mousePressEvent(QGraphicsSceneMouseEvent *me) { setSelected(false); } onClicked(); + + m_scene->setMousePos(me->scenePos()); + m_scene->setClickedPos(me->scenePos()); + + m_scene->updateSnapTarget(this); } //-------------------------------------------------------- @@ -1159,6 +1182,8 @@ void SchematicNode::mousePressEvent(QGraphicsSceneMouseEvent *me) { void SchematicNode::mouseReleaseEvent(QGraphicsSceneMouseEvent *me) { if (me->modifiers() != Qt::ControlModifier && me->button() != Qt::RightButton) QGraphicsItem::mouseReleaseEvent(me); + + m_scene->updateSnapTarget(nullptr); } //-------------------------------------------------------- @@ -1222,3 +1247,46 @@ void SchematicNode::updateLinksGeometry() { for (it = m_ports.begin(); it != m_ports.end(); ++it) it.value()->updateLinksGeometry(); } + +//======================================================== +// +// class SnapTargetItem +// +//======================================================== + +SnapTargetItem::SnapTargetItem(const QPointF &pos, const QRectF &rect, + const QPointF &theOtherEndPos, + const QPointF &portEndOffset) + : m_rect(rect) + , m_theOtherEndPos(theOtherEndPos) + , m_portEndOffset(portEndOffset) { + setFlag(QGraphicsItem::ItemIsMovable, false); + setFlag(QGraphicsItem::ItemIsSelectable, false); + setFlag(QGraphicsItem::ItemIsFocusable, false); + setZValue(3.0); + setPos(pos); + setVisible(false); +} + +QRectF SnapTargetItem::boundingRect() const { + QRectF linkRect(m_theOtherEndPos - scenePos(), m_portEndOffset); + return m_rect.united(linkRect); +} + +void SnapTargetItem::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget) { + painter->setPen(QPen(Qt::magenta, 1, Qt::DashDotLine)); + painter->drawRect(m_rect); + + QPointF startPos = m_theOtherEndPos - scenePos(); + QPointF endPos = m_portEndOffset; + QPointF p0((endPos.x() + startPos.x()) * 0.5, startPos.y()); + QPointF p1(p0.x(), endPos.y()); + QPointF p2(endPos); + + QPainterPath path(startPos); + path.cubicTo(p0, p1, p2); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->drawPath(path); +} \ No newline at end of file diff --git a/toonz/sources/toonzqt/schematicviewer.cpp b/toonz/sources/toonzqt/schematicviewer.cpp index cca6755..678388b 100644 --- a/toonz/sources/toonzqt/schematicviewer.cpp +++ b/toonz/sources/toonzqt/schematicviewer.cpp @@ -75,8 +75,12 @@ public: } }; +const int snapDistance = 15; } // namespace +int SchematicScene::snapHSpacing = 50; +int SchematicScene::snapVInterval = 75; + //================================================================== // // SchematicScene @@ -191,6 +195,96 @@ QVector SchematicScene::getPlacedNode(SchematicNode *node) { return nodes; } +//------------------------------------------------------------------ + +void SchematicScene::addSnapTarget(const QPointF &pos, const QRectF &rect, + const QPointF &theOtherEndPos, + const QPointF &endPosOffset) { + // reduce highlight margin + QRectF nodeRect = rect.adjusted(5, 5, -5, -5); + + /* + auto findSnapPos = [&](QPointF pos) { + for (auto item : m_snapTargets) { + if (item->scenePos() == pos) return true; + } + return false; + }; + + QList posList = {pos}; + + for (int i = 1; i <= 10; i++) { + QPointF tmp_pos = + pos + QPointF(0, (double)(SchematicScene::snapVInterval * i)); + posList.append(tmp_pos); + if (isAnEmptyZone(nodeRect.translated(tmp_pos))) break; + } + for (int i = -1; i >= -10; i--) { + QPointF tmp_pos = + pos + QPointF(0, (double)(SchematicScene::snapVInterval * i)); + posList.append(tmp_pos); + if (isAnEmptyZone(nodeRect.translated(tmp_pos))) break; + } + + for (auto p : posList) { + if (findSnapPos(p)) continue; + SnapTargetItem *item = new SnapTargetItem(p, nodeRect); + addItem(item); + m_snapTargets.append(item); + }*/ + + SnapTargetItem *item = + new SnapTargetItem(pos, nodeRect, theOtherEndPos, endPosOffset); + addItem(item); + m_snapTargets.append(item); +} + +//------------------------------------------------------------------ + +void SchematicScene::clearSnapTargets() { + for (auto item : m_snapTargets) { + removeItem(item); + delete item; + } + m_snapTargets.clear(); +} + +//------------------------------------------------------------------ +// snap to neighbor nodes on dragging +void SchematicScene::computeSnap(SchematicNode *node, QPointF &delta, + bool enable) { + if (m_snapTargets.isEmpty()) return; + + if (!enable) { + // hide targets + if (m_snapTargets[0]->isVisible()) { + for (auto item : m_snapTargets) item->setVisible(false); + } + return; + } + + if (!m_snapTargets[0]->isVisible()) { + for (auto item : m_snapTargets) item->setVisible(true); + } + + QPointF newScenePos = node->scenePos() + delta; + QPointF newPos = views()[0]->mapFromScene(newScenePos); + + for (auto target : m_snapTargets) { + QPointF targetPos = target->scenePos(); + int snapIndex = std::nearbyint((newScenePos.y() - targetPos.y()) / + (double)SchematicScene::snapVInterval); + targetPos.setY(targetPos.y() + + (double)(snapIndex * SchematicScene::snapVInterval)); + target->setPos(targetPos); + if ((newPos - views()[0]->mapFromScene(targetPos)).manhattanLength() < + snapDistance) { + delta = targetPos - node->scenePos(); + return; + } + } +} + //================================================================== // // SchematicSceneViewer @@ -250,7 +344,7 @@ void SchematicSceneViewer::mousePressEvent(QMouseEvent *me) { m_mousePanPoint = m_touchDevice == QTouchDevice::TouchScreen ? mapToScene(me->pos()) : me->pos() * getDevicePixelRatio(this); - m_panning = true; + m_panning = true; return; } } else if (m_buttonState == Qt::MidButton) { @@ -289,9 +383,9 @@ void SchematicSceneViewer::mouseMoveEvent(QMouseEvent *me) { QPointF currScenePos = mapToScene(currWinPos); if ((m_cursorMode == CursorMode::Hand && m_panning) || m_buttonState == Qt::MidButton) { - QPointF usePos = m_touchDevice == QTouchDevice::TouchScreen - ? mapToScene(me->pos()) - : me->pos() * getDevicePixelRatio(this); + QPointF usePos = m_touchDevice == QTouchDevice::TouchScreen + ? mapToScene(me->pos()) + : me->pos() * getDevicePixelRatio(this); QPointF deltaPoint = usePos - m_mousePanPoint; panQt(deltaPoint); m_mousePanPoint = m_touchDevice == QTouchDevice::TouchScreen @@ -631,13 +725,13 @@ void SchematicSceneViewer::touchEvent(QTouchEvent *e, int type) { } } if (m_panning) { - QPointF curPos = m_touchDevice == QTouchDevice::TouchScreen - ? mapToScene(panPoint.pos().toPoint()) - : mapToScene(panPoint.pos().toPoint()) * + QPointF curPos = m_touchDevice == QTouchDevice::TouchScreen + ? mapToScene(panPoint.pos().toPoint()) + : mapToScene(panPoint.pos().toPoint()) * getDevicePixelRatio(this); - QPointF lastPos = m_touchDevice == QTouchDevice::TouchScreen - ? mapToScene(panPoint.lastPos().toPoint()) - : mapToScene(panPoint.lastPos().toPoint()) * + QPointF lastPos = m_touchDevice == QTouchDevice::TouchScreen + ? mapToScene(panPoint.lastPos().toPoint()) + : mapToScene(panPoint.lastPos().toPoint()) * getDevicePixelRatio(this); QPointF centerDelta = curPos - lastPos; panQt(centerDelta); diff --git a/toonz/sources/toonzqt/stageschematicscene.cpp b/toonz/sources/toonzqt/stageschematicscene.cpp index 8b9c71a..10ee604 100644 --- a/toonz/sources/toonzqt/stageschematicscene.cpp +++ b/toonz/sources/toonzqt/stageschematicscene.cpp @@ -1268,3 +1268,50 @@ void StageSchematicScene::onNodeChangedSize() { if (resizingNodes) return; updateScene(); } + +//------------------------------------------------------------------ +// snap to neighbor nodes on dragging +void StageSchematicScene::updateSnapTarget(QGraphicsItem *item) { + clearSnapTargets(); + StageSchematicNode *node = dynamic_cast(item); + if (!node) return; + + int portCount = node->getChildCount(); + for (int i = 0; i < portCount; i++) { + StageSchematicNodePort *port = node->getChildPort(i); + int j, linkCount = port->getLinkCount(); + for (j = 0; j < linkCount; j++) { + SchematicLink *link = port->getLink(j); + if (!link) continue; + SchematicNode *otherNode = link->getOtherNode(node); + if (otherNode && !otherNode->isSelected()) { + QPointF targetPos = + otherNode->scenePos() - + QPointF(node->boundingRect().width() + SchematicScene::snapHSpacing, + 0); + addSnapTarget(targetPos, node->boundingRect(), + link->getOtherPort(port)->getLinkEndPoint(), + port->getLinkEndPoint() - node->scenePos()); + } + } + } + + StageSchematicNodePort *port = node->getParentPort(); + if (port) { + int linkCount = port->getLinkCount(); + for (int i = 0; i < linkCount; i++) { + SchematicLink *link = port->getLink(i); + if (!link) continue; + SchematicNode *otherNode = link->getOtherNode(node); + if (otherNode && !otherNode->isSelected()) { + QPointF targetPos = + otherNode->scenePos() + QPointF(otherNode->boundingRect().width() + + SchematicScene::snapHSpacing, + 0); + addSnapTarget(targetPos, node->boundingRect(), + link->getOtherPort(port)->getLinkEndPoint(), + port->getLinkEndPoint() - node->scenePos()); + } + } + } +} \ No newline at end of file