Snapping a QGraphicsItemGroup to a grid
-
I've been trying to create a subclass of
QGraphicsItemGroup
which will snap to a grid on aQGraphicsScene
, but I've been having problems with the movement being overly jumpy. Below is a MWE:from os import environ environ["QT_ENABLE_HIGHDPI_SCALING"] = "0" from PyQt6.QtWidgets import * from PyQt6.QtCore import * from PyQt6.QtGui import * app = QApplication([]) class Location(QGraphicsItem): color = QColor(0, 110, 200, 75) border_color = QColor(0, 255, 0, 255) def __init__(self, size, **kwargs): super().__init__(**kwargs) self.width, self.height = size def boundingRect(self): return QRectF(0, 0, 32*self.width, 32*self.height) def paint(self, *args): painter = args[0] painter.fillRect(1, 1, int(32*self.width) - 2, int(32*self.height) - 2, Location.color) pen = QPen(Location.border_color) pen.setWidth(0) painter.setPen(pen) painter.drawRect(0, 0, int(32*self.width) - 1, int(32*self.height) - 1) def x(self): """Returns the x-coordinate of the location's position.""" return int(self.pos().x()) def y(self): """Returns the y-coordinate of the location's position.""" return int(self.pos().y()) class GridSnappingQGIG(QGraphicsItemGroup): def __init__(self, **kwargs): super().__init__(**kwargs) self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True) self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges, True) def itemChange(self, change, value): scene = self.scene() if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange and scene: x, y = scene.snap_to_grid(value.x(), value.y(), scene.grid_size) return QPointF(x, y) return super().itemChange(change, value) class Scene(QGraphicsScene): def __init__(self, *args): super().__init__(*args) self.grid_size = 32 self.locations = [] def snap_to_grid(self, x, y, grid_size): return [grid_size * round(x / grid_size), grid_size * round(y / grid_size)] def place_location(self, x, y, size): """Places a location of dimensions size at position (x, y).""" loc = Location(size) self.locations.append(loc) self.addItem(loc) loc.setPos(x, y) def move_locations(self, locations): """Puts the locations in the input list in a GridSnappingQGIG to move them.""" x, y = min([loc.x() for loc in locations]), min([loc.y() for loc in locations]) group = GridSnappingQGIG() self.addItem(group) group.setPos(x, y) for loc in locations: group.addToGroup(loc) def mousePressEvent(self, event): self.move_locations(self.locations) QGraphicsScene.mousePressEvent(self, event) def mouseReleaseEvent(self, event): group = self.locations[0].group() for loc in self.locations: group.removeFromGroup(loc) self.removeItem(group) QGraphicsScene.mouseReleaseEvent(self, event) scene = Scene(0, 0, 480, 480) scene.setBackgroundBrush(QColor(0, 0, 0)) scene.place_location(128, 128, [2, 2]) scene.place_location(64, 64, [2, 2]) frame = QFrame() window = QMainWindow() view = QGraphicsView(scene) layout = QVBoxLayout() layout.addWidget(view) frame.setLayout(layout) window.setCentralWidget(frame) window.showMaximized() app.exec()
The
Location
class is the type of object I'm interested in moving around (in groups) on the scene. To accomplish this, when the mouse is pressed, I form aGridSnappingQGIG
to house the locations, which is then destroyed when the mouse is released. The snapping is achieved by reimplementing theitemChange
function ofGridSnappingQGIG
.The problem is that the group often seems to jump wildly past the position of the mouse cursor. The behavior doesn't seem very consistent, so it's hard to pin down what the problem is. The very first movement seems to always work as expected, and sudden mouse movements seem more likely to trigger the problem.
I asked this question here on the StackExchange, and it was recommended that I make a bug report, but I figured I'd ask here first to see if there's something I've messed up.