mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-06-03 03:11:56 +02:00
[ui] Graph: Node selection refactor (1)
Switch selection management backend to a QItemSelectionModel, while keeping the current 'selectedNodes' API for now. Use DelegateSectionBox for node selection in the graph, and rewrite the handling of node selection / displacement.
This commit is contained in:
parent
6d2e9a2ba9
commit
05eabb2b13
2 changed files with 203 additions and 138 deletions
|
@ -7,8 +7,19 @@ import json
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from threading import Thread, Event, Lock
|
from threading import Thread, Event, Lock
|
||||||
from multiprocessing.pool import ThreadPool
|
from multiprocessing.pool import ThreadPool
|
||||||
|
from typing import Iterator
|
||||||
|
|
||||||
from PySide6.QtCore import Slot, QJsonValue, QObject, QUrl, Property, Signal, QPoint
|
from PySide6.QtCore import (
|
||||||
|
Slot,
|
||||||
|
QJsonValue,
|
||||||
|
QObject,
|
||||||
|
QUrl,
|
||||||
|
Property,
|
||||||
|
Signal,
|
||||||
|
QPoint,
|
||||||
|
QItemSelectionModel,
|
||||||
|
QItemSelection,
|
||||||
|
)
|
||||||
|
|
||||||
from meshroom.core import sessionUid
|
from meshroom.core import sessionUid
|
||||||
from meshroom.common.qt import QObjectListModel
|
from meshroom.common.qt import QObjectListModel
|
||||||
|
@ -359,6 +370,8 @@ class UIGraph(QObject):
|
||||||
self._layout = GraphLayout(self)
|
self._layout = GraphLayout(self)
|
||||||
self._selectedNode = None
|
self._selectedNode = None
|
||||||
self._selectedNodes = QObjectListModel(parent=self)
|
self._selectedNodes = QObjectListModel(parent=self)
|
||||||
|
self._nodeSelection = QItemSelectionModel(self._graph.nodes, parent=self)
|
||||||
|
self._nodeSelection.selectionChanged.connect(self.onNodeSelectionChanged)
|
||||||
self._hoveredNode = None
|
self._hoveredNode = None
|
||||||
|
|
||||||
self.submitLabel = "{projectName}"
|
self.submitLabel = "{projectName}"
|
||||||
|
@ -395,6 +408,8 @@ class UIGraph(QObject):
|
||||||
self._layout.reset()
|
self._layout.reset()
|
||||||
# clear undo-stack after layout
|
# clear undo-stack after layout
|
||||||
self._undoStack.clear()
|
self._undoStack.clear()
|
||||||
|
|
||||||
|
self._nodeSelection.setModel(self._graph.nodes)
|
||||||
self.graphChanged.emit()
|
self.graphChanged.emit()
|
||||||
|
|
||||||
def onGraphUpdated(self):
|
def onGraphUpdated(self):
|
||||||
|
@ -642,27 +657,24 @@ class UIGraph(QObject):
|
||||||
nodes = [nodes]
|
nodes = [nodes]
|
||||||
return [ n for n in nodes if n in self._graph.nodes.values() ]
|
return [ n for n in nodes if n in self._graph.nodes.values() ]
|
||||||
|
|
||||||
@Slot(Node, QPoint, QObject)
|
def moveNode(self, node: Node, position: Position):
|
||||||
def moveNode(self, node, position, nodes=None):
|
|
||||||
"""
|
"""
|
||||||
Move 'node' to the given 'position' and also update the positions of 'nodes' if necessary.
|
Move `node` to the given `position`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
node (Node): the node to move
|
node: The node to move.
|
||||||
position (QPoint): the target position
|
position: The target position.
|
||||||
nodes (list[Node]): the nodes to update the position of
|
|
||||||
"""
|
"""
|
||||||
if not nodes:
|
self.push(commands.MoveNodeCommand(self._graph, node, position))
|
||||||
nodes = [node]
|
|
||||||
nodes = self.filterNodes(nodes)
|
@Slot(QPoint)
|
||||||
if isinstance(position, QPoint):
|
def moveSelectedNodesBy(self, offset: QPoint):
|
||||||
position = Position(position.x(), position.y())
|
"""Move all the selected nodes by the given `offset`."""
|
||||||
deltaX = position.x - node.x
|
|
||||||
deltaY = position.y - node.y
|
|
||||||
with self.groupedGraphModification("Move Selected Nodes"):
|
with self.groupedGraphModification("Move Selected Nodes"):
|
||||||
for n in nodes:
|
for node in self.iterSelectedNodes():
|
||||||
position = Position(n.x + deltaX, n.y + deltaY)
|
position = Position(node.x + offset.x(), node.y + offset.y())
|
||||||
self.push(commands.MoveNodeCommand(self._graph, n, position))
|
self.moveNode(node, position)
|
||||||
|
|
||||||
@Slot(QObject)
|
@Slot(QObject)
|
||||||
def removeNodes(self, nodes):
|
def removeNodes(self, nodes):
|
||||||
|
@ -934,23 +946,80 @@ class UIGraph(QObject):
|
||||||
with self.groupedGraphModification("Remove Images From All CameraInit Nodes"):
|
with self.groupedGraphModification("Remove Images From All CameraInit Nodes"):
|
||||||
self.push(commands.RemoveImagesCommand(self._graph, list(self.cameraInits)))
|
self.push(commands.RemoveImagesCommand(self._graph, list(self.cameraInits)))
|
||||||
|
|
||||||
@Slot(Node)
|
def onNodeSelectionChanged(self, selected, deselected):
|
||||||
def appendSelection(self, node):
|
# Update internal cache of selected Node instances.
|
||||||
""" Append 'node' to the selection if it is not already part of the selection. """
|
self._selectedNodes.setObjectList(list(self.iterSelectedNodes()))
|
||||||
if not self._selectedNodes.contains(node):
|
|
||||||
self._selectedNodes.append(node)
|
|
||||||
|
|
||||||
@Slot("QVariantList")
|
|
||||||
def selectNodes(self, nodes):
|
|
||||||
""" Append 'nodes' to the selection. """
|
|
||||||
for node in nodes:
|
|
||||||
self.appendSelection(node)
|
|
||||||
self.selectedNodesChanged.emit()
|
self.selectedNodesChanged.emit()
|
||||||
|
|
||||||
|
@Slot(list)
|
||||||
|
@Slot(list, int)
|
||||||
|
def selectNodes(self, nodes, command=QItemSelectionModel.SelectionFlag.ClearAndSelect):
|
||||||
|
"""Update selection with `nodes` using the specified `command`."""
|
||||||
|
indices = [self._graph._nodes.indexOf(node) for node in nodes]
|
||||||
|
self.selectNodesByIndices(indices, command)
|
||||||
|
|
||||||
@Slot(Node)
|
@Slot(Node)
|
||||||
def selectFollowing(self, node):
|
def selectFollowing(self, node: Node):
|
||||||
""" Select all the nodes the depend on 'node'. """
|
"""Select all the nodes that depend on `node`."""
|
||||||
self.selectNodes(self._graph.dfsOnDiscover(startNodes=[node], reverse=True, dependenciesOnly=True)[0])
|
self.selectNodes(self._graph.dfsOnDiscover(startNodes=[node], reverse=True, dependenciesOnly=True)[0])
|
||||||
|
self.selectedNode = node
|
||||||
|
|
||||||
|
@Slot(int)
|
||||||
|
@Slot(int, int)
|
||||||
|
def selectNodeByIndex(self, index: int, command=QItemSelectionModel.SelectionFlag.ClearAndSelect):
|
||||||
|
"""Update selection with node at the given `index` using the specified `command`."""
|
||||||
|
if isinstance(command, int):
|
||||||
|
command = QItemSelectionModel.SelectionFlag(command)
|
||||||
|
|
||||||
|
self.selectNodesByIndices([index], command)
|
||||||
|
|
||||||
|
if self._nodeSelection.isRowSelected(index):
|
||||||
|
self.selectedNode = self._graph.nodes.at(index)
|
||||||
|
|
||||||
|
@Slot(list)
|
||||||
|
@Slot(list, int)
|
||||||
|
def selectNodesByIndices(
|
||||||
|
self, indices: list[int], command=QItemSelectionModel.SelectionFlag.ClearAndSelect
|
||||||
|
):
|
||||||
|
"""Update selection with node at given `indices` using the specified `command`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
indices: The list of indices to select.
|
||||||
|
command: The selection command to use.
|
||||||
|
"""
|
||||||
|
if isinstance(command, int):
|
||||||
|
command = QItemSelectionModel.SelectionFlag(command)
|
||||||
|
|
||||||
|
itemSelection = QItemSelection()
|
||||||
|
for index in indices:
|
||||||
|
itemSelection.select(
|
||||||
|
self._graph.nodes.index(index), self._graph.nodes.index(index)
|
||||||
|
)
|
||||||
|
|
||||||
|
self._nodeSelection.select(itemSelection, command)
|
||||||
|
|
||||||
|
if self.selectedNode and not self.isSelected(self.selectedNode):
|
||||||
|
self.selectedNode = None
|
||||||
|
|
||||||
|
def iterSelectedNodes(self) -> Iterator[Node]:
|
||||||
|
"""Iterate over the currently selected nodes."""
|
||||||
|
for idx in self._nodeSelection.selectedRows():
|
||||||
|
yield self._graph.nodes.at(idx.row())
|
||||||
|
|
||||||
|
@Slot(Node, result=bool)
|
||||||
|
def isSelected(self, node: Node) -> bool:
|
||||||
|
"""Whether `node` is part of the current selection."""
|
||||||
|
return self._nodeSelection.isRowSelected(self._graph.nodes.indexOf(node))
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def clearNodeSelection(self):
|
||||||
|
"""Clear all node selection."""
|
||||||
|
self.selectedNode = None
|
||||||
|
self._nodeSelection.clear()
|
||||||
|
|
||||||
|
def clearNodeHover(self):
|
||||||
|
""" Reset currently hovered node to None. """
|
||||||
|
self.hoveredNode = None
|
||||||
|
|
||||||
@Slot(str)
|
@Slot(str)
|
||||||
def setSelectedNodesColor(self, color: str):
|
def setSelectedNodesColor(self, color: str):
|
||||||
|
@ -962,48 +1031,12 @@ class UIGraph(QObject):
|
||||||
# Update the color attribute of the nodes which are currently selected
|
# Update the color attribute of the nodes which are currently selected
|
||||||
with self.groupedGraphModification("Set Nodes Color"):
|
with self.groupedGraphModification("Set Nodes Color"):
|
||||||
# For each of the selected nodes -> Check if the node has a color -> Apply the color if it has
|
# For each of the selected nodes -> Check if the node has a color -> Apply the color if it has
|
||||||
for node in self._selectedNodes:
|
for node in self.iterSelectedNodes():
|
||||||
if node.hasInternalAttribute("color"):
|
if node.hasInternalAttribute("color"):
|
||||||
self.setAttribute(node.internalAttribute("color"), color)
|
self.setAttribute(node.internalAttribute("color"), color)
|
||||||
|
|
||||||
@Slot(QObject, QObject)
|
|
||||||
def boxSelect(self, selection, draggable):
|
|
||||||
"""
|
|
||||||
Select nodes that overlap with 'selection'.
|
|
||||||
Takes into account the zoom and position of 'draggable'.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
selection: the rectangle selection widget.
|
|
||||||
draggable: the parent widget that has position and scale data.
|
|
||||||
"""
|
|
||||||
x = selection.x() - draggable.x()
|
|
||||||
y = selection.y() - draggable.y()
|
|
||||||
otherX = x + selection.width()
|
|
||||||
otherY = y + selection.height()
|
|
||||||
x, y, otherX, otherY = [ i / draggable.scale() for i in [x, y, otherX, otherY] ]
|
|
||||||
if x == otherX or y == otherY:
|
|
||||||
return
|
|
||||||
for n in self._graph.nodes:
|
|
||||||
bbox = self._layout.boundingBox([n])
|
|
||||||
# evaluate if the selection and node intersect
|
|
||||||
if not (x > bbox[2] + bbox[0] or otherX < bbox[0] or y > bbox[3] + bbox[1] or otherY < bbox[1]):
|
|
||||||
self.appendSelection(n)
|
|
||||||
self.selectedNodesChanged.emit()
|
|
||||||
|
|
||||||
@Slot()
|
|
||||||
def clearNodeSelection(self):
|
|
||||||
""" Clear all node selection. """
|
|
||||||
self._selectedNode = None
|
|
||||||
self._selectedNodes.clear()
|
|
||||||
self.selectedNodeChanged.emit()
|
|
||||||
self.selectedNodesChanged.emit()
|
|
||||||
|
|
||||||
def clearNodeHover(self):
|
|
||||||
""" Reset currently hovered node to None. """
|
|
||||||
self.hoveredNode = None
|
|
||||||
|
|
||||||
@Slot(result=str)
|
@Slot(result=str)
|
||||||
def getSelectedNodesContent(self):
|
def getSelectedNodesContent(self) -> str:
|
||||||
"""
|
"""
|
||||||
Return the content of the currently selected nodes in a string, formatted to JSON.
|
Return the content of the currently selected nodes in a string, formatted to JSON.
|
||||||
If no node is currently selected, an empty string is returned.
|
If no node is currently selected, an empty string is returned.
|
||||||
|
@ -1158,6 +1191,8 @@ class UIGraph(QObject):
|
||||||
# Currently selected nodes
|
# Currently selected nodes
|
||||||
selectedNodes = makeProperty(QObject, "_selectedNodes", selectedNodesChanged, resetOnDestroy=True)
|
selectedNodes = makeProperty(QObject, "_selectedNodes", selectedNodesChanged, resetOnDestroy=True)
|
||||||
|
|
||||||
|
nodeSelection = makeProperty(QObject, "_nodeSelection")
|
||||||
|
|
||||||
hoveredNodeChanged = Signal()
|
hoveredNodeChanged = Signal()
|
||||||
# Currently hovered node
|
# Currently hovered node
|
||||||
hoveredNode = makeProperty(QObject, "_hoveredNode", hoveredNodeChanged, resetOnDestroy=True)
|
hoveredNode = makeProperty(QObject, "_hoveredNode", hoveredNodeChanged, resetOnDestroy=True)
|
||||||
|
|
|
@ -195,24 +195,21 @@ Item {
|
||||||
if (mouse.button != Qt.MiddleButton && mouse.modifiers == Qt.NoModifier) {
|
if (mouse.button != Qt.MiddleButton && mouse.modifiers == Qt.NoModifier) {
|
||||||
uigraph.clearNodeSelection()
|
uigraph.clearNodeSelection()
|
||||||
}
|
}
|
||||||
if (mouse.button == Qt.LeftButton && (mouse.modifiers == Qt.NoModifier || mouse.modifiers == Qt.ControlModifier)) {
|
if (mouse.button == Qt.LeftButton && (mouse.modifiers == Qt.NoModifier || mouse.modifiers & (Qt.ControlModifier | Qt.ShiftModifier))) {
|
||||||
boxSelect.startX = mouseX
|
nodeSelectionBox.startSelection(mouse);
|
||||||
boxSelect.startY = mouseY
|
|
||||||
boxSelectDraggable.x = mouseX
|
|
||||||
boxSelectDraggable.y = mouseY
|
|
||||||
drag.target = boxSelectDraggable
|
|
||||||
}
|
}
|
||||||
if (mouse.button == Qt.MiddleButton || (mouse.button == Qt.LeftButton && mouse.modifiers & Qt.ShiftModifier)) {
|
if (mouse.button == Qt.MiddleButton || (mouse.button == Qt.LeftButton && mouse.modifiers & Qt.AltModifier)) {
|
||||||
drag.target = draggable // start drag
|
drag.target = draggable // start drag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onReleased: {
|
onReleased: {
|
||||||
drag.target = undefined // stop drag
|
nodeSelectionBox.endSelection();
|
||||||
|
drag.target = null;
|
||||||
root.forceActiveFocus()
|
root.forceActiveFocus()
|
||||||
workspaceClicked()
|
workspaceClicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
onPositionChanged: {
|
onPositionChanged: {
|
||||||
if (drag.active)
|
if (drag.active)
|
||||||
workspaceMoved()
|
workspaceMoved()
|
||||||
|
@ -820,15 +817,20 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nodes
|
// Nodes
|
||||||
Repeater {
|
Repeater {
|
||||||
id: nodeRepeater
|
id: nodeRepeater
|
||||||
|
|
||||||
model: root.graph ? root.graph.nodes : undefined
|
model: root.graph ? root.graph.nodes : undefined
|
||||||
|
|
||||||
property bool loaded: model ? count === model.count : false
|
property bool loaded: model ? count === model.count : false
|
||||||
property bool dragging: false
|
property bool ongoingDrag: false
|
||||||
|
property bool updateSelectionOnClick: false
|
||||||
property var temporaryEdgeAboutToBeRemoved: undefined
|
property var temporaryEdgeAboutToBeRemoved: undefined
|
||||||
|
|
||||||
|
function isNodeSelected(index: int) {
|
||||||
|
return uigraph.nodeSelection.isRowSelected(index);
|
||||||
|
}
|
||||||
|
|
||||||
delegate: Node {
|
delegate: Node {
|
||||||
id: nodeDelegate
|
id: nodeDelegate
|
||||||
|
|
||||||
|
@ -836,44 +838,77 @@ Item {
|
||||||
width: uigraph.layout.nodeWidth
|
width: uigraph.layout.nodeWidth
|
||||||
|
|
||||||
mainSelected: uigraph.selectedNode === node
|
mainSelected: uigraph.selectedNode === node
|
||||||
selected: uigraph.selectedNodes.contains(node)
|
|
||||||
hovered: uigraph.hoveredNode === node
|
hovered: uigraph.hoveredNode === node
|
||||||
|
|
||||||
|
selected: nodeRepeater.isNodeSelected(index);
|
||||||
|
|
||||||
onAttributePinCreated: function(attribute, pin) { registerAttributePin(attribute, pin) }
|
onAttributePinCreated: function(attribute, pin) { registerAttributePin(attribute, pin) }
|
||||||
onAttributePinDeleted: function(attribute, pin) { unregisterAttributePin(attribute, pin) }
|
onAttributePinDeleted: function(attribute, pin) { unregisterAttributePin(attribute, pin) }
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: uigraph.nodeSelection
|
||||||
|
|
||||||
|
function onSelectionChanged() {
|
||||||
|
selected = nodeRepeater.isNodeSelected(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onPressed: function(mouse) {
|
onPressed: function(mouse) {
|
||||||
|
nodeRepeater.updateSelectionOnClick = true;
|
||||||
|
nodeRepeater.ongoingDrag = true;
|
||||||
|
|
||||||
|
let selectionMode = ItemSelectionModel.NoUpdate;
|
||||||
|
|
||||||
|
if(!selected) {
|
||||||
|
selectionMode = ItemSelectionModel.ClearAndSelect;
|
||||||
|
}
|
||||||
|
|
||||||
if (mouse.button === Qt.LeftButton) {
|
if (mouse.button === Qt.LeftButton) {
|
||||||
if (mouse.modifiers & Qt.ControlModifier && !(mouse.modifiers & Qt.AltModifier)) {
|
if(mouse.modifiers & Qt.ShiftModifier) {
|
||||||
if (mainSelected && selected) {
|
selectionMode = ItemSelectionModel.Select;
|
||||||
// Left clicking a selected node twice with control will deselect it
|
|
||||||
uigraph.selectedNodes.remove(node)
|
|
||||||
uigraph.selectedNodesChanged()
|
|
||||||
selectNode(null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if (mouse.modifiers & Qt.AltModifier) {
|
|
||||||
if (!(mouse.modifiers & Qt.ControlModifier)) {
|
|
||||||
uigraph.clearNodeSelection()
|
|
||||||
}
|
|
||||||
uigraph.selectFollowing(node)
|
|
||||||
} else if (!mainSelected && !selected) {
|
|
||||||
uigraph.clearNodeSelection()
|
|
||||||
}
|
}
|
||||||
} else if (mouse.button === Qt.RightButton) {
|
if(mouse.modifiers & Qt.ControlModifier) {
|
||||||
if (!mainSelected && !selected) {
|
selectionMode = ItemSelectionModel.Deselect;
|
||||||
uigraph.clearNodeSelection()
|
}
|
||||||
|
if(mouse.modifiers & Qt.AltModifier) {
|
||||||
|
uigraph.selectFollowing(node);
|
||||||
|
selectionMode = ItemSelectionModel.Select;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mouse.button === Qt.RightButton) {
|
||||||
|
if(selected) {
|
||||||
|
// Keep the full selection when right-clicking on a node.
|
||||||
|
nodeRepeater.updateSelectionOnClick = false;
|
||||||
}
|
}
|
||||||
nodeMenu.currentNode = node
|
nodeMenu.currentNode = node
|
||||||
nodeMenu.popup()
|
nodeMenu.popup()
|
||||||
}
|
}
|
||||||
selectNode(node)
|
|
||||||
|
if(selectionMode != ItemSelectionModel.NoUpdate) {
|
||||||
|
nodeRepeater.updateSelectionOnClick = false;
|
||||||
|
uigraph.selectNodeByIndex(index, selectionMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the node is selected after this, make it the active selected node.
|
||||||
|
if(selected) {
|
||||||
|
uigraph.selectedNode = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onReleased: function(mouse, wasDragged) {
|
||||||
|
nodeRepeater.ongoingDrag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only called when the node has not been dragged.
|
||||||
|
onClicked: function(mouse) {
|
||||||
|
if(!nodeRepeater.updateSelectionOnClick) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uigraph.selectNodeByIndex(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
onDoubleClicked: function(mouse) { root.nodeDoubleClicked(mouse, node) }
|
onDoubleClicked: function(mouse) { root.nodeDoubleClicked(mouse, node) }
|
||||||
|
|
||||||
onMoved: function(position) { uigraph.moveNode(node, position, uigraph.selectedNodes) }
|
|
||||||
|
|
||||||
onEntered: uigraph.hoveredNode = node
|
onEntered: uigraph.hoveredNode = node
|
||||||
onExited: uigraph.hoveredNode = null
|
onExited: uigraph.hoveredNode = null
|
||||||
|
|
||||||
|
@ -899,62 +934,57 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Interactive dragging: move the visual delegates
|
||||||
onPositionChanged: {
|
onPositionChanged: {
|
||||||
if (dragging && uigraph.selectedNodes.contains(node)) {
|
if(!selected || !dragging) {
|
||||||
// Update all selected nodes positions with this node that is being dragged
|
return;
|
||||||
for (var i = 0; i < nodeRepeater.count; i++) {
|
|
||||||
var otherNode = nodeRepeater.itemAt(i)
|
|
||||||
if (uigraph.selectedNodes.contains(otherNode.node) && otherNode.node !== node) {
|
|
||||||
otherNode.x = otherNode.node.x + (x - node.x)
|
|
||||||
otherNode.y = otherNode.node.y + (y - node.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// Compute offset between the delegate and the stored node position.
|
||||||
|
const offset = Qt.point(x - node.x, y - node.y);
|
||||||
|
|
||||||
|
uigraph.nodeSelection.selectedIndexes.forEach(function(idx) {
|
||||||
|
if(idx != index) {
|
||||||
|
const delegate = nodeRepeater.itemAt(idx.row);
|
||||||
|
delegate.x = delegate.node.x + offset.x;
|
||||||
|
delegate.y = delegate.node.y + offset.y;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow all nodes to know if they are being dragged
|
// After drag: apply the final offset to all selected nodes
|
||||||
onDraggingChanged: nodeRepeater.dragging = dragging
|
onMoved: function(position) {
|
||||||
|
const offset = Qt.point(position.x - node.x, position.y - node.y);
|
||||||
|
uigraph.moveSelectedNodesBy(offset);
|
||||||
|
}
|
||||||
|
|
||||||
// Must not be enabled during drag because the other nodes will be slow to match the movement of the node being dragged
|
|
||||||
Behavior on x {
|
Behavior on x {
|
||||||
enabled: !nodeRepeater.dragging
|
enabled: !nodeRepeater.ongoingDrag
|
||||||
NumberAnimation { duration: 100 }
|
NumberAnimation { duration: 100 }
|
||||||
}
|
}
|
||||||
Behavior on y {
|
Behavior on y {
|
||||||
enabled: !nodeRepeater.dragging
|
enabled: !nodeRepeater.ongoingDrag
|
||||||
NumberAnimation { duration: 100 }
|
NumberAnimation { duration: 100 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
DelegateSelectionBox {
|
||||||
id: boxSelect
|
id: nodeSelectionBox
|
||||||
property int startX: 0
|
mouseArea: mouseArea
|
||||||
property int startY: 0
|
modelInstantiator: nodeRepeater
|
||||||
property int toX: boxSelectDraggable.x - startX
|
container: draggable
|
||||||
property int toY: boxSelectDraggable.y - startY
|
onDelegateSelectionEnded: function(selectedIndices, modifiers) {
|
||||||
|
let selectionMode = ItemSelectionModel.ClearAndSelect;
|
||||||
x: toX < 0 ? startX + toX : startX
|
if(modifiers & Qt.ShiftModifier) {
|
||||||
y: toY < 0 ? startY + toY : startY
|
selectionMode = ItemSelectionModel.Select;
|
||||||
width: Math.abs(toX)
|
} else if(modifiers & Qt.ControlModifier) {
|
||||||
height: Math.abs(toY)
|
selectionMode = ItemSelectionModel.Deselect;
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
border.color: activePalette.text
|
|
||||||
visible: mouseArea.drag.target == boxSelectDraggable
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (!visible) {
|
|
||||||
uigraph.boxSelect(boxSelect, draggable)
|
|
||||||
}
|
}
|
||||||
|
uigraph.selectNodesByIndices(selectedIndices, selectionMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
|
||||||
id: boxSelectDraggable
|
|
||||||
}
|
|
||||||
|
|
||||||
DropArea {
|
DropArea {
|
||||||
id: dropArea
|
id: dropArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue