1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "nodeview.h"
5#include "effectmanager.h"
6
7NodeView::NodeView(QQuickItem *parent)
8 : QQuickItem(parent)
9{
10 setAcceptedMouseButtons(Qt::AllButtons);
11 setAcceptHoverEvents(true);
12 m_nodesModel = new NodesModel();
13 m_arrowsModel = new ArrowsModel();
14
15 // Create main and output nodes
16 // These always exist
17 NodesModel::Node n1{.type: 0, .nodeId: 0, .x: 220, .y: 50, .width: 80, .height: 80, .name: "Main"};
18 NodesModel::Node n2{.type: 1, .nodeId: 1, .x: 220, .y: 400, .width: 80, .height: 80, .name: "Output"};
19 initializeNodeSize(node&: n1);
20 initializeNodeSize(node&: n2);
21 n1.nextNodeId = 1;
22 m_nodesModel->m_nodesList << n1;
23 m_nodesModel->m_nodesList << n2;
24
25 // Connect source & output by default
26 ArrowsModel::Arrow a1{.startX: 0, .startY: 0, .endX: 0, .endY: 0, .startNodeId: 0, .endNodeId: 1};
27 m_arrowsModel->m_arrowsList << a1;
28
29 updateArrowsPositions();
30 m_activeArrow.startNodeId = -1;
31 m_activeArrow.endNodeId = -1;
32
33 connect(sender: m_nodesModel, signal: &QAbstractItemModel::modelReset, slot: [this]() {
34 updateCodeSelectorModel();
35 });
36}
37
38void NodeView::mouseMoveEvent(QMouseEvent *event)
39{
40 // Nodes not moved with right button
41 if (event->buttons() & Qt::RightButton)
42 return;
43
44 m_nodesModel->beginResetModel();
45 QPointF movement = event->position() - m_pressPosition;
46 for (auto &node : m_nodesModel->m_nodesList) {
47 if (node.selected) {
48 float newX = node.startX + movement.x();
49 float newY = node.startY + movement.y();
50 // Don't allow dragging outside the visible nodeview area.
51 newX = std::max(a: newX, b: float(-node.width / 2.0f));
52 newX = std::min(a: newX, b: float(width() - node.width / 2.0f));
53 newY = std::max(a: newY, b: float(-node.height / 2.0f));
54 newY = std::min(a: newY, b: float(height() - node.height / 2.0f));
55 node.x = newX;
56 node.y = newY;
57 }
58 }
59 m_nodesModel->endResetModel();
60
61 updateArrowsPositions();
62}
63
64void NodeView::mousePressEvent(QMouseEvent *event)
65{
66 m_mousePressed = true;
67
68 setFocus(true);
69
70 QPointF p = event->position();
71 m_pressPosition = p;
72 bool shiftPressed = (event->modifiers() & Qt::ShiftModifier);
73
74 m_nodesModel->beginResetModel();
75 m_arrowsModel->beginResetModel();
76
77 // Reset the nodes starting positions
78 for (auto &node : m_nodesModel->m_nodesList) {
79 if (node.selected) {
80 node.startX = node.x;
81 node.startY = node.y;
82 }
83 }
84
85 bool connectorPressed = false;
86 if (!m_activeArrowEnabled) {
87 m_activeArrow.startNodeId = -1;
88 m_activeArrow.endNodeId = -1;
89 }
90 // Check node connectors first
91 for (auto &node : m_nodesModel->m_nodesList) {
92 float size = 20;
93 // Start is bottom side and end top side of the node.
94 QPointF startConnectorCenter = { node.x + node.width / 2, node.y + node.height};
95 QPointF endConnectorCenter = { node.x + node.width / 2, node.y };
96 QRectF startConnector(startConnectorCenter.x() - size / 2, startConnectorCenter.y() - size / 2, size, size);
97 QRectF endConnector(endConnectorCenter.x() - size / 2, endConnectorCenter.y() - size / 2, size, size);
98 if (startConnector.contains(p)) {
99 m_activeArrowStartPoint = startConnectorCenter;
100 emit activeArrowStartPointChanged();
101 m_activeArrowEndPoint = startConnectorCenter;
102 emit activeArrowEndPointChanged();
103 connectorPressed = true;
104 m_activeArrow.startNodeId = node.nodeId;
105 // Remove possible already existing arrow
106 for (auto &arrow : m_arrowsModel->m_arrowsList) {
107 if (arrow.startNodeId == node.nodeId) {
108 // Update nextNode
109 node.nextNodeId = -1;
110 m_arrowsModel->m_arrowsList.removeAll(t: arrow);
111 }
112 }
113 } else if (endConnector.contains(p)) {
114 m_activeArrowStartPoint = endConnectorCenter;
115 emit activeArrowStartPointChanged();
116 m_activeArrowEndPoint = endConnectorCenter;
117 emit activeArrowEndPointChanged();
118 connectorPressed = true;
119 m_activeArrow.endNodeId = node.nodeId;
120 // Remove possible already existing arrow
121 for (auto &arrow : m_arrowsModel->m_arrowsList) {
122 if (arrow.endNodeId == node.nodeId) {
123 // Update nextNode
124 if (auto n = m_nodesModel->getNodeWithId(id: arrow.startNodeId))
125 n->nextNodeId = -1;
126 m_arrowsModel->m_arrowsList.removeAll(t: arrow);
127 }
128 }
129 }
130 // Don't allow connecting same node out & in together
131 if (m_activeArrow.startNodeId != -1 && m_activeArrow.startNodeId == m_activeArrow.endNodeId) {
132 m_activeArrow.startNodeId = -1;
133 m_activeArrow.endNodeId = -1;
134 }
135 }
136
137 // True when pressing a node that was already selected
138 bool selectedNodeUnderMouse = false;
139 NodesModel::Node newlySelectedNode;
140 if (!connectorPressed) {
141 for (auto &node : m_nodesModel->m_nodesList) {
142 node.startX = node.x;
143 node.startY = node.y;
144 QRectF nodeArea(node.x, node.y, node.width, node.height);
145 if (nodeArea.contains(p)) {
146 if (node.selected)
147 selectedNodeUnderMouse = true;
148 if (shiftPressed)
149 node.selected = !node.selected;
150 else
151 node.selected = true;
152 if (node.selected) {
153 newlySelectedNode = node;
154 break;
155 }
156 }
157 }
158 }
159
160 // Deselected previous nodes
161 if (!shiftPressed && !selectedNodeUnderMouse) {
162 for (auto &node : m_nodesModel->m_nodesList) {
163 if (newlySelectedNode != node)
164 node.selected = false;
165 }
166 }
167
168 // Check if both arrow ends are connected
169 if (m_activeArrow.startNodeId >= 0 && m_activeArrow.endNodeId >= 0) {
170 m_arrowsModel->m_arrowsList.append(t: m_activeArrow);
171 connectorPressed = false;
172 updateArrowsPositions();
173 // Update nextNode
174 auto n1 = m_nodesModel->getNodeWithId(id: m_activeArrow.startNodeId);
175 if (n1)
176 n1->nextNodeId = m_activeArrow.endNodeId;
177 }
178
179 if (m_activeArrowEnabled != connectorPressed) {
180 m_activeArrowEnabled = connectorPressed;
181 emit activeArrowEnabledChanged();
182 }
183
184 // Check first selected node
185 NodesModel::Node *selectedNode = nullptr;
186 for (auto &node : m_nodesModel->m_nodesList) {
187 if (node.selected) {
188 selectedNode = &node;
189 break;
190 }
191 }
192 setSelectedNode(selectedNode);
193
194 m_nodesModel->endResetModel();
195 m_arrowsModel->endResetModel();
196
197 // Right button opens the context menu
198 if (m_effectNodeSelected && event->button() == Qt::RightButton)
199 Q_EMIT openNodeContextMenu();
200
201 // Check if active (connected) nodes have changed
202 updateActiveNodesList();
203}
204
205void NodeView::mouseReleaseEvent(QMouseEvent *event)
206{
207 Q_UNUSED(event)
208 m_mousePressed = false;
209}
210
211void NodeView::mouseDoubleClickEvent(QMouseEvent *event)
212{
213 Q_UNUSED(event)
214 // When double clicking custom nodes or main node
215 if (m_selectedNodeId != -1 && m_selectedNodeId != 1)
216 Q_EMIT doubleClickNode();
217}
218
219void NodeView::hoverMoveEvent(QHoverEvent *event)
220{
221 // If start/end is connected, other side follows mouse
222 if (m_activeArrow.startNodeId >= 0) {
223 m_activeArrowEndPoint = event->position();
224 emit activeArrowEndPointChanged();
225 } else if (m_activeArrow.endNodeId >= 0) {
226 m_activeArrowStartPoint = event->position();
227 emit activeArrowStartPointChanged();
228 }
229
230 // Pass event forward, to e.g. keep SplitView mouse cursor
231 // changes functioning correctly.
232 event->setAccepted(false);
233}
234
235void NodeView::keyPressEvent(QKeyEvent *event)
236{
237 Q_UNUSED(event)
238}
239
240void NodeView::keyReleaseEvent(QKeyEvent *event)
241{
242 Q_UNUSED(event)
243}
244
245// Selects the node with \a nodeId and deselects other nodes.
246void NodeView::selectSingleNode(int nodeId)
247{
248 m_nodesModel->beginResetModel();
249 // Check first selected node
250 NodesModel::Node *selectedNode = nullptr;
251 for (auto &node : m_nodesModel->m_nodesList) {
252 if (node.nodeId == nodeId) {
253 node.selected = true;
254 selectedNode = &node;
255 } else {
256 node.selected = false;
257 }
258 }
259 setSelectedNode(selectedNode);
260 m_nodesModel->endResetModel();
261}
262
263// Selects the Main node and deselects other nodes.
264void NodeView::selectMainNode()
265{
266 m_nodesModel->beginResetModel();
267 NodesModel::Node *selectedNode = nullptr;
268 for (auto &node : m_nodesModel->m_nodesList) {
269 if (node.type == 0) {
270 node.selected = true;
271 selectedNode = &node;
272 } else {
273 node.selected = false;
274 }
275 }
276 setSelectedNode(selectedNode);
277 m_nodesModel->endResetModel();
278}
279
280void NodeView::setSelectedNode(NodesModel::Node *selectedNode)
281{
282 if (m_nodesModel->m_selectedNode == selectedNode)
283 return;
284
285 m_nodesModel->setSelectedNode(selectedNode);
286 bool effectNodeSelected = (selectedNode != nullptr && selectedNode->type == 2);
287 if (effectNodeSelected != m_effectNodeSelected) {
288 m_effectNodeSelected = effectNodeSelected;
289 Q_EMIT effectNodeSelectedChanged();
290 }
291
292 bool mainNodeSelected = (selectedNode != nullptr && selectedNode->type == 0);
293 if (mainNodeSelected != m_mainNodeSelected) {
294 m_mainNodeSelected = mainNodeSelected;
295 Q_EMIT mainNodeSelectedChanged();
296 }
297
298 m_selectedNodeId = selectedNode ? selectedNode->nodeId : -1;
299 Q_EMIT selectedNodeIdChanged();
300
301 updateCodeSelectorIndex();
302
303 Q_EMIT selectedNodeFragmentCodeChanged();
304 Q_EMIT selectedNodeVertexCodeChanged();
305 Q_EMIT selectedNodeQmlCodeChanged();
306 Q_EMIT selectedNodeNameChanged();
307 Q_EMIT selectedNodeDescriptionChanged();
308}
309
310NodesModel *NodeView::nodesModel() const
311{
312 return m_nodesModel;
313}
314
315ArrowsModel *NodeView::arrowsModel() const
316{
317 return m_arrowsModel;
318}
319
320QPointF NodeView::activeArrowStartPoint() const
321{
322 return m_activeArrowStartPoint;
323}
324
325QPointF NodeView::activeArrowEndPoint() const
326{
327 return m_activeArrowEndPoint;
328}
329
330bool NodeView::activeArrowEnabled() const
331{
332 return m_activeArrowEnabled;
333}
334
335void NodeView::updateArrowsPositions()
336{
337 m_arrowsModel->beginResetModel();
338 for (auto &arrow : m_arrowsModel->m_arrowsList) {
339 auto node = m_nodesModel->getNodeWithId(id: arrow.startNodeId);
340 auto node2 = m_nodesModel->getNodeWithId(id: arrow.endNodeId);
341 if (node && node2) {
342 arrow.startX = node->x + node->width / 2;
343 arrow.startY = node->y + node->height;
344 arrow.endX = node2->x + node2->width / 2;
345 arrow.endY = node2->y;
346 }
347 }
348 m_arrowsModel->endResetModel();
349}
350
351void NodeView::updateActiveNodesList()
352{
353 QList<NodesModel::Node *> nodes;
354 QList<int> nodesIds;
355 auto node = m_nodesModel->getNodeWithId(id: 0);
356 if (!node)
357 return;
358 nodes << node;
359 int nodeId = node ? node->nextNodeId : -1;
360 while (nodeId > 0) {
361 auto n = m_nodesModel->getNodeWithId(id: nodeId);
362 if (n) {
363 nodes << n;
364 if (!n->disabled)
365 nodesIds << n->nodeId;
366 nodeId = n->nextNodeId;
367 } else {
368 break;
369 }
370 }
371 m_activeNodesIds = nodesIds;
372
373 // Update info of graph being complete (source -> output)
374 bool nodeGraphComplete = false;
375 if (!nodes.isEmpty() && nodes.last()->type == 1)
376 nodeGraphComplete = true;
377
378 if (nodeGraphComplete != m_nodeGraphComplete) {
379 m_nodeGraphComplete = nodeGraphComplete;
380 Q_EMIT nodeGraphCompleteChanged();
381 }
382
383 if (m_activeNodesList != nodes) {
384 m_activeNodesList = nodes;
385 // Don't emit until fully initialized to avoid shader errors
386 if (m_initialized)
387 Q_EMIT activeNodesListChanged();
388 }
389}
390
391void NodeView::deleteSelectedNodes()
392{
393 QList<int> nodes;
394 for (auto &node : m_nodesModel->m_nodesList) {
395 if (node.selected && node.type == 2)
396 nodes << node.nodeId;
397 }
398 m_effectManager->deleteEffectNodes(nodeIds: nodes);
399}
400
401QString NodeView::selectedNodeName() const {
402 if (auto selectedNode = m_nodesModel->m_selectedNode)
403 return selectedNode->name;
404 return QString();
405}
406
407void NodeView::setSelectedNodeName(const QString &name) {
408 if (auto selectedNode = m_nodesModel->m_selectedNode) {
409 if (selectedNode->name == name)
410 return;
411
412 // Make sure the name is unique
413 QString newName = getUniqueNodeName(origName: name);
414 m_nodesModel->beginResetModel();
415 selectedNode->name = newName;
416 m_nodesModel->endResetModel();
417 Q_EMIT selectedNodeNameChanged();
418 }
419}
420
421QString NodeView::selectedNodeDescription() const {
422 if (auto selectedNode = m_nodesModel->m_selectedNode)
423 return selectedNode->description;
424 return QString();
425}
426
427void NodeView::setSelectedNodeDescription(const QString &description) {
428 if (auto selectedNode = m_nodesModel->m_selectedNode) {
429 if (selectedNode->description == description)
430 return;
431
432 m_nodesModel->beginResetModel();
433 selectedNode->description = description;
434 m_nodesModel->endResetModel();
435 Q_EMIT selectedNodeDescriptionChanged();
436 }
437}
438
439QString NodeView::selectedNodeFragmentCode() const
440{
441 if (auto selectedNode = m_nodesModel->m_selectedNode)
442 return selectedNode->fragmentCode;
443 return QString();
444}
445
446void NodeView::setSelectedNodeFragmentCode(const QString &newSelectedNodeFragmentCode)
447{
448 if (auto selectedNode = m_nodesModel->m_selectedNode) {
449 if (selectedNode->fragmentCode == newSelectedNodeFragmentCode)
450 return;
451 selectedNode->fragmentCode = newSelectedNodeFragmentCode;
452 Q_EMIT selectedNodeFragmentCodeChanged();
453 }
454}
455
456QString NodeView::selectedNodeVertexCode() const
457{
458 if (auto selectedNode = m_nodesModel->m_selectedNode)
459 return selectedNode->vertexCode;
460 return QString();
461}
462
463void NodeView::setSelectedNodeVertexCode(const QString &newSelectedNodeVertexCode)
464{
465 if (auto selectedNode = m_nodesModel->m_selectedNode) {
466 if (selectedNode->vertexCode == newSelectedNodeVertexCode)
467 return;
468 selectedNode->vertexCode = newSelectedNodeVertexCode;
469 Q_EMIT selectedNodeVertexCodeChanged();
470 }
471}
472
473QString NodeView::selectedNodeQmlCode() const
474{
475 if (auto selectedNode = m_nodesModel->m_selectedNode)
476 return selectedNode->qmlCode;
477 return QString();
478}
479
480void NodeView::setSelectedNodeQmlCode(const QString &newSelectedNodeQmlCode)
481{
482 if (auto selectedNode = m_nodesModel->m_selectedNode) {
483 if (selectedNode->qmlCode == newSelectedNodeQmlCode)
484 return;
485 selectedNode->qmlCode = newSelectedNodeQmlCode;
486 Q_EMIT selectedNodeQmlCodeChanged();
487 }
488}
489
490bool NodeView::nodeGraphComplete() const
491{
492 return m_nodeGraphComplete;
493}
494
495bool NodeView::effectNodeSelected() const
496{
497 return m_effectNodeSelected;
498}
499
500bool NodeView::mainNodeSelected() const
501{
502 return m_mainNodeSelected;
503}
504
505int NodeView::selectedNodeId() const
506{
507 return m_selectedNodeId;
508}
509
510// Layout the nodes currently in active list
511void NodeView::layoutNodes(bool distribute)
512{
513 if (m_activeNodesList.size() < 2)
514 return;
515
516 // Make sure that also non-connected nodes
517 // are inside the visible nodeview area.
518 if (!distribute) {
519 for (auto &node : m_nodesModel->m_nodesList) {
520 float newX = node.x;
521 float newY = node.y;
522 newX = std::max(a: newX, b: float(-node.width / 2.0f));
523 newX = std::min(a: newX, b: float(width() - node.width / 2.0f));
524 newY = std::max(a: newY, b: float(-node.height / 2.0f));
525 newY = std::min(a: newY, b: float(height() - node.height / 2.0f));
526 node.x = newX;
527 node.y = newY;
528 }
529 }
530
531 const int nodeCount = m_activeNodesList.size();
532 const float areaHeight = height();
533 const float sideMargin = areaHeight * 0.05f;
534 const float marginY = areaHeight * 0.02f;
535 const float areaWCenter = width() / 2.0f;
536 float origFullHeight = m_activeNodesList.last()->y - m_activeNodesList.first()->y;
537 origFullHeight = std::max(a: origFullHeight, b: 100.0f);
538 const float firstY = sideMargin;
539 const float lastY = areaHeight - firstY - m_activeNodesList.last()->height;
540 const float newFullHeight = lastY - firstY;
541 const float yScaling = newFullHeight / origFullHeight;
542 int i = 0;
543 m_nodesModel->beginResetModel();
544 for (const auto &n : m_activeNodesList) {
545 if (!distribute)
546 n->x = areaWCenter - n->width / 2.0f;
547
548 if (i == 0) {
549 n->y = firstY;
550 } else if (i == nodeCount - 1) {
551 n->y = lastY;
552 } else {
553 if (distribute) {
554 n->y = sideMargin + (float(i) / (nodeCount - 1)) * (areaHeight - sideMargin * 2.0f - n->height);
555 } else {
556 n->y = yScaling * n->y;
557 // Make sure that middle nodes Y are between some limits
558 if (auto previousNode = m_activeNodesList.at(i: i - 1))
559 n->y = std::max(a: n->y, b: previousNode->y + previousNode->height + marginY);
560 n->y = std::max(a: n->y, b: firstY + marginY + m_activeNodesList.first()->height);
561 n->y = std::min(a: n->y, b: lastY - marginY - n->height);
562 }
563 }
564 i++;
565 }
566
567 m_nodesModel->endResetModel();
568 updateArrowsPositions();
569}
570
571QStringList NodeView::codeSelectorModel() const
572{
573 return m_codeSelectorModel;
574}
575
576int NodeView::codeSelectorIndex() const
577{
578 return m_codeSelectorIndex;
579}
580
581int NodeView::getNodeIdWithName(const QString &name)
582{
583 for (const auto &node : m_nodesModel->m_nodesList) {
584 if (node.name == name)
585 return node.nodeId;
586 }
587 return -1;
588}
589
590// Update the code selector (combobox) model
591void NodeView::updateCodeSelectorModel()
592{
593 QStringList codeSelectorModel;
594 for (const auto &node : m_nodesModel->m_nodesList) {
595 // Hide "Output" node as it currently doesn't contain code
596 if (node.type == NodesModel::DestinationNode)
597 continue;
598 codeSelectorModel << node.name;
599 }
600 if (codeSelectorModel != m_codeSelectorModel) {
601 m_codeSelectorModel = codeSelectorModel;
602 Q_EMIT codeSelectorModelChanged();
603 }
604}
605
606// Update the index of code selector (combobox) to match the currectly selected node
607void NodeView::updateCodeSelectorIndex() {
608 int index = -1;
609 int i = 0;
610 for (const auto &node : m_nodesModel->m_nodesList) {
611 if (node.nodeId == m_selectedNodeId && node.type != NodesModel::DestinationNode) {
612 index = i;
613 break;
614 }
615 if (node.type != NodesModel::DestinationNode)
616 i++;
617 }
618 index = std::min(a: index, b: int(m_codeSelectorModel.size() - 1));
619 if (index != m_codeSelectorIndex) {
620 m_codeSelectorIndex = index;
621 Q_EMIT codeSelectorIndexChanged();
622 }
623}
624
625void NodeView::toggleNodeDisabled()
626{
627 if (auto node = m_nodesModel->m_selectedNode) {
628 m_nodesModel->beginResetModel();
629 node->disabled = !node->disabled;
630 m_nodesModel->endResetModel();
631 }
632 updateActiveNodesList();
633}
634
635
636// This will return node id, which is not already in use
637int NodeView::getUniqueNodeId()
638{
639 if (!m_nodesModel)
640 return 0;
641
642 int id = 0;
643 // Get biggest id + 1
644 for (const auto &node : m_nodesModel->m_nodesList)
645 id = std::max(a: id, b: node.nodeId);
646 id++;
647
648 return id;
649}
650
651void NodeView::initializeNode(NodesModel::Node &node)
652{
653 node.type = 2;
654 node.x = 20;
655 node.y = 60;
656 node.nodeId = getUniqueNodeId();
657 initializeNodeSize(node);
658}
659
660void NodeView::initializeNodeSize(NodesModel::Node &node)
661{
662 if (node.type == NodesModel::CustomNode) {
663 node.width = 150;
664 node.height = 50;
665 } else {
666 node.width = 80;
667 node.height = 80;
668 }
669}
670
671// Returns unique node name, so name appended with a number.
672// "Custom" -> "Custom2" -> "Custom3" etc.
673QString NodeView::getUniqueNodeName(const QString &origName, int counter)
674{
675 if (!m_nodesModel)
676 return origName;
677
678 // Initially try name as is, then first counter '2'.
679 QString counterString = (counter == 0) ? "" : QString::number(counter + 1);
680 QString name = origName + counterString;
681 counter++;
682 // Bail out just in case
683 if (counter > 99)
684 return name;
685 for (const auto &node : m_nodesModel->m_nodesList) {
686 if (node.name == name) {
687 name = getUniqueNodeName(origName, counter);
688 }
689 }
690 return name;
691}
692

source code of qtquickeffectmaker/tools/qqem/nodeview.cpp