Setting Transformation Point On Scaling QGraphicsItem

Viewed 119

I am having an issue here.

In the given code, The animation and transformations are working perfectly. However, I want the set the transformation point.

According to me, I have to set .setTransformationOriginPoint(item.boundingRect().center() however, it looks like the transformations are taking place from top left.

I even tried setting the point manually but still it didn't work.

Here is the code:

from typing import Any
from PyQt6.QtCore import QPointF, QSequentialAnimationGroup, Qt, pyqtSignal
from PyQt6.QtGui import QColor, QEnterEvent, QMouseEvent , QTransform
from PyQt6.QtWidgets import QApplication, QGraphicsColorizeEffect, QGraphicsScene, QGraphicsView, QMainWindow, QWidget
from functools import partial
from PyQt6.QtSvgWidgets import QGraphicsSvgItem

from typing import Any, Callable
from PyQt6.QtCore import QEasingCurve, QPoint, QPropertyAnimation, QRect, QVariantAnimation
from PyQt6.QtWidgets import QGraphicsOpacityEffect, QWidget

class Animation:
    def variantAnimation(startValue: Any , endValue: Any , duration: int , callback: Callable) -> QVariantAnimation:
        animation = QVariantAnimation()

        animation.setStartValue(startValue)

        animation.setEndValue(endValue)

        animation.setDuration(duration)

        animation.setEasingCurve(QEasingCurve.Type.InOutQuad)

        animation.valueChanged.connect(callback)

        return animation


class Rounded_Jolly_Button(QGraphicsView):
    clicked = pyqtSignal()
    
    def __init__(self , parent: QWidget , location: str):
        super().__init__(parent)

        self.location = location

        self.setStyleSheet("background-color: #2E3440; border-radius: 22px")

        self.setFixedSize(45 , 45)

        self.setCursor(Qt.CursorShape.PointingHandCursor)

        self._scene = QGraphicsScene()

        self.setScene(self._scene)

        self._scene.setSceneRect(0 , 0 , 40 , 40)

        self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)

        self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)

        self.setStyles()

    def setStyles(self):
        color = QGraphicsColorizeEffect()

        color.setColor(QColor("#D8DEE9"))

        svg = QGraphicsSvgItem("./assets/google.svg")

        svg.setAcceptHoverEvents(True)

        svg.setPos(10 , 10)

        svg.setGraphicsEffect(color)

        self._scene.addItem(svg)

        svg.setTransformOriginPoint(svg.boundingRect().center())

        self.animations = QSequentialAnimationGroup()

        self.animations.addAnimation(Animation.variantAnimation(QPointF(1.0 , 1.0) , QPointF(1.25 , 0.75) , 270 , partial(self.updateTransform , svg)))

        self.animations.addAnimation(Animation.variantAnimation(QPointF(1.25 , 0.75) , QPointF(.75 , 1.25) , 90 , partial(self.updateTransform , svg)))

        self.animations.addAnimation(Animation.variantAnimation(QPointF(.75 , 1.25) , QPointF(1.15 , .85) , 90 , partial(self.updateTransform , svg)))

        self.animations.addAnimation(Animation.variantAnimation(QPointF(1.15 , 0.85) , QPointF(.95 , 1.05) , 135 , partial(self.updateTransform , svg)))

        self.animations.addAnimation(Animation.variantAnimation(QPointF(.95 , 1.05) , QPointF(1.05 , 0.95) , 90 , partial(self.updateTransform , svg)))

        self.animations.addAnimation(Animation.variantAnimation(QPointF(1.05 , 0.95) , QPointF(1 , 1) , 225 , partial(self.updateTransform , svg)))

    def enterEvent(self, event: QEnterEvent) -> None:
        self.animations.start()

        return super().enterEvent(event)

    def mousePressEvent(self, ev: QMouseEvent) -> None:
        if(ev.button() == Qt.MouseButton.LeftButton):
            self.clicked.emit()

        return super().mousePressEvent(ev)

    def updateTransform(self , target: QGraphicsSvgItem , newValue: Any):
        target.setTransform(QTransform().scale(newValue.x() , newValue.y()))


def window():
    app = QApplication([])

    window = QMainWindow()

    parent = QWidget()

    button = Rounded_Jolly_Button(parent , None)

    window.setCentralWidget(parent)

    window.show()
    
    exit(app.exec())
    
if __name__ == '__main__':
   window()

Note: ./assets/google.svg contains an svg downloaded from Font Awesome and resized to 20x20 pixel

1 Answers

Transformations are always created with an origin point set at (0, 0), and, when using setTransform(), the item's transformOriginPoint is ignored, since it's only used for the internal setRotation and setScale functions.

Internally, when a rotation or scale is set to a QGraphicsItem, the transformation is applied in the following way:

  • create a new transformation;
  • translate it to the origin point;
  • apply the rotation;
  • apply the scale;
  • restore the translation back;

Since you need to do an asymmetrical scaling (which setScale doesn't provide), you need to do the same for the new transformation.

    def updateTransform(self , target: QGraphicsSvgItem , newValue: Any):
        origin = target.transformOriginPoint()
        transform = QTransform().translate(origin.x(), origin.y())
        transform.scale(newValue.x(), newValue.y())
        transform.translate(-origin.x(), -origin.y())
        target.setTransform(transform)
Related