[ui] Utils: add SelectionBox and DelegateSelectionBox

- SelectionBox: generic Selection box component.
- DelegateSelectionBox: specialized SelectionBox to select model delegates
  from an instantiator (Repeater, ListView).

Also Introduce a Geom2D helper class to provide missing features for
intersection testing in QML.
This commit is contained in:
Yann Lanthony 2024-12-06 10:12:11 +01:00
parent dfe2166942
commit 6d2e9a2ba9
5 changed files with 106 additions and 1 deletions

View file

@ -1,11 +1,12 @@
def registerTypes():
from PySide6.QtQml import qmlRegisterType
from PySide6.QtQml import qmlRegisterType, qmlRegisterSingletonType
from meshroom.ui.components.clipboard import ClipboardHelper
from meshroom.ui.components.edge import EdgeMouseArea
from meshroom.ui.components.filepath import FilepathHelper
from meshroom.ui.components.scene3D import Scene3DHelper, TrackballController, Transformations3DHelper
from meshroom.ui.components.csvData import CsvData
from meshroom.ui.components.geom2D import Geom2D
qmlRegisterType(EdgeMouseArea, "GraphEditor", 1, 0, "EdgeMouseArea")
qmlRegisterType(ClipboardHelper, "Meshroom.Helpers", 1, 0, "ClipboardHelper") # TODO: uncreatable
@ -14,3 +15,5 @@ def registerTypes():
qmlRegisterType(Transformations3DHelper, "Meshroom.Helpers", 1, 0, "Transformations3DHelper") # TODO: uncreatable
qmlRegisterType(TrackballController, "Meshroom.Helpers", 1, 0, "TrackballController")
qmlRegisterType(CsvData, "DataObjects", 1, 0, "CsvData")
qmlRegisterSingletonType(Geom2D, "Meshroom.Helpers", 1, 0, "Geom2D")

View file

@ -0,0 +1,8 @@
from PySide6.QtCore import QObject, Slot, QRectF
class Geom2D(QObject):
@Slot(QRectF, QRectF, result=bool)
def rectRectIntersect(self, rect1: QRectF, rect2: QRectF) -> bool:
"""Check if two rectangles intersect."""
return rect1.intersects(rect2)

View file

@ -0,0 +1,32 @@
import QtQuick
import Meshroom.Helpers
/*
A SelectionBox that can be used to select delegates in a model instantiator (Repeater, ListView...).
Interesection test is done in the coordinate system of the container Item, using delegate's bounding boxes.
The list of selected indices is emitted when the selection ends.
*/
SelectionBox {
id: root
// The Item instantiating the delegates.
property Item modelInstantiator
// The Item containing the delegates (used for coordinate mapping).
property Item container
// Emitted when the selection has ended, with the list of selected indices and modifiers.
signal delegateSelectionEnded(list<int> indices, int modifiers)
onSelectionEnded: function(selectionRect, modifiers) {
let selectedIndices = [];
const mappedSelectionRect = mapToItem(container, selectionRect);
for (var i = 0; i < modelInstantiator.count; ++i) {
const delegate = modelInstantiator.itemAt(i);
const delegateRect = Qt.rect(delegate.x, delegate.y, delegate.width, delegate.height);
if (Geom2D.rectRectIntersect(mappedSelectionRect, delegateRect)) {
selectedIndices.push(i);
}
}
delegateSelectionEnded(selectedIndices, modifiers);
}
}

View file

@ -0,0 +1,60 @@
import QtQuick
/*
Simple selection box that can be used by a MouseArea.
Usage:
1. Create a MouseArea and a SelectionBox.
2. Bind the SelectionBox to the MouseArea by setting the `mouseArea` property.
3. Call startSelection() with coordinates when the selection starts.
4. Call endSelection() when the selection ends.
5. Listen to the selectionEnded signal to get the selection rectangle.
*/
Item {
id: root
property MouseArea mouseArea
property alias color: selectionBox.color
property alias border: selectionBox.border
readonly property bool active: mouseArea.drag.target == dragTarget
signal selectionEnded(rect selectionRect, int modifiers)
function startSelection(mouse) {
dragTarget.startPos.x = dragTarget.x = mouse.x;
dragTarget.startPos.y = dragTarget.y = mouse.y;
dragTarget.modifiers = mouse.modifiers;
mouseArea.drag.target = dragTarget;
}
function endSelection() {
if (!active) {
return;
}
mouseArea.drag.target = null;
const rect = Qt.rect(selectionBox.x, selectionBox.y, selectionBox.width, selectionBox.height)
selectionEnded(rect, dragTarget.modifiers);
}
visible: active
Rectangle {
id: selectionBox
color: "#109b9b9b"
border.width: 1
border.color: "#b4b4b4"
x: Math.min(dragTarget.startPos.x, dragTarget.x)
y: Math.min(dragTarget.startPos.y, dragTarget.y)
width: Math.abs(dragTarget.x - dragTarget.startPos.x)
height: Math.abs(dragTarget.y - dragTarget.startPos.y)
}
Item {
id: dragTarget
property point startPos
property var modifiers
}
}

View file

@ -17,3 +17,5 @@ IntSelector 1.0 IntSelector.qml
MScrollBar 1.0 MScrollBar.qml
MSplitView 1.0 MSplitView.qml
DirectionalLightPane 1.0 DirectionalLightPane.qml
SelectionBox 1.0 SelectionBox.qml
DelegateSelectionBox 1.0 DelegateSelectionBox.qml