/* Copyright 2006-2026 The QElectroTech Team This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. QElectroTech is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with QElectroTech. If not, see . */ #ifndef SHAPETRANSFORM_H #define SHAPETRANSFORM_H #include #include /** @brief The ShapeTransform struct Rotation, skew and scale applied to a shape's local geometry around an arbitrary local pivot point. These five scalars plus a pivot are a convenient, directly-editable basis for 2D affine transforms: every handle in the UI changes exactly one field, which is what makes per-handle drag math simple. Note that a plain 2x2 linear map only has 4 true degrees of freedom, one fewer than (rotation, skewX, skewY, scaleX, scaleY) -- so this is not a *unique* representation of a matrix, only a convenient one. That redundancy is harmless for editing (nothing here ever needs to invert the forward direction) and only matters to decomposeLinear() below, which exists for later consumers (flattening a group transform onto its children, importing a foreign matrix) rather than everyday use. The resulting matrix is meant to be handed directly to QGraphicsItem::setTransform(). QGraphicsItem::transformOriginPoint() is deliberately NOT used anywhere in this design: it only centers QGraphicsItem's own rotation()/scale() convenience properties, and has no effect on a custom transform() matrix -- the pivot has to be baked into the matrix itself, as toMatrix() does. QGraphicsItem::pos() supplies the translation on top, unchanged from how shapes are already positioned and moved today. */ struct ShapeTransform { qreal rotation = 0; // degrees qreal skewX = 0; // degrees qreal skewY = 0; // degrees qreal scaleX = 1; qreal scaleY = 1; QPointF pivot; // local coordinates; caller decides the default (usually local bbox center) bool isIdentity() const; // Rotate+shear+scale about the origin, ignoring pivot -- this is the // "linear part" used by compensatedPositionForNewPivot() and by // anything that needs to map a direction/offset rather than a point. QTransform linearPart() const; // The pivot-centered matrix to pass to QGraphicsItem::setTransform(). QTransform toMatrix() const; bool operator==(const ShapeTransform &other) const; bool operator!=(const ShapeTransform &other) const { return !(*this == other); } }; // Canonical decomposition of an arbitrary 2x2 linear map into the five // scalars above, always returning skewY == 0 (all shear folded into // skewX -- matching how most authoring tools represent shear on export). // Rebuilding via ShapeTransform::linearPart() from the result reproduces // the input matrix exactly; the individual scalar values are not // guaranteed to match whatever scalars (if any) originally produced that // matrix, only the matrix itself is guaranteed to match. ShapeTransform decomposeLinear(const QTransform &linear); // Position adjustment needed when the pivot moves, so the shape does not // visibly jump on screen: call once, at the end of a pivot-handle drag, // alongside setting the new pivot. QPointF compensatedPositionForNewPivot( const QPointF &position, const QPointF &oldPivot, const QPointF &newPivot, const QTransform &linearPart); #endif // SHAPETRANSFORM_H