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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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