Skip to content

Commit

Permalink
[ui] Utils: add SelectionBox and DelegateSelectionBox
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
yann-lty committed Nov 25, 2024
1 parent 42c2c2b commit 02087f5
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 1 deletion.
5 changes: 4 additions & 1 deletion meshroom/ui/components/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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")
8 changes: 8 additions & 0 deletions meshroom/ui/components/geom2D.py
Original file line number Diff line number Diff line change
@@ -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)
32 changes: 32 additions & 0 deletions meshroom/ui/qml/Utils/DelegateSelectionBox.qml
Original file line number Diff line number Diff line change
@@ -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);
}
}
60 changes: 60 additions & 0 deletions meshroom/ui/qml/Utils/SelectionBox.qml
Original file line number Diff line number Diff line change
@@ -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
}
}
2 changes: 2 additions & 0 deletions meshroom/ui/qml/Utils/qmldir
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Request 1.0 request.js
Format 1.0 format.js
ErrorHandler 1.0 errorHandler.js
singleton ExifOrientation 1.0 ExifOrientation.qml
SelectionBox 1.0 SelectionBox.qml
DelegateSelectionBox 1.0 DelegateSelectionBox.qml
# using singleton here causes random crash at application exit
# singleton Clipboard 1.0 Clipboard.qml
# singleton Filepath 1.0 Filepath.qml
Expand Down

0 comments on commit 02087f5

Please sign in to comment.