class RoundAnimation(QPropertyAnimation): def __init__(self, target, prop, parent=None): super(RoundAnimation, self).__init__(target, prop, parent) def updateCurrentTime(self, currentTime): self.path = QPainterPath() if self.path.isEmpty(): end = self.endValue() start = self.startValue() self.path.addEllipse(QRectF(start, end)) duration = self.duration() if duration == 0: progress = 1.0 else: progress = (((currentTime - 1) % duration) + 1) / float(duration) easedProgress = self.easingCurve().valueForProgress(progress) if easedProgress > 1.0: easedProgress -= 1.0 elif easedProgress < 0: easedProgress += 1.0 pt = self.path.pointAtPercent(easedProgress) self.updateCurrentValue(pt)
class Animation(QPropertyAnimation): ''' 动画类 ''' def __init__(self, target, prop): ''' target, prop这个两个参数分别对应:动画的产生对象和setter ''' super(Animation, self).__init__(target, prop) def updateCurrentTime(self, currentTime): ''' currentTime(此属性保存动画的当前时间和进度)总是在变化的。 每次动画的currentTime更改时,都会调用updateCurrentTime()函数 ''' self.m_path = QPainterPath() if self.m_path.isEmpty(): end = self.endValue() start = self.startValue() # endValue()、startValue()分别表示动画的结束值和起始值 self.m_path.addEllipse(QRectF(start, end)) # 在指定的boundingRectangle内创建一个椭圆,这里是QRectF(start, end),并将其作为封闭的子路径添加到painter路径中。 dura = self.duration() progress = (((currentTime - 1) % dura) + 1) / float(dura) # duration()此属性保存动画的持续时间(以毫秒为单位)。 默认持续时间为250毫秒。progress则描绘了当前的完成比率。 easedProgress = self.easingCurve().valueForProgress(progress) if easedProgress > 1.0: easedProgress -= 1.0 elif easedProgress < 0: easedProgress += 1.0 # 返回进度缓和曲线的有效进度。 进度必须介于0和1之间,而返回的有效进度可能超出这些范围。大于1就减1,小于0就加1。 pt = self.m_path.pointAtPercent(easedProgress) # 返回当前路径的百分比easedProgress处的点。 # 参数easedProgress必须介于0和1之间。当存在曲线时,百分比参数被映射到贝塞尔方程的t参数。 self.updateCurrentValue(pt) # 每次动画的当前值更改时,都会调用updateCurrentValue()。pt参数是新的当前值。没有这个函数动画动不了。 self.valueChanged.emit(pt) def startAnimation(self, startx, starty, endx, endy, duration): ''' setStartValue()、setEndValue()分别表示设置动画的起止位置,setDuration()设置动画的运行时间。 ''' self.setStartValue(QPointF(startx, starty)) self.setEndValue(QPointF(endx, endy)) self.setDuration(duration) self.setLoopCount(-1) # 值为-1时,动画将永远循环直至停止 self.start()
class _MoveAnimation(QPropertyAnimation): '''move animation''' MOVE1 = 1 MOVE2 = 2 MOVE3 = 3 def __init__(self, target, parent, easing=QEasingCurve.Custom): super(_MoveAnimation, self).__init__(target, b"pos") self.parent = parent self.easing = easing if easing != -1: self.setEasingCurve(easing) self.m_path = QPainterPath() def setMoveType(self, _type): self._type = _type def updateCurrentTime(self, currentTime): if self.m_path.isEmpty(): # end = self.endValue() start = self.startValue() if self._type == self.MOVE1: end = QPoint(self.parent.width() / 4.0, 0) elif self._type == self.MOVE2: end = QPoint((self.parent.width() / 4.0) * 3.0, 0) if not start: start = QPoint(self.parent.width() / 4.0, 0) # 第二个移动没有设置开始坐标,这里以第一个移动结束为开始 elif self._type == self.MOVE3: if not start: start = QPoint((self.parent.width() / 4.0) * 3.0, 0) # 第三个移动没有设置开始坐标,这里以第二个移动结束为开始 end = QPoint(self.parent.width(), 0) self.m_path.moveTo(start) self.m_path.addEllipse(QRectF(start, end)) dura = self.duration() if dura == 0: progress = 1.0 else: progress = (((currentTime - 1) % dura) + 1) / float(dura) easedProgress = self.easingCurve().valueForProgress(progress) if easedProgress > 1.0: easedProgress -= 1.0 elif easedProgress < 0: easedProgress += 1.0 pt = self.m_path.pointAtPercent(easedProgress) self.updateCurrentValue(pt) self.valueChanged.emit(pt) super(_MoveAnimation, self).updateCurrentTime(currentTime)
class _MoveAnimation(QPropertyAnimation): '''move animation''' MOVE1 = 1 MOVE2 = 2 MOVE3 = 3 def __init__(self, target, parent, easing = QEasingCurve.Custom): super(_MoveAnimation, self).__init__(target, b"pos") self.parent = parent self.easing = easing if easing != -1: self.setEasingCurve(easing) self.m_path = QPainterPath() def setMoveType(self, _type): self._type = _type def updateCurrentTime(self, currentTime): if self.m_path.isEmpty(): # end = self.endValue() start = self.startValue() if self._type == self.MOVE1: end = QPoint(self.parent.width() / 4.0, 0) elif self._type == self.MOVE2: end = QPoint((self.parent.width() / 4.0) * 3.0, 0) if not start: start = QPoint(self.parent.width() / 4.0, 0) # 第二个移动没有设置开始坐标,这里以第一个移动结束为开始 elif self._type == self.MOVE3: if not start: start = QPoint((self.parent.width() / 4.0) * 3.0, 0) # 第三个移动没有设置开始坐标,这里以第二个移动结束为开始 end = QPoint(self.parent.width(), 0) self.m_path.moveTo(start) self.m_path.addEllipse(QRectF(start, end)) dura = self.duration() if dura == 0: progress = 1.0 else: progress = (((currentTime - 1) % dura) + 1) / float(dura) easedProgress = self.easingCurve().valueForProgress(progress) if easedProgress > 1.0: easedProgress -= 1.0 elif easedProgress < 0: easedProgress += 1.0 pt = self.m_path.pointAtPercent(easedProgress) self.updateCurrentValue(pt) self.valueChanged.emit(pt) super(_MoveAnimation, self).updateCurrentTime(currentTime)
class _MpcbAnimation(QPropertyAnimation): def __init__(self, parent, target, index, easing = QEasingCurve.InQuad): super(_MpcbAnimation, self).__init__(target, b"pos") self.parent = parent self.m_pathType = 1 self.easing = easing self.createAnimation(target, index) def createAnimation(self, target, index): '''创建动画''' self.m_path = QPainterPath() if self.easing != -1: self.setEasingCurve(self.easing) self.setStartValue(QPointF(0, 0)) self.setEndValue(QPointF(90, 90)) self.setDuration(2000) self.setLoopCount(-1) def updateCurrentTime(self, currentTime): if self.m_pathType: if self.m_path.isEmpty(): end = self.endValue() start = self.startValue() self.m_path.moveTo(start) self.m_path.addEllipse(QRectF(start, end)) dura = self.duration() if dura == 0: progress = 1.0 else: progress = (((currentTime - 1) % dura) + 1) / float(dura) easedProgress = self.easingCurve().valueForProgress(progress) if easedProgress > 1.0: easedProgress -= 1.0 elif easedProgress < 0: easedProgress += 1.0 pt = self.m_path.pointAtPercent(easedProgress) self.updateCurrentValue(pt) self.valueChanged.emit(pt) else: super(_MpcbAnimation, self).updateCurrentTime(currentTime)
class _MpcbAnimation(QPropertyAnimation): def __init__(self, parent, target, index, easing=QEasingCurve.InQuad): super(_MpcbAnimation, self).__init__(target, b"pos") self.parent = parent self.m_pathType = 1 self.easing = easing self.createAnimation(target, index) def createAnimation(self, target, index): '''创建动画''' self.m_path = QPainterPath() if self.easing != -1: self.setEasingCurve(self.easing) self.setStartValue(QPointF(0, 0)) self.setEndValue(QPointF(90, 90)) self.setDuration(2000) self.setLoopCount(-1) def updateCurrentTime(self, currentTime): if self.m_pathType: if self.m_path.isEmpty(): end = self.endValue() start = self.startValue() self.m_path.moveTo(start) self.m_path.addEllipse(QRectF(start, end)) dura = self.duration() if dura == 0: progress = 1.0 else: progress = (((currentTime - 1) % dura) + 1) / float(dura) easedProgress = self.easingCurve().valueForProgress(progress) if easedProgress > 1.0: easedProgress -= 1.0 elif easedProgress < 0: easedProgress += 1.0 pt = self.m_path.pointAtPercent(easedProgress) self.updateCurrentValue(pt) self.valueChanged.emit(pt) else: super(_MpcbAnimation, self).updateCurrentTime(currentTime)
class Animation(QPropertyAnimation): LinearPath, CirclePath = range(2) def __init__(self, target, prop): super(Animation, self).__init__(target, prop) self.setPathType(Animation.LinearPath) def setPathType(self, pathType): self.m_pathType = pathType self.m_path = QPainterPath() def updateCurrentTime(self, currentTime): if self.m_pathType == Animation.CirclePath: if self.m_path.isEmpty(): end = self.endValue() start = self.startValue() self.m_path.moveTo(start) self.m_path.addEllipse(QRectF(start, end)) dura = self.duration() if dura == 0: progress = 1.0 else: progress = (((currentTime - 1) % dura) + 1) / float(dura) easedProgress = self.easingCurve().valueForProgress(progress) if easedProgress > 1.0: easedProgress -= 1.0 elif easedProgress < 0: easedProgress += 1.0 pt = self.m_path.pointAtPercent(easedProgress) self.updateCurrentValue(pt) self.valueChanged.emit(pt) else: super(Animation, self).updateCurrentTime(currentTime)
class BorderItem(QGraphicsObject): def __init__(self, scene, view_scale, shape, transform=QTransform(), parent=None): super(BorderItem, self).__init__(parent) self.setTransform(transform) self.shape = shape self._item_path = QPainterPath(QPoint(0, 0)) self._pen = QPen() self._pen.setWidthF(adjust_pen_width(PEN_STANDARD_WIDTH, view_scale)) # 设置蚂蚁线数据 self._line_len = 4 self._line_step = .2 self._line_speed = 100 self._line_color = Qt.black # 线条的长度 self._dashes = 4 # 空白长度 self._spaces = 10 self._dash_pattern = [self._line_len] * 20 self._timer = QTimer() self._timer.timeout.connect(self.update_value) if scene: scene.clearSelection() scene.addItem(self) def is_empty(self): return self._item_path.isEmpty() def get_path(self): return self._item_path def get_scene_path(self): return self.mapToScene(self.get_path()) def copy(self): new_item = SelectionItem(position=self.scenePos(), scene=None, view_scale=1, path=self._item_path, shape=self.get_shape(), transform=self.transform(), parent=self.parent()) new_item.pen = self.pen return new_item def set_item_path_by_size(self, width=0, height=0): self._item_path = QPainterPath(QPoint(0, 0)) self._item_path.addRect(QRectF(0, 0, width, height)) self.update(self.boundingRect()) def set_item_path_by_path(self, path): self._item_path = path def set_pen_width_by_scale(self, width: [int, float]): pen_width = adjust_pen_width(PEN_STANDARD_WIDTH, width) self._pen.setWidthF(pen_width) def set_pen_width_by_width(self, width: [int, float]): if isinstance(width, int): self._pen.setWidth(width) elif isinstance(width, float): self._pen.setWidthF(width) @property def pen(self): return self._pen @pen.setter def pen(self, new_pen: QPen): self._pen = new_pen def update_value(self): """""" if self._dashes >= self._line_len and self._spaces >= self._line_len: self._dashes = self._spaces = 0. if self._dashes <= 0 and self._spaces < self._line_len: self._spaces += self._line_step elif self._dashes < self._line_len <= self._spaces: self._dashes += self._line_step self._dash_pattern[0] = self._dashes self._dash_pattern[1] = self._spaces self.update(self.boundingRect()) def add_area(self, path: QPainterPath): """TODO""" def cut_area(self, path: QPainterPath): """TODO""" def combine_area(self, path: QPainterPath): """TODO""" def intersects(self, other) -> bool: p1 = self.mapToScene(self.get_path()) p2 = other.mapToScene(other.get_path()) return p1.intersects(p2) def shape(self): return self._item_path # 继承QGraphicsItem类必须实现 boundingRect() paint()两个方法 # 返回本item的 包围和矩形 QRectF 用于item的点击等判断 def boundingRect(self): return self._item_path.boundingRect().adjusted(0, 0, 2, 2) def paint(self, painter: QPainter, option, widget=None) -> None: self._pen.setColor(Qt.white) self._pen.setStyle(Qt.SolidLine) painter.setPen(self._pen) painter.drawPath(self._item_path) self._pen.setColor(Qt.black) self._pen.setDashPattern(self._dash_pattern) painter.setPen(self._pen) painter.drawPath(self._item_path) def __add__(self, other): """ 轮廓 +operation :param other: :return: self """ p1 = self.mapToScene(self._item_path) p2 = other.mapToScene(other.get_path()) new_path = self.mapFromScene(p1 + p2) new_item = self.copy() new_item.set_item_path_by_path(new_path) return new_item def __iadd__(self, other): """ 轮廓 +=operation :param other: :return: self """ p1 = self.mapToScene(self._item_path) p2 = other.mapToScene(other.get_path()) new_path = self.mapFromScene(p1 + p2) new_path.closeSubpath() self._item_path = new_path return self def __sub__(self, other): """ 轮廓 -operation :param other: :return: self """ p1 = self.mapToScene(self._item_path) p2 = other.mapToScene(other.get_path()) new_path = self.mapFromScene(p1 - p2) new_path.closeSubpath() new_item = self.copy() new_item.set_item_path_by_path(new_path) return new_item def __isub__(self, other): """ 轮廓 -=operation :param other: :return: self """ p1 = self.mapToScene(self._item_path) p2 = other.mapToScene(other.get_path()) new_path = self.mapFromScene(p1 - p2) new_path.closeSubpath() self._item_path = new_path self.update() return self def __and__(self, other): """ 轮廓 &operation :param other: :return: self """ p1 = self.mapToScene(self._item_path) p2 = other.mapToScene(other.get_path()) new_path = self.mapFromScene(p1 & p2) new_path.closeSubpath() new_item = self.copy() new_item.set_item_path_by_path(new_path) return new_item def __iand__(self, other): """ 轮廓 &=operation :param other: :return: self """ p1 = self.mapToScene(self._item_path) p2 = other.mapToScene(other.get_path()) self._item_path = self.mapFromScene(p1 & p2) self._item_path.closeSubpath() self.update() return self
class GraphicsPathObject(QGraphicsObject): """A QGraphicsObject subclass implementing an interface similar to QGraphicsPathItem, and also adding a positionChanged() signal """ positionChanged = Signal([], ["QPointF"]) def __init__(self, parent=None, **kwargs): QGraphicsObject.__init__(self, parent, **kwargs) self.setFlag(QGraphicsObject.ItemSendsGeometryChanges) self.__path = QPainterPath() self.__brush = QBrush(Qt.NoBrush) self.__pen = QPen() self.__boundingRect = None def setPath(self, path): """Set the items `path` (:class:`QPainterPath`). """ if not isinstance(path, QPainterPath): raise TypeError("%r, 'QPainterPath' expected" % type(path)) if self.__path != path: self.prepareGeometryChange() # Need to store a copy of object so the shape can't be mutated # without properly updating the geometry. self.__path = QPainterPath(path) self.__boundingRect = None self.update() def path(self): """Return the items path. """ return QPainterPath(self.__path) def setBrush(self, brush): """Set the items `brush` (:class:`QBrush`) """ if not isinstance(brush, QBrush): brush = QBrush(brush) if self.__brush != brush: self.__brush = QBrush(brush) self.update() def brush(self): """Return the items brush. """ return QBrush(self.__brush) def setPen(self, pen): """Set the items outline `pen` (:class:`QPen`). """ if not isinstance(pen, QPen): pen = QPen(pen) if self.__pen != pen: self.prepareGeometryChange() self.__pen = QPen(pen) self.__boundingRect = None self.update() def pen(self): """Return the items pen. """ return QPen(self.__pen) def paint(self, painter, option, widget=None): if self.__path.isEmpty(): return painter.save() painter.setPen(self.__pen) painter.setBrush(self.__brush) painter.drawPath(self.__path) painter.restore() def boundingRect(self): if self.__boundingRect is None: br = self.__path.controlPointRect() pen_w = self.__pen.widthF() self.__boundingRect = br.adjusted(-pen_w, -pen_w, pen_w, pen_w) return self.__boundingRect def shape(self): return shapeFromPath(self.__path, self.__pen) def itemChange(self, change, value): if change == QGraphicsObject.ItemPositionHasChanged: pos = qunwrap(value) self.positionChanged.emit() self.positionChanged[QPointF].emit(pos) return QGraphicsObject.itemChange(self, change, value)