Home > OS >  Animate QGraphicsView fitInView method
Animate QGraphicsView fitInView method

Time:12-15

So lets say I have a simple QGraphicsView and QGraphicsScene, and I want to animate the view to smoothly scroll/scale over to an arbitrary QRecF within the scene. I know you can call view.fitInView() or view.centerOn() to 'frame up' on the rect, however neither of those provide for much animation. I can iteratively call view.centerOn() to animate the scrolling nicely, but this doesn't handle scale. I believe I need to modify the view's viewport but I'm not quite sure how to do that (calling view.setSceneRect() seemed promising but that just sets the area that the view can manage, not what it is currently 'looking at').

Based on some C answer I figured out how to re-write the fitInView method for the most part, however I am stuck on the part where I actually transform the view to look at a different part of the scene without calling centerOn. Here's what I have so far:

view = QtWidgets.QGraphicsView()
scene = QtWidgets.QGraphicsScene()
targetRect = QtCore.QRectF(500, 500, 500, 500)

viewRect = view.viewport().rect()
sceneRect = view.transform().mapRect(targetRect)

xratio = viewRect.width() / sceneRect.width()
yratio = viewRect.height() / sceneRect.height()

# keep aspect
xratio = yratio = min(xratio, yratio)

# so... now what to do with the ratio? I need to scale the view rect somehow.

# animation
start = view.mapToScene(viewRect).boundingRect()
end = sceneRect # I guess?

animator = QtCore.QVariantAnimation()
animator.setStartValue(start)
animator.setEndValue(end)
animator.valueChanged.connect(view.????) # what am I setting here?
animator.start()

CodePudding user response:

I went down many twisty roads only to end up with an incredibly simple answer, thought I'd post it in case anyone finds it useful to animate both scroll and scale at the same time:

view = QtWidgets.QGraphicsView()
scene = QtWidgets.QGraphicsScene()
targetRect = QtCore.QRectF(500, 500, 500, 500)


animator = QtCore.QVariantAnimation()
animator.setStartValue(view.mapToScene(view.viewport().rect()).boundingRect())
animator.setEndValue(targetRect)
animator.valueChanged.connect(lambda x: view.fitInView(x, QtCore.Qt.KeepAspectRatio))
animator.start()

CodePudding user response:

A simple, basic solution is to map the target rectangle and use that value as end value for the animation.

Note that this solution is not really optimal, for two reasons:

  • it completely relies on fitInView(), which has to compute the transformation for the whole scene at each iteration of the animation by checking the current viewport size; a better (but more complex) implementation would be to use scale() or setTransform() and also call centerOn() on the currently mapped rectangle of the transformation;
  • since the scene rect might be smaller than what the viewport is showing, zooming out could be a bit awkward;
class SmoothZoomGraphicsView(QtWidgets.QGraphicsView):
    def __init__(self):
        super().__init__()
        scene = QtWidgets.QGraphicsScene()
        self.setScene(scene)
        self.pixmap = scene.addPixmap(QtGui.QPixmap('image.png'))
        self.animation = QtCore.QVariantAnimation()
        self.animation.valueChanged.connect(self.smoothScale)
        self.setTransformationAnchor(self.AnchorUnderMouse)

    def wheelEvent(self, event):
        viewRect = self.mapToScene(self.viewport().rect()).boundingRect()
        # assuming we are using a basic x2 or /2 ratio, we need to remove
        # a quarter of the width/height and translate to half the position
        # betweeen the current rectangle and cursor position, or add half
        # the size and translate to a negative relative position
        if event.angleDelta().y() > 0:
            xRatio = viewRect.width() / 4
            yRatio = viewRect.height() / 4
            translate = .5
        else:
            xRatio = -viewRect.width() / 2
            yRatio = -viewRect.height() / 2
            translate = -1
        finalRect = viewRect.adjusted(xRatio, yRatio, -xRatio, -yRatio)
        cursor = self.viewport().mapFromGlobal(QtGui.QCursor.pos())
        if cursor in self.viewport().rect():
            line = QtCore.QLineF(viewRect.center(), self.mapToScene(cursor))
            finalRect.moveCenter(line.pointAt(translate))
        self.animation.setStartValue(viewRect)
        self.animation.setEndValue(finalRect)
        self.animation.start()

    def smoothScale(self, rect):
        self.fitInView(rect, QtCore.Qt.KeepAspectRatio)
  • Related