class AnimateWindowOpacity: def __init__(self, widget: QtWidgets.QWidget, duration: int, start_value: float = 0.8, end_value: float = 1.0): self.widget = widget self.duration = duration self.animation = QPropertyAnimation(self.widget, b"windowOpacity") self.start_value, self.end_value = start_value, end_value self.setup_animation(self.start_value, self.end_value) def setup_animation(self, start_value: float = 0.0, end_value: float = 1.0, duration: int = 0): if not duration: duration = self.duration self.animation.setDuration(duration) self.animation.setKeyValueAt(0.0, start_value) self.animation.setKeyValueAt(1.0, end_value) self.animation.setEasingCurve(QEasingCurve.OutCubic) def fade_in(self, duration: int = 0): if self.widget.windowOpacity() >= self.end_value: return False self.setup_animation(self.start_value, self.end_value, duration) self.play() return True def fade_out(self, duration: int = 0): if self.widget.windowOpacity() <= self.start_value: return False self.setup_animation(self.end_value, self.start_value, duration) self.play() return True def play(self): if self.animation.state() != QAbstractAnimation.Running: self.animation.start()
class Eye(QWidget): def __init__(self, *args, **kwargs): QWidget.__init__(self, *args, **kwargs) self.body_color = QColor(0xffffff) self.border_color = QColor(0x000000) self.border_size = 0.02 self.iris_size = 0.4 self.watch_direction = (0.0, 0.0) self.iris = Iris(QColor(0x00aa00), parent=self) self.iris_move_anim = QPropertyAnimation( self.iris, b'pos') # Animation for iris position self.iris_move_anim.setDuration(100) self.iris_size_anim = QPropertyAnimation( self.iris, b'pupil_size') # Animation for pupil size self.iris_size_anim.setDuration(500) self.resize_iris() def resize_iris(self): pupil_size = int(min(self.width(), self.height()) * self.iris_size) self.iris.setFixedSize(pupil_size, pupil_size) self.set_watch_direction(*self.watch_direction, animate=False) def resizeEvent(self, event: QResizeEvent): self.resize_iris() def paintEvent(self, event): size = min(self.width(), self.height()) painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.translate(self.width() / 2, self.height() / 2) # draw border around eye painter.setBrush(QBrush(self.border_color)) painter.drawEllipse(QPointF(0, 0), size / 2, size / 2) # draw sclera (white part of eyes) painter.setBrush(QBrush(self.body_color)) body_size = size * (1 - self.border_size) * 0.5 painter.drawEllipse(QPointF(0, 0), body_size, body_size) @property def radius(self): return min(self.width(), self.height()) / 2 @property def body_radius(self): return self.radius * (1 - self.border_size) @staticmethod def cart2pol(x, y): """Convert cartesian coordinate to polar""" # https://stackoverflow.com/a/26757297/8181134 mag = np.sqrt(x**2 + y**2) angle = np.arctan2(y, x) return (mag, angle) @staticmethod def pol2cart(mag, angle): """Convert polar coordinate to cartesian""" # https://stackoverflow.com/a/26757297/8181134 x = mag * np.cos(angle) y = mag * np.sin(angle) return (x, y) def set_watch_direction(self, horizontal, vertical, animate=True): """ Move the eyes to the specified direction :param horizontal: float [-1 1] :param vertical: float [-1 1] """ self.watch_direction = (horizontal, vertical) mag, angle = self.cart2pol(horizontal, vertical) mag = np.clip(mag, 0, 1) # Limit the magnitude to max 1 mag *= (self.body_radius - self.iris.radius ) # Max value for mag is so the edge of iris hits edge of eye x, y = self.pol2cart(mag, angle) x += (self.width() / 2) - self.iris.radius # Position of top-left corner of iris y += (self.height() / 2) - self.iris.radius if self.iris_move_anim.state() != QPropertyAnimation.Stopped: self.iris_move_anim.stop() if animate: self.iris_move_anim.setStartValue(self.iris.pos()) self.iris_move_anim.setEndValue(QPointF(x, y)) QTimer.singleShot(0, self.iris_move_anim.start) else: QTimer.singleShot(0, lambda x=x, y=y: self.iris.move(x, y)) def set_pupil_size(self, size: float): """Set the pupil size :arg size: Size of the pupil [0..1] """ self.iris_size_anim.stop() self.iris_size_anim.setStartValue(self.iris.pupil_size) self.iris_size_anim.setEndValue(size) QTimer.singleShot(0, self.iris_size_anim.start)
class AnimatedToggle(Toggle): _transparent_pen = QPen(Qt.transparent) _light_grey_pen = QPen(Qt.lightGray) def __init__(self, *args, pulse_unchecked_color="#44999999", pulse_checked_color="#4400B0EE", **kwargs): self._pulse_radius = 0 super().__init__(*args, **kwargs) self.animation = QPropertyAnimation(self, b"handle_position", self) self.animation.setEasingCurve(QEasingCurve.InOutCubic) self.animation.setDuration(200) # time in ms self.pulse_anim = QPropertyAnimation(self, b"pulse_radius", self) self.pulse_anim.setDuration(350) # time in ms self.pulse_anim.setStartValue(10) self.pulse_anim.setEndValue(20) self.animations_group = QSequentialAnimationGroup() self.animations_group.addAnimation(self.animation) self.animations_group.addAnimation(self.pulse_anim) self._pulse_unchecked_animation = QBrush(QColor(pulse_unchecked_color)) self._pulse_checked_animation = QBrush(QColor(pulse_checked_color)) @Slot(int) def handle_state_change(self, value): self.animations_group.stop() if value: self.animation.setEndValue(1) else: self.animation.setEndValue(0) self.animations_group.start() def paintEvent(self, e: QPaintEvent): contRect = self.contentsRect() handleRadius = round(0.24 * contRect.height()) p = QPainter(self) p.setRenderHint(QPainter.Antialiasing) p.setPen(self._transparent_pen) barRect = QRectF(0, 0, contRect.width() - handleRadius, 0.40 * contRect.height()) barRect.moveCenter(contRect.center()) rounding = barRect.height() / 2 # the handle will move along this line trailLength = contRect.width() - 2 * handleRadius xPos = contRect.x( ) + handleRadius + trailLength * self._handle_position if self.pulse_anim.state() == QPropertyAnimation.Running: p.setBrush(self._pulse_checked_animation if self.isChecked( ) else self._pulse_unchecked_animation) p.drawEllipse(QPointF(xPos, barRect.center().y()), self._pulse_radius, self._pulse_radius) if self.isChecked(): p.setBrush(self._bar_checked_brush) p.drawRoundedRect(barRect, rounding, rounding) p.setBrush(self._handle_checked_brush) else: p.setBrush(self._bar_brush) p.drawRoundedRect(barRect, rounding, rounding) p.setPen(self._light_grey_pen) p.setBrush(self._handle_brush) p.drawEllipse(QPointF(xPos, barRect.center().y()), handleRadius, handleRadius) p.end()
class StackedWidgetSlideOverAnimator(AbstractAnimator): m_direction: AnimationDirection m_coveredWidget: QWidget m_decorator: StackedWidgetSlideOverDecorator m_animation: QPropertyAnimation def __init__(self, _container, _widgetForSlide): super(StackedWidgetSlideOverAnimator, self).__init__(_container) self.m_direction = AnimationDirection.FromLeftToRight self.m_coveredWidget = _container.currentWidget() self.m_decorator = StackedWidgetSlideOverDecorator( _container, _widgetForGrab=_widgetForSlide) self.m_animation = QPropertyAnimation(self.m_decorator, b"pos") _container.installEventFilter(self) self.m_animation.setDuration(400) self.m_decorator.hide() def finished(): self.setAnimatedStopped() if self.isAnimatedForward(): _container.setCurrentWidget(_widgetForSlide) self.m_decorator.hide() self.m_animation.finished.connect(finished) def updateCoveredWidget(self): self.m_coveredWidget = self.stackedWidget().currentWidget() def setAnimationDirection(self, _direction): if self.m_direction != _direction: self.m_direction = _direction def animationDuration(self): return self.m_animation.duration() def animateForward(self): self.slideOverIn() def slideOverIn(self): if self.isAnimated() and self.isAnimatedForward(): return self.setAnimatedForward() self.m_decorator.grabWidget() startPos = QPoint() finalPos = QPoint() if self.m_direction == AnimationDirection.FromLeftToRight: startPos.setX(-1 * self.stackedWidget().width()) if self.m_direction == AnimationDirection.FromRightToLeft: startPos.setX(self.stackedWidget().width()) if self.m_direction == AnimationDirection.FromTopToBottom: startPos.setY(-1 * self.stackedWidget().height()) if self.m_direction == AnimationDirection.FromBottomToTop: startPos.setY(self.stackedWidget().height()) self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.InOutExpo) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startPos) self.m_animation.setEndValue(finalPos) self.m_animation.start() def animateBackward(self): self.slideOverOut() def slideOverOut(self): if self.isAnimated() and self.isAnimatedBackward(): return self.setAnimatedBackward() self.m_decorator.grabWidget() startPos = QPoint() finalPos = QPoint() if self.m_direction == AnimationDirection.FromLeftToRight: finalPos.setX(-1 * self.stackedWidget().width()) if self.m_direction == AnimationDirection.FromRightToLeft: finalPos.setX(self.stackedWidget().width()) if self.m_direction == AnimationDirection.FromTopToBottom: finalPos.setY(-1 * self.stackedWidget().height()) if self.m_direction == AnimationDirection.FromBottomToTop: finalPos.setY(self.stackedWidget().height()) if isinstance(self.stackedWidget(), type(self)): self.stackedWidget().setCurrentWidget(self.m_coveredWidget) self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.InOutExpo) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startPos) self.m_animation.setEndValue(finalPos) self.m_animation.start() def eventFilter(self, _object, _event): if _object == self.stackedWidget() and _event.type( ) == QEvent.Resize and self.m_decorator.isVisible(): self.m_decorator.grabWidget() return QWidget.eventFilter(self, _object, _event) def stackedWidget(self): return self.parent()
class ScreensharingToolbox(base_class, ui_class): exposedPixels = 3 def __init__(self, parent): super(ScreensharingToolbox, self).__init__(parent) with Resources.directory: self.setupUi() parent.installEventFilter(self) self.animation = QPropertyAnimation(self, 'pos') self.animation.setDuration(250) self.animation.setDirection(QPropertyAnimation.Forward) self.animation.setEasingCurve( QEasingCurve.Linear) # or OutCirc with 300ms self.retract_timer = QTimer(self) self.retract_timer.setInterval(3000) self.retract_timer.setSingleShot(True) self.retract_timer.timeout.connect(self.retract) self.resize(self.size().expandedTo(self.toolbox_layout.minimumSize())) def setupUi(self): super(ScreensharingToolbox, self).setupUi(self) # fix the SVG icons, as the generated code loads them as pixmaps, losing their ability to scale -Dan scale_icon = QIcon() scale_icon.addFile(Resources.get('icons/scale.svg'), mode=QIcon.Normal, state=QIcon.Off) viewonly_icon = QIcon() viewonly_icon.addFile(Resources.get('icons/viewonly.svg'), mode=QIcon.Normal, state=QIcon.Off) screenshot_icon = QIcon() screenshot_icon.addFile(Resources.get('icons/screenshot.svg'), mode=QIcon.Normal, state=QIcon.Off) fullscreen_icon = QIcon() fullscreen_icon.addFile(Resources.get('icons/fullscreen.svg'), mode=QIcon.Normal, state=QIcon.Off) fullscreen_icon.addFile(Resources.get('icons/fullscreen-exit.svg'), mode=QIcon.Normal, state=QIcon.On) fullscreen_icon.addFile(Resources.get('icons/fullscreen-exit.svg'), mode=QIcon.Active, state=QIcon.On) fullscreen_icon.addFile(Resources.get('icons/fullscreen-exit.svg'), mode=QIcon.Disabled, state=QIcon.On) fullscreen_icon.addFile(Resources.get('icons/fullscreen-exit.svg'), mode=QIcon.Selected, state=QIcon.On) minimize_icon = QIcon() minimize_icon.addFile(Resources.get('icons/minimize.svg'), mode=QIcon.Normal, state=QIcon.Off) minimize_icon.addFile(Resources.get('icons/minimize-active.svg'), mode=QIcon.Active, state=QIcon.Off) close_icon = QIcon() close_icon.addFile(Resources.get('icons/close.svg'), mode=QIcon.Normal, state=QIcon.Off) close_icon.addFile(Resources.get('icons/close-active.svg'), mode=QIcon.Active, state=QIcon.Off) self.scale_action.setIcon(scale_icon) self.viewonly_action.setIcon(viewonly_icon) self.screenshot_action.setIcon(screenshot_icon) self.fullscreen_action.setIcon(fullscreen_icon) self.minimize_action.setIcon(minimize_icon) self.close_action.setIcon(close_icon) self.scale_button.setIcon(scale_icon) self.viewonly_button.setIcon(viewonly_icon) self.screenshot_button.setIcon(screenshot_icon) self.fullscreen_button.setIcon(fullscreen_icon) self.minimize_button.setIcon(minimize_icon) self.close_button.setIcon(close_icon) self.scale_button.setDefaultAction(self.scale_action) self.viewonly_button.setDefaultAction(self.viewonly_action) self.screenshot_button.setDefaultAction(self.screenshot_action) self.fullscreen_button.setDefaultAction(self.fullscreen_action) self.minimize_button.setDefaultAction(self.minimize_action) self.close_button.setDefaultAction(self.close_action) self.color_depth_button.clear() self.color_depth_button.addItem('Default Color Depth', ServerDefault) self.color_depth_button.addItem('TrueColor (24 bits)', TrueColor) self.color_depth_button.addItem('HighColor (16 bits)', HighColor) self.color_depth_button.addItem('LowColor (8 bits)', LowColor) def eventFilter(self, watched, event): if watched is self.parent() and event.type() == QEvent.Resize: new_x = (watched.width() - self.width()) / 2 self.move(new_x, self.y()) self.animation.setStartValue( QPoint(new_x, -self.height() + self.exposedPixels)) self.animation.setEndValue(QPoint(new_x, 0)) return False def enterEvent(self, event): super(ScreensharingToolbox, self).enterEvent(event) self.retract_timer.stop() self.expose() def leaveEvent(self, event): super(ScreensharingToolbox, self).leaveEvent(event) self.retract_timer.start() def paintEvent(self, event): # make the widget style aware option = QStyleOption() option.initFrom(self) painter = QStylePainter(self) painter.drawPrimitive(QStyle.PE_Widget, option) def expose(self): if self.animation.state( ) == QPropertyAnimation.Running and self.animation.direction( ) == QPropertyAnimation.Forward: return elif self.animation.state() == QPropertyAnimation.Stopped and self.pos( ) == self.animation.endValue(): return self.animation.setDirection(QPropertyAnimation.Forward) self.animation.start() def retract(self): if self.animation.state( ) == QPropertyAnimation.Running and self.animation.direction( ) == QPropertyAnimation.Backward: return elif self.animation.state() == QPropertyAnimation.Stopped and self.pos( ) == self.animation.startValue(): return self.animation.setDirection(QPropertyAnimation.Backward) self.animation.start()
class PlayingInterface(QWidget): """ 正在播放界面 """ nextSongSig = pyqtSignal() # 点击下一首或者上一首按钮时由主界面的播放列表决定下一首的Index lastSongSig = pyqtSignal() switchPlayStateSig = pyqtSignal() randomPlayAllSignal = pyqtSignal() removeMediaSignal = pyqtSignal(int) # 点击歌曲卡或者滑动歌曲信息卡滑槽时直接设置新的index,index由自己决定 currentIndexChanged = pyqtSignal(int) switchToAlbumInterfaceSig = pyqtSignal(str, str) # 发出进入最小模式的信号 smallestModeStateChanged = pyqtSignal(bool) # 退出全屏信号 exitFullScreenSig = pyqtSignal() def __init__(self, playlist: list = None, parent=None): super().__init__(parent) self.playlist = playlist.copy() self.currentIndex = 0 self.isPlaylistVisible = False # 创建小部件 self.blurPixmap = None self.blurBackgroundPic = QLabel(self) self.blurCoverThread = BlurCoverThread(self) self.songInfoCardChute = SongInfoCardChute(self, self.playlist) self.parallelAniGroup = QParallelAnimationGroup(self) self.songInfoCardChuteAni = QPropertyAnimation(self.songInfoCardChute, b"geometry") self.playBar = PlayBar(self) self.songListWidget = SongListWidget(self.playlist, self) self.smallestModeInterface = SmallestPlayModeInterface(playlist, self) self.playBarAni = QPropertyAnimation(self.playBar, b"geometry") self.songListWidgetAni = QPropertyAnimation(self.songListWidget, b"geometry") self.guideLabel = QLabel("在这里,你将看到正在播放的歌曲以及即将播放的歌曲。", self) self.randomPlayAllButton = ThreeStatePushButton( { "normal": r"app\resource\images\playing_interface\全部随机播放_normal.png", "hover": r"app\resource\images\playing_interface\全部随机播放_hover.png", "pressed": r"app\resource\images\playing_interface\全部随机播放_pressed.png", }, " 随机播放你收藏中的所有内容", (30, 22), self) # 创建定时器 self.showPlaylistTimer = QTimer(self) self.hidePlaylistTimer = QTimer(self) # 初始化 self.__initWidget() def __initWidget(self): """ 初始化小部件 """ self.resize(1100, 870) self.currentSmallestModeSize = QSize(340, 340) self.setAttribute(Qt.WA_StyledBackground) self.guideLabel.move(45, 62) self.randomPlayAllButton.move(45, 117) self.playBar.move(0, self.height() - self.playBar.height()) # 隐藏部件 self.smallestModeInterface.hide() self.randomPlayAllButton.hide() self.guideLabel.hide() self.playBar.hide() # 设置层叠样式 self.setObjectName("playingInterface") self.guideLabel.setObjectName("guideLabel") self.randomPlayAllButton.setObjectName("randomPlayAllButton") self.__setQss() # 开启磨砂线程 if self.playlist: self.startBlurThread( self.songInfoCardChute.curSongInfoCard.albumCoverPath) # 将信号连接到槽 self.__connectSignalToSlot() # 初始化动画 self.playBarAni.setDuration(350) self.songListWidgetAni.setDuration(350) self.songListWidgetAni.setEasingCurve(QEasingCurve.InOutQuad) self.playBarAni.setEasingCurve(QEasingCurve.InOutQuad) self.parallelAniGroup.addAnimation(self.playBarAni) self.parallelAniGroup.addAnimation(self.songInfoCardChuteAni) # 初始化定时器 self.showPlaylistTimer.setInterval(120) self.hidePlaylistTimer.setInterval(120) self.showPlaylistTimer.timeout.connect(self.showPlayListTimerSlot) self.hidePlaylistTimer.timeout.connect(self.hidePlayListTimerSlot) def __setQss(self): """ 设置层叠样式 """ with open(r"app\resource\css\playInterface.qss", encoding="utf-8") as f: self.setStyleSheet(f.read()) def setBlurPixmap(self, blurPixmap): """ 设置磨砂pixmap """ self.blurPixmap = blurPixmap # 更新背景 self.__resizeBlurPixmap() def __resizeBlurPixmap(self): """ 调整背景图尺寸 """ maxWidth = max(self.width(), self.height()) if self.blurPixmap: self.blurBackgroundPic.setPixmap( self.blurPixmap.scaled( maxWidth, maxWidth, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation, )) def startBlurThread(self, albumCoverPath): """ 开启磨砂线程 """ blurRadius = [6, 40][self.smallestModeInterface.isVisible()] self.blurCoverThread.setTargetCover(albumCoverPath, blurRadius=blurRadius) self.blurCoverThread.start() def resizeEvent(self, e): """ 改变尺寸时也改变小部件的大小 """ super().resizeEvent(e) self.__resizeBlurPixmap() self.songInfoCardChute.resize(self.size()) self.blurBackgroundPic.setFixedSize(self.size()) self.playBar.resize(self.width(), self.playBar.height()) self.songListWidget.resize(self.width(), self.height() - 382) self.smallestModeInterface.resize(self.size()) if self.isPlaylistVisible: self.playBar.move(0, 190) self.songListWidget.move(0, 382) self.songInfoCardChute.move(0, 258 - self.height()) else: self.playBar.move(0, self.height() - self.playBar.height()) self.songListWidget.move(0, self.height()) def showPlayBar(self): """ 显示播放栏 """ # 只在播放栏不可见的时候显示播放栏和开启动画 if not self.playBar.isVisible(): self.playBar.show() self.songInfoCardChuteAni.setDuration(450) self.songInfoCardChuteAni.setEasingCurve(QEasingCurve.OutCubic) self.songInfoCardChuteAni.setStartValue( self.songInfoCardChute.rect()) self.songInfoCardChuteAni.setEndValue( QRect(0, -self.playBar.height() + 68, self.width(), self.height())) self.songInfoCardChuteAni.start() def hidePlayBar(self): """ 隐藏播放栏 """ if self.playBar.isVisible() and not self.isPlaylistVisible: self.playBar.hide() self.songInfoCardChuteAni.setEasingCurve(QEasingCurve.OutCirc) self.songInfoCardChuteAni.setStartValue( QRect(0, -self.playBar.height() + 68, self.width(), self.height())) self.songInfoCardChuteAni.setEndValue( QRect(0, 0, self.width(), self.height())) self.songInfoCardChuteAni.start() def showPlaylist(self): """ 显示播放列表 """ if self.songListWidgetAni.state() != QAbstractAnimation.Running: self.songInfoCardChuteAni.setDuration(350) self.songInfoCardChuteAni.setEasingCurve(QEasingCurve.InOutQuad) self.songInfoCardChuteAni.setStartValue( QRect(0, self.songInfoCardChute.y(), self.width(), self.height())) self.songInfoCardChuteAni.setEndValue( QRect(0, 258 - self.height(), self.width(), self.height())) self.playBarAni.setStartValue( QRect(0, self.playBar.y(), self.width(), self.playBar.height())) self.playBarAni.setEndValue( QRect(0, 190, self.width(), self.playBar.height())) self.songListWidgetAni.setStartValue( QRect( self.songListWidget.x(), self.songListWidget.y(), self.songListWidget.width(), self.songListWidget.height(), )) self.songListWidgetAni.setEndValue( QRect( self.songListWidget.x(), 382, self.songListWidget.width(), self.songListWidget.height(), )) if self.sender() == self.playBar.showPlaylistButton: self.playBar.pullUpArrowButton.timer.start() self.playBar.show() self.parallelAniGroup.start() self.blurBackgroundPic.hide() self.showPlaylistTimer.start() def showPlayListTimerSlot(self): """ 显示播放列表定时器溢出槽函数 """ self.showPlaylistTimer.stop() self.songListWidgetAni.start() self.isPlaylistVisible = True def hidePlayListTimerSlot(self): """ 显示播放列表定时器溢出槽函数 """ self.hidePlaylistTimer.stop() self.parallelAniGroup.start() def hidePlaylist(self): """ 隐藏播放列表 """ if self.parallelAniGroup.state() != QAbstractAnimation.Running: self.songInfoCardChuteAni.setDuration(350) self.songInfoCardChuteAni.setEasingCurve(QEasingCurve.InOutQuad) self.songInfoCardChuteAni.setStartValue( QRect(0, self.songInfoCardChute.y(), self.width(), self.height())) self.songInfoCardChuteAni.setEndValue( QRect(0, -self.playBar.height() + 68, self.width(), self.height())) self.playBarAni.setStartValue( QRect(0, 190, self.width(), self.playBar.height())) self.playBarAni.setEndValue( QRect( 0, self.height() - self.playBar.height(), self.width(), self.playBar.height(), )) self.songListWidgetAni.setStartValue( QRect( self.songListWidget.x(), self.songListWidget.y(), self.songListWidget.width(), self.songListWidget.height(), )) self.songListWidgetAni.setEndValue( QRect( self.songListWidget.x(), self.height(), self.songListWidget.width(), self.songListWidget.height(), )) if self.sender() == self.playBar.showPlaylistButton: self.playBar.pullUpArrowButton.timer.start() # self.parallelAniGroup.start() self.songListWidgetAni.start() self.hidePlaylistTimer.start() self.blurBackgroundPic.show() self.isPlaylistVisible = False def showPlaylistButtonSlot(self): """ 显示或隐藏播放列表 """ if not self.isPlaylistVisible: self.showPlaylist() else: self.hidePlaylist() def setCurrentIndex(self, index): """ 更新播放列表下标 """ # 下标大于等于0时才更新 if self.currentIndex != index and index > -1: # 在播放列表的最后一首歌被移除时不更新样式 if index >= len(self.playlist): return if self.smallestModeInterface.isVisible(): self.smallestModeInterface.setCurrentIndex(index) self.currentIndex = index self.songListWidget.setCurrentIndex(index) self.songInfoCardChute.setCurrentIndex(index) def setPlaylist(self, playlist: list, isResetIndex: bool = True): """ 更新播放列表 Parameters ---------- playlist: list 播放列表,每一个元素都是songInfo字典 isResetIndex: bool 是否将下标重置为0 """ self.playlist = deepcopy(playlist) self.currentIndex = 0 if isResetIndex else self.currentIndex if playlist: self.songInfoCardChute.setPlaylist(self.playlist, isResetIndex) self.smallestModeInterface.setPlaylist(self.playlist, isResetIndex) self.songListWidget.updateSongCards(self.playlist) # 如果小部件不可见就显示 if playlist and not self.songListWidget.isVisible(): self.__setGuideLabelHidden(True) def __settleDownPlayBar(self): """ 定住播放栏 """ self.songInfoCardChute.stopSongInfoCardTimer() def __startSongInfoCardTimer(self): """ 重新打开歌曲信息卡的定时器 """ if not self.playBar.volumeSlider.isVisible(): # 只有音量滑动条不可见才打开计时器 self.songInfoCardChute.startSongInfoCardTimer() def __songListWidgetCurrentChangedSlot(self, index): """ 歌曲列表当前下标改变插槽 """ self.currentIndex = index self.songInfoCardChute.setCurrentIndex(index) self.currentIndexChanged.emit(index) def __songInfoCardChuteCurrentChangedSlot(self, index): """ 歌曲列表当前下标改变插槽 """ self.currentIndex = index self.songListWidget.setCurrentIndex(index) self.currentIndexChanged.emit(index) def __removeSongFromPlaylist(self, index): """ 从播放列表中移除选中的歌曲 """ lastSongRemoved = False if self.currentIndex > index: self.currentIndex -= 1 self.songInfoCardChute.currentIndex -= 1 elif self.currentIndex == index: # 如果被移除的是最后一首需要将当前下标-1 if index == self.songListWidget.currentIndex + 1: self.currentIndex -= 1 self.songInfoCardChute.currentIndex -= 1 lastSongRemoved = True else: self.songInfoCardChute.setCurrentIndex(self.currentIndex) self.removeMediaSignal.emit(index) # 如果播放列表为空,隐藏小部件 if len(self.playlist) == 0: self.__setGuideLabelHidden(False) # 如果被移除的是最后一首就将当前播放歌曲置为被移除后的播放列表最后一首 """ if lastSongRemoved: self.currentIndexChanged.emit(self.currentIndex) """ def clearPlaylist(self): """ 清空歌曲卡 """ self.playlist.clear() self.songListWidget.clearSongCards() # 显示随机播放所有按钮 self.__setGuideLabelHidden(False) def __setGuideLabelHidden(self, isHidden): """ 设置导航标签和随机播放所有按钮的可见性 """ self.randomPlayAllButton.setHidden(isHidden) self.guideLabel.setHidden(isHidden) self.songListWidget.setHidden(not isHidden) if isHidden: # 隐藏导航标签时根据播放列表是否可见设置磨砂背景和播放栏的可见性 self.blurBackgroundPic.setHidden(self.isPlaylistVisible) self.playBar.setHidden(not self.isPlaylistVisible) else: # 显示导航标签时隐藏磨砂背景 self.blurBackgroundPic.hide() self.playBar.hide() # 最后再显示歌曲信息卡 self.songInfoCardChute.setHidden(not isHidden) def updateOneSongCard(self, oldSongInfo: dict, newSongInfo): """ 更新一个歌曲卡 """ self.songListWidget.updateOneSongCard(oldSongInfo, newSongInfo) self.playlist = self.songListWidget.playlist self.songInfoCardChute.playlist = self.playlist def updateMultiSongCards(self, oldSongInfo_list: list, newSongInfo_list: list): """ 更新多个歌曲卡 """ self.songListWidget.updateMultiSongCards(oldSongInfo_list, newSongInfo_list) self.playlist = self.songListWidget.playlist self.songInfoCardChute.playlist = self.playlist def showSmallestModeInterface(self): """ 显示最小播放模式界面 """ self.exitFullScreenSig.emit() # 记录下正常尺寸 self.currentGeometry = self.window().geometry() # type:QRect # 更新磨砂半径 self.blurCoverThread.setTargetCover( self.blurCoverThread.albumCoverPath, 40, (350, 350)) self.blurCoverThread.start() self.playBar.hide() self.songListWidget.hide() self.songInfoCardChute.hide() self.blurBackgroundPic.show() # 先更新歌曲信息卡再显示界面 self.smallestModeInterface.setCurrentIndex(self.currentIndex) self.smallestModeInterface.show() # 发出隐藏标题栏按钮的信号 self.smallestModeStateChanged.emit(True) self.window().setMinimumSize(206, 197) self.window().setGeometry( self.currentGeometry.x() + self.currentGeometry.width() - self.currentSmallestModeSize.width(), self.currentGeometry.y(), self.currentSmallestModeSize.width(), self.currentSmallestModeSize.height(), ) def __hideSmallestModeInterface(self): """ 隐藏最小播放模式界面 """ # 记录下最小播放模式的尺寸 self.currentSmallestModeSize = self.window().size() # type:QSize # 更新磨砂半径 self.blurCoverThread.setTargetCover( self.blurCoverThread.albumCoverPath, 6, (450, 450)) self.blurCoverThread.start() self.smallestModeInterface.hide() self.window().setMinimumSize(1030, 850) self.window().setGeometry(self.currentGeometry) # 发出显示标题栏按钮的信号 self.smallestModeStateChanged.emit(False) self.blurBackgroundPic.setHidden(self.isPlaylistVisible) self.playBar.show() self.songListWidget.show() self.songInfoCardChute.show() def __connectSignalToSlot(self): """ 将信号连接到槽 """ self.blurCoverThread.blurDone.connect(self.setBlurPixmap) # 更新背景封面和下标 self.songInfoCardChute.currentIndexChanged[int].connect( self.__songInfoCardChuteCurrentChangedSlot) self.songInfoCardChute.currentIndexChanged[str].connect( self.startBlurThread) # 显示和隐藏播放栏 self.songInfoCardChute.showPlayBarSignal.connect(self.showPlayBar) self.songInfoCardChute.hidePlayBarSignal.connect(self.hidePlayBar) # 将播放栏的信号连接到槽 self.playBar.lastSongButton.clicked.connect(self.lastSongSig) self.playBar.nextSongButton.clicked.connect(self.nextSongSig) self.playBar.playButton.clicked.connect(self.switchPlayStateSig) self.playBar.pullUpArrowButton.clicked.connect( self.showPlaylistButtonSlot) self.playBar.showPlaylistButton.clicked.connect( self.showPlaylistButtonSlot) self.playBar.smallPlayModeButton.clicked.connect( self.showSmallestModeInterface) self.playBar.enterSignal.connect(self.__settleDownPlayBar) self.playBar.leaveSignal.connect(self.__startSongInfoCardTimer) # 将歌曲列表的信号连接到槽函数 self.songListWidget.currentIndexChanged.connect( self.__songListWidgetCurrentChangedSlot) self.songListWidget.removeItemSignal.connect( self.__removeSongFromPlaylist) self.randomPlayAllButton.clicked.connect(self.randomPlayAllSignal) # 将最小化播放界面的信号连接到槽函数 self.smallestModeInterface.lastSongButton.clicked.connect( self.lastSongSig) self.smallestModeInterface.nextSongButton.clicked.connect( self.nextSongSig) self.smallestModeInterface.playButton.clicked.connect( self.switchPlayStateSig) self.smallestModeInterface.exitSmallestModeButton.clicked.connect( self.__hideSmallestModeInterface) # 切换到专辑界面 self.songInfoCardChute.switchToAlbumInterfaceSig.connect( self.switchToAlbumInterfaceSig) self.songListWidget.switchToAlbumInterfaceSig.connect( self.switchToAlbumInterfaceSig)
class SubPlayWindow(QWidget): """ 桌面左上角子播放窗口 """ def __init__(self, parent=None, songInfo: dict = None): super().__init__(parent) self.songInfo = {} if songInfo: self.songInfo = songInfo self.__lastSongIconPath = { 'normal': r'resource\images\sub_play_window\lastSong_50_50_normal.png', 'hover': r'resource\images\sub_play_window\lastSong_50_50_hover.png', 'pressed': r'resource\images\sub_play_window\lastSong_50_50_pressed.png' } self.__nextSongIconPath = { 'normal': r'resource\images\sub_play_window\nextSong_50_50_normal.png', 'hover': r'resource\images\sub_play_window\nextSong_50_50_hover.png', 'pressed': r'resource\images\sub_play_window\nextSong_50_50_pressed.png' } # 创建小部件 self.volumeSlider = Slider(Qt.Vertical, self) self.volumeLabel = QLabel(self) self.lastSongButton = ThreeStateButton(self.__lastSongIconPath, self, (50, 50)) self.playButton = PlayButton(self) self.nextSongButton = ThreeStateButton(self.__nextSongIconPath, self, (50, 50)) self.albumPic = QLabel(self) self.songNameLabel = QLabel(self) self.songerNameLabel = QLabel(self) self.ani = QPropertyAnimation(self, b'windowOpacity') self.timer = QTimer(self) # 系统音量控制类 self.systemVolume = SystemVolume() # 初始化 self.__initWidget() def __initWidget(self): """ 初始化小部件 """ self.resize(635, 175) self.__initLayout() self.setAttribute(Qt.WA_TranslucentBackground) self.setWindowFlags(Qt.FramelessWindowHint | Qt.Window | Qt.WindowStaysOnTopHint) # 初始化音量滑块 self.volumeSlider.setRange(0, 100) self.volumeSlider.setSingleStep(1) # 初始化动画和定时器 self.timer.setInterval(3000) self.ani.setEasingCurve(QEasingCurve.Linear) self.ani.setDuration(700) self.ani.setStartValue(1) self.ani.setEndValue(0) # 分配ID self.volumeLabel.setObjectName('volumeLabel') self.songNameLabel.setObjectName('songNameLabel') self.songerNameLabel.setObjectName('songerNameLabel') # 设置层叠样式 self.__setQss() # 将信号连接到槽 self.__connectSignalToSlot() self.volumeSlider.setValue(self.systemVolume.getVolume()) # 设置封面和标签 self.updateWindow(self.songInfo) # 引用方法 self.setPlay = self.playButton.setPlay def __initLayout(self): """ 初始化布局 """ self.move(62, 75) self.albumPic.move(478, 25) self.playButton.move(222, 26) self.volumeLabel.move(32, 140) self.volumeSlider.move(34, 25) self.songNameLabel.move(122, 93) self.lastSongButton.move(122, 26) self.nextSongButton.move(322, 26) self.songerNameLabel.move(122, 135) self.albumPic.setFixedSize(125, 125) self.songNameLabel.setFixedWidth(285) self.songerNameLabel.setFixedWidth(290) self.volumeLabel.setFixedWidth(65) def updateWindow(self, songInfo: dict): """ 设置窗口内容 """ self.songInfo = songInfo self.songName = self.songInfo.get('songName', '未知歌曲') self.songerName = self.songInfo.get('songer', '未知歌手') # 调整长度 self.__adjustText() # 更新标签和专辑封面 self.songNameLabel.setText(self.songName) self.songerNameLabel.setText(self.songerName) self.__setAlbumCover() def timerSlot(self): """ 定时器溢出时间 """ self.timer.stop() self.ani.start() def enterEvent(self, e): """ 鼠标进入时停止动画并重置定时器 """ self.timer.stop() if self.ani.state() == QAbstractAnimation.Running: self.ani.stop() self.setWindowOpacity(1) def leaveEvent(self, e): """ 鼠标离开窗口时打开计时器 """ # 判断事件发生的位置发生在自己所占的rect内 notLeave = isNotLeave(self) if not notLeave: self.timer.start() def show(self): """ show()时重置透明度并根据鼠标位置决定是否打开计时器 """ self.setWindowOpacity(1) self.volumeSlider.setValue(self.systemVolume.getVolume()) super().show() notLeave = isNotLeave(self) if not notLeave: self.timer.start() def paintEvent(self, e): """ 绘制背景色 """ painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing) painter.setPen(Qt.NoPen) brush = QBrush(Qt.black) painter.setBrush(brush) # 绘制音量滑动条的背景 painter.drawRect(0, 0, 81, 175) # 绘制控制面板的背景 painter.drawRect(86, 0, 549, 175) def __connectSignalToSlot(self): """ 将信号连接到槽 """ self.ani.finished.connect(self.hide) self.timer.timeout.connect(self.timerSlot) self.volumeSlider.valueChanged.connect(self.sliderValueChangedSlot) def __setQss(self): """ 设置层叠样式 """ with open(r'resource\css\subPlayWindow.qss', encoding='utf-8') as f: self.setStyleSheet(f.read()) def __setAlbumCover(self): """ 设置封面 """ # 如果专辑信息为空就直接隐藏 self.coverPath = getCoverPath(self.songInfo.get('modifiedAlbum')) self.albumPic.setPixmap( QPixmap(self.coverPath).scaled(125, 125, Qt.KeepAspectRatio, Qt.SmoothTransformation)) def __adjustText(self): """ 根据文本长度决定是否显示省略号 """ fontMetrics_1 = QFontMetrics(QFont('Microsoft YaHei', 17, 63)) self.songName = fontMetrics_1.elidedText(self.songName, Qt.ElideRight, 285) fontMetrics_2 = QFontMetrics(QFont('Microsoft YaHei', 9)) self.songerName = fontMetrics_2.elidedText(self.songerName, Qt.ElideRight, 290) def __adjustVolumeLabelPos(self): """ 调整音量标签的位置 """ if 10 <= int(self.volumeLabel.text()) <= 99: self.volumeLabel.move(32, 140) elif 0 <= int(self.volumeLabel.text()) <= 9: self.volumeLabel.move(37, 140) else: self.volumeLabel.move(27, 140) def sliderValueChangedSlot(self, value): """ 音量改变时调整系统音量并更新标签 """ self.volumeLabel.setText(str(value)) self.__adjustVolumeLabelPos() self.systemVolume.setVolume(value)
class QSlideSwitchPrivate(QObject): def __init__(self, q): QObject.__init__(self) self._position = 0 self._sliderShape = QRectF() self._gradient = QLinearGradient() self._gradient.setSpread(QGradient.PadSpread) self._qPointer = q self.animation = QPropertyAnimation(self, b"position") self.animation.setTargetObject(self) # self.animation.setPropertyName() self.animation.setStartValue(0) self.animation.setEndValue(1) self.animation.setDuration(300) self.animation.setEasingCurve(QEasingCurve.InOutExpo) def __del__(self): del self.animation @pyqtProperty(float) def position(self): return self._position @position.setter def position(self, value): self._position = value self._qPointer.repaint() def drawSlider(self, painter): margin = 3 r = self._qPointer.rect().adjusted(0, 0, -1, -1) dx = (r.width() - self._sliderShape.width()) * self._position sliderRect = self._sliderShape.translated(dx, 0) painter.setPen(Qt.NoPen) # basic settings shadow = self._qPointer.palette().color(QPalette.Dark) light = self._qPointer.palette().color(QPalette.Light) button = self._qPointer.palette().color(QPalette.Button) # draw background # draw outer background self._gradient.setColorAt(0, shadow.darker(130)) self._gradient.setColorAt(1, light.darker(130)) self._gradient.setStart(0, r.height()) self._gradient.setFinalStop(0, 0) painter.setBrush(self._gradient) painter.drawRoundedRect(r, 15, 15) # draw background # draw inner background self._gradient.setColorAt(0, shadow.darker(140)) self._gradient.setColorAt(1, light.darker(160)) self._gradient.setStart(0, 0) self._gradient.setFinalStop(0, r.height()) painter.setBrush(self._gradient) painter.drawRoundedRect(r.adjusted(margin, margin, -margin, -margin), 15, 15) # draw slider self._gradient.setColorAt(0, button.darker(130)) self._gradient.setColorAt(1, button) # draw outer slider self._gradient.setStart(0, r.height()) self._gradient.setFinalStop(0, 0) painter.setBrush(self._gradient) painter.drawRoundedRect(sliderRect.adjusted(margin, margin, -margin, -margin), 10, 15) # draw inner slider self._gradient.setStart(0, 0) self._gradient.setFinalStop(0, r.height()) painter.setBrush(self._gradient) painter.drawRoundedRect(sliderRect.adjusted(2.5 * margin, 2.5 * margin, -2.5 * margin, - 2.5 * margin), 5, 15) # draw text if self.animation.state() == QPropertyAnimation.Running: return # don't draw any text while animation is running font = self._qPointer.font() self._gradient.setColorAt(0, light) self._gradient.setColorAt(1, shadow) self._gradient.setStart(0, r.height() / 2.0 + font.pointSizeF()) self._gradient.setFinalStop(0, r.height() / 2.0 - font.pointSizeF()) painter.setFont(font) painter.setPen(QPen(QBrush(self._gradient), 0)) if self._qPointer.isChecked(): painter.drawText(0, 0, r.width() / 2, r.height() - 1, Qt.AlignCenter, "ON") else: painter.drawText(r.width() / 2, 0, r.width() / 2, r.height() - 1, Qt.AlignCenter, "OFF") def updateSliderRect(self, size): self._sliderShape.setWidth(size.width() / 2.0) self._sliderShape.setHeight(size.height() - 1.0) @pyqtSlot(bool, name='animate') def animate(self, checked): self.animation.setDirection(QPropertyAnimation.Forward if checked else QPropertyAnimation.Backward) self.animation.start()
class Screen(QWidget): card_w = 200 card_h = 250 card_start_x = SCREEN_X / 2 - card_w / 2 card_start_y = SCREEN_Y / 2 - card_h / 2 card_pause_x = SCREEN_X / 2 - card_w / 2 card_pause_y = 0.4275 * SCREEN_Y - card_h / 2 card_pause2_x = SCREEN_X / 2 - card_w / 2 card_pause2_y = 0.4225 * SCREEN_Y - card_h / 2 card_pause3_x = SCREEN_X / 2 - card_w / 2 card_pause3_y = 0.4175 * SCREEN_Y - card_h / 2 card_end_x = SCREEN_X / 2 - card_w / 2 card_end_y = SCREEN_Y / 6 - card_h / 2 speed = 1000 def __init__(self): super().__init__() self.initUI() def initUI(self): self.initButtons() self.printerback = QLabel(self) self.printerback.setStyleSheet( "QLabel {border-image: url(./images/printer-back.png) 0 0 0 0 stretch stretch}" ) self.printerback.setGeometry( QRect(SCREEN_X / 2 - SCREEN_X / 4.2, SCREEN_Y / 2 - SCREEN_Y / 2.625, SCREEN_X / 2.1, SCREEN_Y / 1.3125)) self.label = PrintCard(self, self.card_start_x, self.card_start_y, self.card_w, self.card_h) self.label.setText(self.chooseWord()) self.printerfront = QLabel(self) self.printerfront.setStyleSheet( "QLabel {border-image: url(./images/printer-front.png) 0 0 0 0 stretch stretch}" ) self.printerfront.setGeometry( QRect(SCREEN_X / 2 - SCREEN_X / 4.2, SCREEN_Y / 2 - SCREEN_Y / 2.625, SCREEN_X / 2.1, SCREEN_Y / 1.3125)) self.gameover = QLabel(self) self.gameover.setVisible(False) self.gameover.setText("Game Over!") self.gameover.setStyleSheet("font-size: 30pt") self.gameover.setGeometry( QRect(SCREEN_X / 6 - SCREEN_X / 16.8, SCREEN_Y / 2, SCREEN_X / 8.4, SCREEN_Y / 25)) self.anim = QPropertyAnimation(self.label, b"geometry") self.anim.setDuration(self.speed) self.anim.setStartValue( QRect(self.label.x(), self.label.y(), self.label.card_w, self.label.card_h)) self.anim.setEndValue( QRect(self.card_pause_x, self.card_pause_y, self.label.card_w, self.label.card_h)) self.anim2 = QPropertyAnimation(self.label, b"geometry") self.anim2.setDuration(200) self.anim2.setEndValue( QRect(self.card_pause2_x, self.card_pause2_y, self.label.card_w, self.label.card_h)) self.anim3 = QPropertyAnimation(self.label, b"geometry") self.anim3.setDuration(200) self.anim3.setEndValue( QRect(self.card_pause3_x, self.card_pause3_y, self.label.card_w, self.label.card_h)) self.animFinish = QPropertyAnimation(self.label, b"geometry") self.animFinish.setDuration(self.speed) self.animFinish.setEndValue( QRect(self.card_end_x, self.card_end_y, self.label.card_w, self.label.card_h)) self.startbutton.clicked.connect(self.startButtonClicked) self.randombutton.clicked.connect(self.randomButtonClicked) self.printbutton.clicked.connect(self.printButtonClicked) self.print2button.clicked.connect(self.print2ButtonClicked) self.print3button.clicked.connect(self.print3ButtonClicked) self.finishprintbutton.clicked.connect(self.finishprintButtonClicked) self.reprintbutton.clicked.connect(self.reprintButtonClicked) self.nextbutton.clicked.connect(self.nextButtonClicked) self.restartbutton.clicked.connect(self.restartButtonClicked) self.exitbutton.clicked.connect(self.exit) self.animFinish.finished.connect(self.endAnimation) self.setWindowTitle("Printer") self.setGeometry(0, 0, SCREEN_X, SCREEN_Y) self.show() def initButtons(self): self.startbutton = PrinterButton("Start", self, width=SCREEN_X / 8.4, height=SCREEN_Y / 25, xPos=SCREEN_X / 6 - SCREEN_X / 16.8, yPos=SCREEN_Y / 2, font_size="20pt", background_color="#77eb34", background_color_pressed="#449615", border_radius="8px") self.randombutton = PrinterButton("Randomize Words", self, width=SCREEN_X / 8.4, height=SCREEN_Y / 25, xPos=SCREEN_X / 6 - SCREEN_X / 16.8, yPos=SCREEN_Y / 2 - SCREEN_Y / 25 - 15, font_size="20pt", background_color="#f49eff", background_color_pressed="#a843cc", border_radius="8px") self.printbutton = PrinterButton("Print", self, visible=False, width=SCREEN_X / 8.4, height=SCREEN_Y / 25, xPos=SCREEN_X / 6 - SCREEN_X / 16.8, yPos=SCREEN_Y / 2, font_size="20pt", background_color="#77eb34", background_color_pressed="#449615", border_radius="8px") self.print2button = PrinterButton("Print More", self, visible=False, width=SCREEN_X / 8.4, height=SCREEN_Y / 25, xPos=SCREEN_X / 6 - SCREEN_X / 16.8, yPos=SCREEN_Y / 2 + SCREEN_Y / 25 + 15, font_size="20pt", background_color="#77eb34", background_color_pressed="#449615", border_radius="8px") self.print3button = PrinterButton("Print More", self, visible=False, width=SCREEN_X / 8.4, height=SCREEN_Y / 25, xPos=SCREEN_X / 6 - SCREEN_X / 16.8, yPos=SCREEN_Y / 2 + SCREEN_Y / 25 + 15, font_size="20pt", background_color="#77eb34", background_color_pressed="#449615", border_radius="8px") self.finishprintbutton = PrinterButton( "Finish Printing", self, visible=False, width=SCREEN_X / 8.4, height=SCREEN_Y / 25, xPos=SCREEN_X / 6 - SCREEN_X / 16.8, yPos=SCREEN_Y / 2, font_size="20pt", background_color="#ff9717", background_color_pressed="#8f4f00", border_radius="8px") self.reprintbutton = PrinterButton("Print Again", self, visible=False, width=SCREEN_X / 8.4, height=SCREEN_Y / 25, xPos=SCREEN_X / 6 - SCREEN_X / 16.8, yPos=SCREEN_Y / 2 + SCREEN_Y / 25 + 15, font_size="20pt", background_color="#77eb34", background_color_pressed="#449615", border_radius="8px") self.nextbutton = PrinterButton("Next Page", self, visible=False, width=SCREEN_X / 8.4, height=SCREEN_Y / 25, xPos=SCREEN_X / 6 - SCREEN_X / 16.8, yPos=SCREEN_Y / 2, font_size="20pt", background_color="#f49eff", background_color_pressed="#a843cc", border_radius="8px") self.restartbutton = PrinterButton("Restart", self, visible=False, width=SCREEN_X / 8.4, height=SCREEN_Y / 25, xPos=SCREEN_X / 6 - SCREEN_X / 16.8, yPos=SCREEN_Y / 2 - SCREEN_Y / 25 - 15, font_size="20pt", background_color="#30f5ff", background_color_pressed="17a6ff", border_radius="8px") self.exitbutton = PrinterButton("Exit", self, width=SCREEN_X / 8.4, height=SCREEN_Y / 25, xPos=SCREEN_X / 6 - SCREEN_X / 16.8, yPos=8 * SCREEN_Y / 10 - SCREEN_Y / 50, font_size="20pt", background_color="#fc3003", background_color_pressed="a11216", border_radius="8px") def startButtonClicked(self): self.startbutton.setVisible(False) self.randombutton.setVisible(False) self.printbutton.setVisible(True) self.restartbutton.setVisible(True) def randomButtonClicked(self): global WORDS WORDS = read_words.shuffle(WORDS) self.restartButtonClicked() def printButtonClicked(self): self.anim.start() self.printbutton.setVisible(False) self.print2button.setVisible(True) self.finishprintbutton.setVisible(True) def print2ButtonClicked(self): if self.anim.state() == 2: return self.anim2.setStartValue( QRect(self.label.x(), self.label.y(), self.label.card_w, self.label.card_h)) self.anim2.start() self.print2button.setVisible(False) self.print3button.setVisible(True) def print3ButtonClicked(self): if self.anim2.state() == 2: return self.anim3.setStartValue( QRect(self.label.x(), self.label.y(), self.label.card_w, self.label.card_h)) self.anim3.start() self.print3button.setVisible(False) def finishprintButtonClicked(self): if (self.anim.state() == 2 or self.anim2.state() == 2 or self.anim3.state() == 2): return self.animFinish.setStartValue( QRect(self.label.x(), self.label.y(), self.label.card_w, self.label.card_h)) self.animFinish.start() self.printbutton.setVisible(False) self.print2button.setVisible(False) self.print3button.setVisible(False) self.finishprintbutton.setVisible(False) self.nextbutton.setVisible(True) self.reprintbutton.setVisible(True) def reprintButtonClicked(self): if self.animFinish.state() == 2: return global INDEX INDEX -= 1 self.nextButtonClicked() def nextButtonClicked(self): if self.animFinish.state() == 2: return if INDEX >= len(WORDS): self.restartbutton.setVisible(True) self.reprintbutton.setVisible(False) self.nextbutton.setVisible(False) self.gameover.setVisible(True) return self.resetUI() def restartButtonClicked(self): if self.anim.state() == 2: return global INDEX INDEX = 0 self.resetUI(main_menu=True) def endAnimation(self): self.printbutton.setVisible(False) self.finishprintbutton.setVisible(False) self.nextbutton.setVisible(True) self.reprintbutton.setVisible(True) def chooseWord(self): global INDEX word = WORDS[INDEX] INDEX += 1 return word def resetUI(self, main_menu=False): if main_menu: self.startbutton.setVisible(True) self.randombutton.setVisible(True) self.printbutton.setVisible(False) self.restartbutton.setVisible(False) else: self.startbutton.setVisible(False) self.randombutton.setVisible(False) self.printbutton.setVisible(True) self.restartbutton.setVisible(True) self.gameover.setVisible(False) self.anim.setCurrentTime(0) self.animFinish.setCurrentTime(0) self.reprintbutton.setVisible(False) self.nextbutton.setVisible(False) self.label.setText(self.chooseWord()) self.label.move(self.card_start_x, self.card_start_y) def exit(self): sys.exit(0)
class SideSlideAnimator(AbstractAnimator): m_side: ApplicationSide m_decorateBackground: bool m_decorator: SideSlideDecorator m_animation: QPropertyAnimation def __init__(self, _widgetForSlide): super(SideSlideAnimator, self).__init__(_widgetForSlide) self.m_side = ApplicationSide() self.m_decorateBackground = True self.m_decorator = SideSlideDecorator(_widgetForSlide.parentWidget()) self.m_animation = QPropertyAnimation(self.m_decorator, b"_slidePos") _widgetForSlide.parentWidget().installEventFilter(self) self.m_animation.setEasingCurve(QEasingCurve.InQuad) self.m_animation.setDuration(260) self.m_decorator.hide() def finished(): self.setAnimatedStopped() if self.isAnimatedForward(): self.widgetForSlide().move(self.m_decorator.slidePos()) else: self.m_decorator.hide() self.m_animation.finished.connect(finished) self.m_decorator.clicked.connect(self.slideOut) def setApplicationSide(self, _side): if self.m_side != _side: self.m_side = _side def setDecorateBackground(self, _decorate): if self.m_decorateBackground != _decorate: self.m_decorateBackground = _decorate def animationDuration(self): return self.m_animation.duration() def animateForward(self): self.slideIn() def slideIn(self): if self.isAnimated() and self.isAnimatedForward(): return self.setAnimatedForward() self.widgetForSlide().lower() self.widgetForSlide().move(-self.widgetForSlide().width(), -self.widgetForSlide().height()) self.widgetForSlide().show() self.widgetForSlide().resize(self.widgetForSlide().sizeHint()) _topWidget = self.widgetForSlide() topWidget = self.widgetForSlide() while _topWidget: topWidget = _topWidget _topWidget = topWidget.parentWidget() finalSize = self.widgetForSlide().size() startPosition = QPoint() finalPosition = QPoint() if self.m_side == ApplicationSide.LeftSide: finalSize.setHeight(topWidget.height()) startPosition = QPoint(-finalSize.width(), 0) finalPosition = QPoint(0, 0) if self.m_side == ApplicationSide.TopSide: finalSize.setWidth(topWidget.width()) startPosition = QPoint(0, -finalSize.height()) finalPosition = QPoint(0, 0) if self.m_side == ApplicationSide.RightSide: finalSize.setHeight(topWidget.height()) startPosition = QPoint(topWidget.width(), 0) finalPosition = QPoint(topWidget.width() - finalSize.width(), 0) if self.m_side == ApplicationSide.BottomSide: finalSize.setWidth(topWidget.width()) startPosition = QPoint(0, topWidget.height()) finalPosition = QPoint(0, topWidget.height() - finalSize.height()) if self.m_decorateBackground: self.m_decorator.setParent(topWidget) self.m_decorator.move(0, 0) self.m_decorator.grabParent() self.m_decorator.show() self.m_decorator.raise_() self.widgetForSlide().move(startPosition) self.widgetForSlide().resize(finalSize) self.widgetForSlide().raise_() self.m_decorator.grabSlideWidget(self.widgetForSlide()) if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.OutQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startPosition) self.m_animation.setEndValue(finalPosition) self.m_animation.start() if self.m_decorateBackground: DARKER = True self.m_decorator.decorate(DARKER) def animateBackward(self): self.slideOut() def slideOut(self): if self.isAnimated() and self.isAnimatedBackward(): return self.setAnimatedBackward() if self.widgetForSlide().isVisible(): _topWidget = self.widgetForSlide() topWidget = self.widgetForSlide() while _topWidget: topWidget = _topWidget _topWidget = topWidget.parentWidget() self.widgetForSlide().hide() finalSize = self.widgetForSlide().size() startPosition = self.widgetForSlide().pos() finalPosition = QPoint() if self.m_side == ApplicationSide.LeftSide: startPosition = QPoint(0, 0) finalPosition = QPoint(-finalSize.width(), 0) if self.m_side == ApplicationSide.TopSide: startPosition = QPoint(0, 0) finalPosition = QPoint(0, -finalSize.height()) if self.m_side == ApplicationSide.RightSide: startPosition = QPoint(topWidget.width() - finalSize.width(), 0) finalPosition = QPoint(topWidget.width(), 0) if self.m_side == ApplicationSide.BottomSide: startPosition = QPoint(0, topWidget.height() - finalSize.height()) finalPosition = QPoint(0, topWidget.height()) if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.InQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startPosition) self.m_animation.setEndValue(finalPosition) self.m_animation.start() if self.m_decorateBackground: LIGHTER = False self.m_decorator.decorate(LIGHTER) def eventFilter(self, _object, _event): if _object == self.widgetForSlide().parentWidget() and _event.type( ) == QEvent.Resize: widgetForSlideParent = self.widgetForSlide().parentWidget() if self.m_side == ApplicationSide.RightSide: self.widgetForSlide().move( widgetForSlideParent.width() - self.widgetForSlide().width(), 0) if self.m_side == ApplicationSide.LeftSide: self.widgetForSlide().resize(self.widgetForSlide().width(), widgetForSlideParent.height()) if self.m_side == ApplicationSide.BottomSide: self.widgetForSlide().move( 0, widgetForSlideParent.height() - self.widgetForSlide().height()) if self.m_side == ApplicationSide.TopSide: self.widgetForSlide().resize(widgetForSlideParent.width(), self.widgetForSlide().height()) self.m_decorator.grabSlideWidget(self.widgetForSlide()) return QObject.eventFilter(self, _object, _event) def widgetForSlide(self): return self.parent()
class SlideAnimator(AbstractAnimator): m_direction:AnimationDirection m_isFixBackground:bool m_isFixStartSize:bool m_startMinSize = QSize() m_startMaxSize = QSize() m_startSize = QSize() m_decorator: SlideForegroundDecorator m_animation: QPropertyAnimation def __init__(self, _widgetForSlide): super(SlideAnimator, self).__init__(_widgetForSlide) self.m_direction = AnimationDirection.FromLeftToRight self.m_isFixBackground = True self.m_isFixStartSize = False self.m_decorator = SlideForegroundDecorator(_widgetForSlide) self.m_animation = QPropertyAnimation(self.m_decorator, b"maximumWidth") _widgetForSlide.installEventFilter(self) self.m_animation.setDuration(300) self.m_decorator.hide() def valueChanged(_value): if self.isWidth(): self.widgetForSlide().setMaximumWidth(_value) else: self.widgetForSlide().setMaximumHeight(_value) self.m_animation.valueChanged.connect(valueChanged) def finished(): self.setAnimatedStopped() self.m_decorator.hide() self.m_animation.finished.connect(finished) def setAnimationDirection(self, _direction): if self.m_direction != _direction: self.m_direction = _direction self.m_animation.setPropertyName(b"maximumWidth" if self.isWidth() else b"maximumHeight") def setFixBackground(self, _fix): if self.m_isFixBackground != _fix: self.m_isFixBackground = _fix def setFixStartSize(self, _fix): if self.m_isFixStartSize != _fix: self.m_isFixStartSize = _fix def animationDuration(self): return self.m_animation.duration() def animateForward(self): self.slideIn() def slideIn(self): if not self.m_startMinSize.isValid(): self.m_startMinSize = self.widgetForSlide().minimumSize() if not self.m_startMaxSize.isValid(): self.m_startMaxSize = self.widgetForSlide().maximumSize() if not self.m_startSize.isValid(): self.m_startSize = self.widgetForSlide().sizeHint() if self.isAnimated() and self.isAnimatedForward(): return self.setAnimatedForward() if self.isWidth(): self.widgetForSlide().setMaximumWidth(0) else: self.widgetForSlide().setMaximumHeight(0) self.widgetForSlide().show() currentSize = self.widgetForSlide().size() finalSize = QSize(currentSize.width(), currentSize.height()) self.fixSize(self.m_startSize, finalSize) self.widgetForSlide().hide() self.fixSizeOfWidgetForSlide(finalSize) self.m_decorator.grabParent(finalSize) self.fixSizeOfWidgetForSlide(currentSize) self.widgetForSlide().show() if self.m_isFixBackground: self.m_decorator.move(0, 0) self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.OutQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(self.widgetForSlide().width() if self.isWidth() else self.widgetForSlide().height()) self.m_animation.setEndValue(finalSize.width() if self.isWidth() else finalSize.height()) self.m_animation.start() def animateBackward(self): self.slideOut() def slideOut(self): if not self.m_startMinSize.isValid(): self.m_startMinSize = self.widgetForSlide().minimumSize() if not self.m_startMaxSize.isValid(): self.m_startMaxSize = self.widgetForSlide().maximumSize() if not self.m_startSize.isValid() or not self.m_isFixStartSize: self.m_startSize = self.widgetForSlide().size() if self.isAnimated() and self.isAnimatedBackward(): return self.setAnimatedBackward() finalSize = self.widgetForSlide().size() if self.isWidth(): finalSize.setWidth(0) else: finalSize.setHeight(0) self.m_decorator.grabParent(self.widgetForSlide().size()) if self.m_isFixBackground: self.m_decorator.move(0, 0) self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.InQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(self.widgetForSlide().width() if self.isWidth() else self.widgetForSlide().height()) self.m_animation.setEndValue(finalSize.width() if self.isWidth() else finalSize.height()) self.m_animation.start() def eventFilter(self, _object, _event): if _object == self.widgetForSlide() and _event.type() == QEvent.Resize and self.m_decorator.isVisible(): if self.m_direction == AnimationDirection.FromLeftToRight: self.m_decorator.move(self.widgetForSlide().width() - self.m_decorator.width(), 0) if self.m_direction == AnimationDirection.FromTopToBottom: self.m_decorator.move(0, self.widgetForSlide().height() - self.m_decorator.height()) return QObject.eventFilter(self, _object, _event) def isWidth(self): return self.m_direction == AnimationDirection.FromLeftToRight or self.m_direction == AnimationDirection.FromRightToLeft def fixSize(self, _sourceSize, _targetSize): if self.isWidth(): _targetSize.setWidth(_sourceSize.width()) else: _targetSize.setHeight(_sourceSize.height()) def fixSizeOfWidgetForSlide(self, _sourceSize): if self.isWidth(): self.widgetForSlide().setFixedWidth(_sourceSize.width()) else: self.widgetForSlide().setFixedHeight(_sourceSize.height()) def widgetForSlide(self): return self.parent()
class LoginWidget(QWidget, Ui_LoginUi): DEBUG = 0 def __init__(self, *args, **kwargs): super(LoginWidget, self).__init__(*args, **kwargs) self.setupUi(self) self.startValue = '' self.par = self.parent() self.oe = QGraphicsOpacityEffect() if os.path.exists('userLogin.ini'): with open('userLogin.ini') as f: inf = eval(f.read()) self.lineEdit_2.setText(inf['user']) self.lineEdit.setText(inf['pw']) else: self.lineEdit_2.setText('') self.lineEdit.setText('') # 初始化爬虫 self.spider = zk_spider() # 定时器 self.t = QTimer(self) # 开始的加载背景1 self.t2 = QTimer(self) # 使背景1隐藏 self.t3 = QTimer(self) # 让背景2浮现 # 动画 self.widget_2Ani = QPropertyAnimation( self.par.widget_2, b"pos", ) # 变量 self.bgOpacity = 0 # oe self.setGraphicsEffect(self.oe) # connect self.pushButton.clicked.connect(self.login) # 函数 # 如已有 cookies则直接登录 def login(self): """ 开始判断是否登录成功,若成功,执行动画 """ # 获取账号, 密码, 验证码 user = self.lineEdit_2.text() pw = self.lineEdit.text() code = self.lineEdit_3.text() hot = self.checkBox.checkState() auto = self.checkBox_2.checkState() if self.DEBUG: self.loginAni() else: if os.path.exists('cookies.c*k'): os.remove('cookies.c*k') self.spider.checkCookiesAndTryLogin(user, pw, code, False) if self.spider.login_success: self.loginAni() else: QMessageBox.warning(self, '用户名或密码 或验证码错误', '用户名或密码 或验证码错误') """ 若记住密码 """ if self.checkBox.isChecked(): with open('userLogin.ini', 'w') as f: f.write(str({'user': user, 'pw': pw})) # 耦合性太强了 麻烦分离 这里开始login了 pass def loginAni(self): if not self.widget_2Ani.state(): self.widget_2Animation() self.autoHide() def autoHide(self, login=True): # 开始切换到content if login: t = QTimer(self) t2 = QTimer(self.par) self.par.fadeWidget( self.par, t, self.par.widget_2.hide, self.par.widget_2.lower, self.par.contentShow, lambda: self.par.emergedWidget(self.par.ContentWidget, t2), lambda: self.par.setStyleSheet(""" QMainWindow#ZhkuMainWindow{ border-image:url(:/img/img/72e13cfaff5836b02aa0b0553584c7c7.jpg)} """)) else: # 显示login # 去除 弹簧 self.par.removeSpacer(self.par.horizontalLayout_2) """ hide的先后顺序会导致变大变小 """ self.par.ContentWidget.hide() # 显示 父控件scoretable def widget_2Animation(self): self.widget_2Ani.setStartValue( QPoint(self.par.widget_2.x(), self.par.widget_2.y())) self.widget_2Ani.setEndValue( QPoint(self.par.widget.x(), self.par.widget.y())) self.widget_2Ani.setDuration(3000) self.widget_2Ani.setEasingCurve(QEasingCurve.InOutElastic) self.widget_2Ani.start() def autoLogin(self, login=True): """ 自动更换背景 """ if login: print('尝试自动登录') if self.spider.login_success: self.loginAni() else: pass
class Button(QGraphicsObject): font: QFont = QFont("monospace", 16) clicked = pyqtSignal('PyQt_PyObject') hovered_ = pyqtSignal('PyQt_PyObject') def __init__(self, btn_id: str, label: str, tooltip: str = "", parent: QGraphicsItem = None): QGraphicsObject.__init__(self, parent) self.setFlag(QGraphicsItem.ItemIgnoresTransformations, True) self.scaling = 1.0 self.label = QGraphicsSimpleTextItem(label, self) self.label.setFont(Button.font) self.text_color_enabled = QColor(255, 255, 255, 255) self.text_color_disabled = QColor(200, 200, 200, 255) self.fill_color_disabled = QColor(125, 125, 125, 200) self.label.setBrush(QBrush(self.text_color_enabled)) self.btn_id = btn_id self.rect = QRectF(0, 0, 0, 0) self.tooltip = QGraphicsSimpleTextItem(tooltip, self) self.tooltip.setBrush(QColor(255, 255, 255, 200)) self.tooltip.setFont(Button.font) self.tooltip.setVisible(False) self.tooltip.setZValue(25) self.tooltip_shown = False self.base_color = ButtonColor.GRAY self.hor_margin = 10 self.ver_margin = 5 self.current_timer = 0 self.logic: Union[CheckbuttonLogic, PushbuttonLogic] = PushbuttonLogic("gray") self.fill_color_current = self.logic.idle_color() self.color_animation = QPropertyAnimation(self, b"current_fill_color") self.color_animation.setEasingCurve(QEasingCurve.Linear) self.hovered = False self.setAcceptHoverEvents(True) self.setZValue(4) self.set_height(12) self.pixmap: QPixmap = None self.max_pixmap_height = 128 self.disabled = False self.mode = ButtonMode.Button def set_height(self, h: int): self.prepareGeometryChange() self.ver_margin = int(0.25 * h) font: QFont = self.label.font() font.setPointSize(h) self.label.setFont(font) self.rect.setWidth(self.label.boundingRect().width() + 2 * self.hor_margin) self.rect.setHeight(self.label.boundingRect().height() + 2 * self.ver_margin) self._reposition_text() def set_width(self, w: int): if self.pixmap is not None: return self.prepareGeometryChange() self.rect.setWidth(w) self.hor_margin = self.ver_margin if self.label.boundingRect().width() > self.rect.width(): w = self.rect.width() - 2 * self.hor_margin factor = w / self.label.boundingRect().width() h = factor * self.label.boundingRect().height() font = self.label.font() font.setPixelSize(max(h, 12)) self.label.setFont(font) self._reposition_text() def set_button_height(self, h: int): self.rect.setHeight(h) self._reposition_text() def scale_button(self, factor: float): factor = 1.0 self.rect.setHeight(int(factor * self.rect.height())) self.rect.setWidth(int(factor * self.rect.width())) self.label.setScale(self.scaling) self.fit_to_contents() def _reposition_text(self): x = self.rect.width() / 2 - self.label.boundingRect().width() / 2 y = self.rect.height() / 2 - self.label.boundingRect().height() / 2 self.label.setPos(QPointF(x, y)) self.update() def boundingRect(self): return self.rect def paint(self, painter: QPainter, options, widget=None): painter.setBrush(QBrush(self.fill_color_current)) painter.setPen(QPen(QColor(0, 0, 0, 0))) painter.drawRoundedRect(self.rect, 5, 5) if self.pixmap is not None: painter.drawPixmap(self.hor_margin * self.scaling, self.ver_margin * self.scaling, self.pixmap) if self.tooltip_shown: painter.setBrush(QBrush(QColor(50, 50, 50, 200))) painter.drawRoundedRect(self.tooltip.boundingRect().translated(self.tooltip.pos()) .marginsAdded(QMarginsF(5, 0, 5, 0)), 5, 5) def mousePressEvent(self, event: QGraphicsSceneMouseEvent): if self.disabled or self.mode == ButtonMode.Label: return self.fill_color_current = self.logic.press_color() self.scene().update() def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent): if self.disabled or self.mode == ButtonMode.Label: return self.click_button() def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): if self.disabled or self.mode == ButtonMode.Label: return self.hovered_.emit({'btn_id': self.btn_id, 'button': self, 'hovered': True}) self.hovered = True if len(self.tooltip.text()) > 0: self.tooltip_shown = True self.tooltip.setVisible(True) view = self.scene().views()[0] rect_ = view.mapFromScene(self.tooltip.sceneBoundingRect()).boundingRect() pos = self.boundingRect().topRight() mouse_pos = view.mapFromScene(event.scenePos()) if mouse_pos.x() + rect_.width() >= view.viewport().width(): pos = QPointF(-self.tooltip.boundingRect().width(), 0) self.tooltip.setPos(pos) self.color_animation.setDuration(200) self.color_animation.setStartValue(self.logic.idle_color()) self.color_animation.setEndValue(self.logic.hover_enter_color()) self.scene().update() if self.current_timer >= 0: self.killTimer(self.current_timer) self.color_animation.start() self.current_timer = self.startTimer(self.color_animation.duration() // 80) def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent): if self.disabled or self.mode == ButtonMode.Label: return self.hovered_.emit({'btn_id': self.btn_id, 'button': self, 'hovered': False}) self.hovered = False self.tooltip_shown = False self.tooltip.setVisible(False) self.color_animation.setDuration(200) self.color_animation.setStartValue(self.logic.hover_enter_color()) self.color_animation.setEndValue(self.logic.hover_left_color()) self.scene().update() if self.current_timer > 0: self.killTimer(self.current_timer) self.color_animation.start() self.current_timer = self.startTimer(self.color_animation.duration() // 80) def hoverMoveEvent(self, event: QGraphicsSceneHoverEvent): pass @pyqtProperty(QColor) def current_fill_color(self): return self.fill_color_current @current_fill_color.setter def current_fill_color(self, color: QColor): self.fill_color_current = color def timerEvent(self, ev: QTimerEvent): self.scene().update() if self.color_animation.state() == QPropertyAnimation.Stopped: self.killTimer(ev.timerId()) self.current_timer = 0 def set_base_color(self, colors: List[ButtonColor]): self.logic.set_colors(colors) self.fill_color_current = self.logic.idle_color() def is_on(self): return self.logic.is_down() def set_on(self, value: bool): if isinstance(self.logic, CheckbuttonLogic): self.logic.down = value self.fill_color_current = self.logic.press_color() if value else self.logic.idle_color() self.update() def set_is_check_button(self, value: bool): if value: self.logic = CheckbuttonLogic() else: self.logic = PushbuttonLogic("gray") def set_pixmap(self, pixmap: QPixmap): if pixmap is not None: self.pixmap = pixmap.scaledToHeight(self.max_pixmap_height * self.scaling) if pixmap is not None else None self.fit_to_contents() def fit_to_contents(self): self.prepareGeometryChange() width = 2 * self.hor_margin * self.label.scale() height = 2 * self.ver_margin * self.label.scale() + self.label.boundingRect().height() * self.label.scale() if self.pixmap is not None: width += max(self.pixmap.width(), self.label.boundingRect().width() * self.label.scale()) height += self.ver_margin * self.scaling + self.pixmap.height() else: width += self.label.boundingRect().width() * self.label.scale() self.rect.setWidth(width) self.rect.setHeight(height) self.label.setPos(0.5 * width - 0.5 * self.label.boundingRect().width() * self.label.scale() + 0.0 * self.hor_margin, height - self.label.boundingRect().height() * self.label.scale() - self.ver_margin * self.scaling) self.update() def adjust_text_to_button(self): height_diff = 0.75 * self.rect.height() - self.label.boundingRect().height() fac = height_diff / (0.75 * self.rect.height()) self.label.setTransformOriginPoint(self.label.boundingRect().center()) self.label.setScale(1.0 + fac) self._reposition_text() def set_label(self, text: str, direction: str = 'horizontal'): if direction == 'vertical': text = '\n'.join(list(text)) self.label.setText(text) self._reposition_text() def click_button(self, artificial_emit: bool = False): if self.disabled: return self.logic.do_click() self.fill_color_current = self.logic.release_color() if not artificial_emit else self.logic.idle_color() self.clicked.emit({"btn_id": self.btn_id, "btn_label": self.label, "button": self, 'checked': self.is_on()}) if artificial_emit: self.hovered_.emit({'btn_id': self.btn_id, 'button': self, 'hovered': False}) self.update() if self.scene() is not None: self.scene().update() def set_opacity(self, opacity: float): self.setOpacity(opacity) self.update() def set_default_state(self): self.logic.reset_state() self.fill_color_current = self.logic.idle_color() self.tooltip.setVisible(False) self.tooltip_shown = False self.update() def set_disabled(self, disabled: bool): self.disabled = disabled if disabled: self.label.setBrush(QBrush(self.text_color_disabled)) self.fill_color_current = self.fill_color_disabled else: self.label.setBrush(QBrush(self.text_color_enabled)) self.fill_color_current = self.logic.idle_color() self.update() def set_tooltip(self, tooltip: str): self.tooltip.setVisible(False) self.tooltip_shown = False self.tooltip.setText(tooltip) def set_custom_color(self, colors: List[QColor]): if isinstance(self.logic, CheckbuttonLogic): if len(colors) < 2: return self.logic.set_colors(colors=[ colors[0], colors[0].lighter(120), colors[0].darker(120), colors[1], colors[1].lighter(120), colors[1].darker(120) ]) else: self.logic.set_colors(colors=[ colors[0], colors[0].lighter(120), colors[0].darker(120) ]) self.fill_color_current = self.logic.idle_color() self.update() def set_mode(self, mode: ButtonMode): self.mode = mode
class UiTasklist(object): def setup(self, Dialog): Dialog.setObjectName("TaskList") Dialog.setEnabled(True) Dialog.setWindowFlags(QtCore.Qt.FramelessWindowHint) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth()) Dialog.setSizePolicy(sizePolicy) Dialog.setStyleSheet(CSS) self.label_3 = QtWidgets.QLabel(Dialog) self.label_3.setGeometry(QtCore.QRect(100, 0, 91, 16)) self.label_3.setObjectName("label_3") self.verticalLayoutWidget = QtWidgets.QWidget(Dialog) self.verticalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 341, 541)) self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) self.verticalLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(0) self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setSizeConstraint( QtWidgets.QLayout.SetMaximumSize) self.verticalLayout_2.setContentsMargins(0, 0, 0, 6) self.verticalLayout_2.setSpacing(4) self.verticalLayout_2.setObjectName("verticalLayout_2") self.lbl_status = QtWidgets.QLabel(self.verticalLayoutWidget) font = QtGui.QFont() font.setPointSize(10) font.setBold(True) font.setWeight(75) self.lbl_status.setFont(font) self.lbl_status.setAlignment(QtCore.Qt.AlignCenter) self.lbl_status.setObjectName("lbl_status") self.verticalLayout_2.addWidget(self.lbl_status) self.label_2 = QtWidgets.QLabel(self.verticalLayoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.label_2.sizePolicy().hasHeightForWidth()) self.label_2.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(9) font.setBold(False) font.setWeight(50) self.label_2.setFont(font) self.label_2.setTextFormat(QtCore.Qt.AutoText) self.label_2.setScaledContents(False) self.label_2.setAlignment(QtCore.Qt.AlignCenter) self.label_2.setWordWrap(False) self.label_2.setObjectName("label_2") self.verticalLayout_2.addWidget(self.label_2) self.verticalLayout.addLayout(self.verticalLayout_2) self.listWidget = QtWidgets.QListWidget(self.verticalLayoutWidget) self.listWidget.setFixedHeight(460) self.listWidget.setFixedWidth(340) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.listWidget.sizePolicy().hasHeightForWidth()) self.listWidget.setSizePolicy(sizePolicy) self.listWidget.setAutoFillBackground(False) self.listWidget.setFrameShape(QtWidgets.QFrame.NoFrame) self.listWidget.setFrameShadow(QtWidgets.QFrame.Raised) self.listWidget.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarAlwaysOff) self.listWidget.setEditTriggers( QtWidgets.QAbstractItemView.NoEditTriggers) self.listWidget.setSelectionMode( QtWidgets.QAbstractItemView.NoSelection) self.listWidget.setMovement(QtWidgets.QListView.Static) #self.listWidget.setProperty("isWrapping", False) self.listWidget.setResizeMode(QtWidgets.QListView.Fixed) self.listWidget.setLayoutMode(QtWidgets.QListView.SinglePass) self.listWidget.setViewMode(QtWidgets.QListView.ListMode) self.listWidget.setSelectionRectVisible(False) self.listWidget.setObjectName("listWidget") self.verticalLayout.addWidget(self.listWidget) self.retranslate(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslate(self, Dialog): _translate = QtCore.QCoreApplication.translate Dialog.setWindowTitle(_translate("Dialog", "GSyncftp - Tasklist")) self.lbl_status.setText( _translate("TaskList", "O GSyncftp está atualizado")) self.label_2.setText(_translate("TaskList", " Enterprise Coorp. LTDA")) self.listWidget.setSortingEnabled(False) self.listWidget.addItem("Nenhum arquivo na fila") def status_sync(self): _translate = QtCore.QCoreApplication.translate self.lbl_status.setText( _translate("TaskList", "Enviando os arquivos...")) def status_synced(self): _translate = QtCore.QCoreApplication.translate self.lbl_status.setText( _translate("TaskList", "O GSyncftp está atualizado")) def do_animation(self, Dialog): size_screen = QtWidgets.QDesktopWidget().screenGeometry(-1) pt_x = size_screen.width() - 339 pt_y = size_screen.height() - 537 self.anim = QPropertyAnimation(Dialog, b"geometry") self.anim.setDuration(250) self.anim.setStartValue(QRect(pt_x, size_screen.height(), 339, 537)) self.anim.setEndValue(QRect(pt_x, pt_y, 339, 537)) self.anim.start() Dialog.setEnabled(True) def not_in_animation(self): return self.anim and self.anim.state() != 2
class CircleFillAnimator(AbstractAnimator): m_decorator: CircleFillDecorator m_animation: QPropertyAnimation m_hideAfterFinish = True def __init__(self, _widgetForFill): super(CircleFillAnimator, self).__init__(_widgetForFill) self.m_decorator = CircleFillDecorator(_widgetForFill) self.m_animation = QPropertyAnimation(self.m_decorator, b"_radius") _widgetForFill.installEventFilter(self) self.m_animation.setDuration(500) self.m_decorator.setAttribute(Qt.WA_TransparentForMouseEvents) self.m_decorator.hide() def finished(): self.setAnimatedStopped() if self.m_hideAfterFinish: self.hideDecorator() self.m_animation.finished.connect(finished) def setStartPoint(self, _point): self.m_decorator.setStartPoint(_point) def setFillColor(self, _color): self.m_decorator.setFillColor(_color) def setHideAfterFinish(self, _hide): self.m_hideAfterFinish = _hide def animationDuration(self): return self.m_animation.duration() def animateForward(self): self.fillIn() def fillIn(self): if self.isAnimated() and self.isAnimatedForward(): return self.setAnimatedForward() startRadius = 0 finalRadius = math.sqrt( self.widgetForFill().height() * self.widgetForFill().height() + self.widgetForFill().width() * self.widgetForFill().width()) self.m_decorator.resize(self.widgetForFill().size()) self.m_decorator.move(0, 0) self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.OutQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startRadius) self.m_animation.setEndValue(finalRadius) self.m_animation.start() def animateBackward(self): self.fillOut() def fillOut(self): if self.isAnimated() and self.isAnimatedBackward(): return self.setAnimatedBackward() startRadius = math.sqrt( self.widgetForFill().height() * self.widgetForFill().height() + self.widgetForFill().width() * self.widgetForFill().width()) finalRadius = 0 self.m_decorator.resize(self.widgetForFill().size()) self.m_decorator.move(0, 0) self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.OutQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startRadius) self.m_animation.setEndValue(finalRadius) self.m_animation.start() def hideDecorator(self): hideEffect = self.m_decorator.graphicsEffect() if hideEffect == None: hideEffect = QGraphicsOpacityEffect(self.m_decorator) self.m_decorator.setGraphicsEffect(hideEffect) hideEffect.setOpacity(1) hideAnimation = QPropertyAnimation(hideEffect, b"opacity", self.m_decorator) hideAnimation.setDuration(400) hideAnimation.setStartValue(1) hideAnimation.setEndValue(0) def finished(): self.m_decorator.hide() hideEffect.setOpacity(1) hideAnimation.finished.connect(finished) hideAnimation.start(QAbstractAnimation.DeleteWhenStopped) def widgetForFill(self): return self.parent()
class QTile(QGraphicsObject): colorMap = { Tetris.I: QColor("#53bbf4"), Tetris.J: QColor("#e25fb8"), Tetris.L: QColor("#ffac00"), Tetris.O: QColor("#ecff2e"), Tetris.S: QColor("#97eb00"), Tetris.T: QColor("#ff85cb"), Tetris.Z: QColor("#ff5a48") } def __init__(self, qTetris: 'QTetris', tetrimino: Tetris.Tetrimino, tile: Tetris.Tile): super(QTetris.QTile, self).__init__() tile.delegate = self self.color = self.colorMap[type(tetrimino)] self.qTetris = qTetris self.moveAnimation = QParallelAnimationGroup() self.dropAnimation = QPropertyAnimation(self, b'pos') self.collapseAnimation = QPropertyAnimation(self, b'pos') self.shiftAnimation = QPropertyAnimation(self, b'pos') self.collapseAnimation.finished.connect( lambda tl=tile: tile.delegate.disappeared(tl)) self.qTetris.scene.addItem(self) self.setPos(QPointF(0, 4)) self.moved(tile) def moved(self, tile: Tetris.Tile): translation = QPropertyAnimation(self, b'pos') start, end = self.pos(), QPointF(tile.row, tile.column) curve, speed, delay = QEasingCurve.OutBack, 1 / 50, -1 self.animate(translation, start, end, curve, speed, delay) rotation = QPropertyAnimation(self, b'rotation') start, end = self.rotation(), tile.rotation curve, speed, delay = QEasingCurve.OutBack, 1, -1 self.animate(rotation, start, end, curve, speed, delay) rotation.setDuration(translation.duration()) self.moveAnimation.clear() self.moveAnimation.addAnimation(translation) self.moveAnimation.addAnimation(rotation) self.moveAnimation.start() def dropped(self, tile: Tetris.Tile): start, end = self.pos(), QPointF(tile.row, tile.column) curve, speed, delay = QEasingCurve.OutBounce, 1 / 50, 0 self.animate(self.dropAnimation, start, end, curve, speed, delay) def collapsed(self, tile: Tetris.Tile): start, end = self.pos(), QPointF( tile.row, tile.column + 2 * tile.tetris.num_columns) curve, speed, delay = QEasingCurve.InOutExpo, 1 / 50, 800 if self.dropAnimation.state() == QAbstractAnimation.Running: start = self.dropAnimation.endValue() self.animate(self.collapseAnimation, start, end, curve, speed, delay) def shifted(self, tile: Tetris.Tile): start, end = self.pos(), QPointF(tile.row, tile.column) curve, speed, delay = QEasingCurve.OutBounce, 1 / 100, 1200 if self.dropAnimation.state() == QAbstractAnimation.Running: start = self.dropAnimation.endValue() self.animate(self.shiftAnimation, start, end, curve, speed, delay) def disappeared(self, tile: Tetris.Tile): self.qTetris.scene.removeItem(self) def paint(self, painter: QPainter, styleOption: QStyleOptionGraphicsItem, widget: QWidget = None): pen = QPen() pen.setWidthF(0.05) pen.setColor(Qt.darkGray) painter.setPen(pen) brush = QBrush() brush.setColor(self.color) brush.setStyle(Qt.SolidPattern) painter.setBrush(brush) topLeft = QPointF(0, 0) bottomRight = QPointF(1, 1) rectangle = QRectF(topLeft, bottomRight) rectangle.translate(-0.5, -0.5) painter.drawRect(rectangle) @staticmethod def animate(animation: QPropertyAnimation, start: Union[QPointF, int, float], end: Union[QPointF, int, float], curve: QEasingCurve = QEasingCurve.Linear, speed: float = 1 / 50, delay: int = -1): animation.setStartValue(start) animation.setEndValue(end) animation.setEasingCurve(curve) if type(start) == type(end) == QPointF: distance = (end - start).manhattanLength() else: distance = abs(end - start) animation.setDuration(round(distance / speed)) if delay == 0: animation.start() if delay > 0: QTimer.singleShot(delay, animation.start) def boundingRect(self): topLeft = QPointF(0, 0) bottomRight = QPointF(1, 1) rectangle = QRectF(topLeft, bottomRight) rectangle.translate(-0.5, -0.5) return rectangle
class StackedWidgetFadeInAnimator(AbstractAnimator): m_decorator: StackedWidgetFadeInDecorator m_animation: QPropertyAnimation def __init__(self, _container, _fadeWidget): super(StackedWidgetFadeInAnimator, self).__init__(_container) self.m_decorator = StackedWidgetFadeInDecorator(_container, _fadeWidget=_fadeWidget) self.m_animation = QPropertyAnimation(self.m_decorator, b"opacity") _container.installEventFilter(self) self.m_animation.setDuration(200) self.m_decorator.hide() def finished(): self.setAnimatedStopped() if self.m_animation.direction() == QPropertyAnimation.Forward: _container.setCurrentWidget(_fadeWidget) self.m_decorator.hide() self.m_animation.finished.connect(finished) def animationDuration(self): return self.m_animation.duration() def animateForward(self): self.fadeIn() def fadeIn(self): if self.isAnimated() and self.isAnimatedForward(): return self.setAnimatedForward() self.m_decorator.grabContainer() self.m_decorator.grabFadeWidget() startOpacity = 0 finalOpacity = 1 self.m_decorator.setOpacity(startOpacity) self.m_decorator.move(0, 0) self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.InQuad) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startOpacity) self.m_animation.setEndValue(finalOpacity) self.m_animation.start() def eventFilter(self, _object, _event): if _object == self.fadeWidget() and _event.type() == QEvent.Resize and self.m_decorator.isVisible(): self.m_decorator.grabContainer() self.m_decorator.grabFadeWidget() return QWidget.eventFilter(self, _object, _event) def fadeWidget(self): return self.parent()
class QTetritile(QGraphicsObject): colorMap = { Tetris.I: QColor("#53bbf4"), Tetris.J: QColor("#e25fb8"), Tetris.L: QColor("#ffac00"), Tetris.O: QColor("#ecff2e"), Tetris.S: QColor("#97eb00"), Tetris.T: QColor("#ff85cb"), Tetris.Z: QColor("#ff5a48") } def __init__(self, tetris, tetritile): super(QTetris.QTetritile, self).__init__() tetritile.delegate = self self.color = self.colorMap[type(tetritile.tetrimino)] self.tetris = tetris self.moveAnimation = QSequentialAnimationGroup() self.dropAnimation = QPropertyAnimation(self, b"pos") self.collapseAnimation = QPropertyAnimation(self, b"pos") self.shiftAnimation = QPropertyAnimation(self, b"pos") self.collapseAnimation.finished.connect( lambda tetritile=tetritile: self.tetris.disappearEvent( tetritile)) self.tetris.scene.addItem(self) self.setPos(QPointF(0, 4)) self.moveEvent(tetritile) def moveEvent(self, tetritile): translation = QPropertyAnimation(self, b"pos") start, end = self.pos(), QPointF(tetritile.row, tetritile.column) curve, speed, delay = QEasingCurve.OutBack, 1 / 50, -1 self.animate(translation, start, end, curve, speed, delay) rotation = QPropertyAnimation(self, b"rotation") start, end = self.rotation(), tetritile.rotation curve, speed, delay = QEasingCurve.OutBack, 1, -1 self.animate(rotation, start, end, curve, speed, delay) rotation.setDuration(translation.duration()) self.moveAnimation.clear() self.moveAnimation.addAnimation(translation) self.moveAnimation.addAnimation(rotation) self.moveAnimation.start() def dropEvent(self, tetritile): start, end = self.pos(), QPointF(tetritile.row, tetritile.column) curve, speed, delay = QEasingCurve.OutBounce, 1 / 50, 0 self.animate(self.dropAnimation, start, end, curve, speed, delay) def collapseEvent(self, tetritile): start, end = self.pos(), QPointF( tetritile.row, tetritile.column + self.tetris.tetris.columns + 5) curve, speed, delay = QEasingCurve.InOutExpo, 1 / 50, 800 if self.dropAnimation.state() == QAbstractAnimation.Running: start = self.dropAnimation.endValue() self.animate(self.collapseAnimation, start, end, curve, speed, delay) def shiftEvent(self, tetritile): start, end = self.pos(), QPointF(tetritile.row, tetritile.column) curve, speed, delay = QEasingCurve.OutBounce, 1 / 100, 1200 if self.dropAnimation.state() == QAbstractAnimation.Running: start = self.dropAnimation.endValue() self.animate(self.shiftAnimation, start, end, curve, speed, delay) def paint(self, painter, styleOption, widget=None): pen = QPen() pen.setWidthF(0.05) pen.setColor(Qt.darkGray) painter.setPen(pen) brush = QBrush() brush.setColor(self.color) brush.setStyle(Qt.SolidPattern) painter.setBrush(brush) topLeft = QPointF(0, 0) bottomRight = QPointF(1, 1) rectangle = QRectF(topLeft, bottomRight) rectangle.translate(-0.5, -0.5) painter.drawRect(rectangle) def animate(self, animation, start, end, curve=QEasingCurve.Linear, speed=1 / 50, delay=-1): animation.setStartValue(start) animation.setEndValue(end) animation.setEasingCurve(curve) try: animation.setDuration((end - start).manhattanLength() / speed) except AttributeError: animation.setDuration(abs(end - start) / speed) if delay == 0: animation.start() if delay > 0: QTimer.singleShot(delay, animation.start) def boundingRect(self): topLeft = QPointF(0, 0) bottomRight = QPointF(1, 1) rectangle = QRectF(topLeft, bottomRight) rectangle.translate(-0.5, -0.5) return rectangle
class CAvatar(QWidget): Circle = 0 # 圆圈 Rectangle = 1 # 圆角矩形 SizeLarge = QSize(128, 128) SizeMedium = QSize(64, 64) SizeSmall = QSize(32, 32) StartAngle = 0 # 起始旋转角度 EndAngle = 360 # 结束旋转角度 def __init__(self, *args, shape=0, url='', cacheDir=False, size=QSize(64, 64), animation=False, **kwargs): super(CAvatar, self).__init__(*args, **kwargs) self.url = '' self._angle = 0 # 角度 self.pradius = 0 # 加载进度条半径 self.animation = animation # 是否使用动画 self._movie = None # 动态图 self._pixmap = QPixmap() # 图片对象 self.pixmap = QPixmap() # 被绘制的对象 self.isGif = url.endswith('.gif') # 进度动画定时器 self.loadingTimer = QTimer(self, timeout=self.onLoading) # 旋转动画 self.rotateAnimation = QPropertyAnimation(self, b'angle', self, loopCount=1) self.setShape(shape) self.setCacheDir(cacheDir) self.setSize(size) self.setUrl(url) def paintEvent(self, event): super(CAvatar, self).paintEvent(event) # 画笔 painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing, True) painter.setRenderHint(QPainter.HighQualityAntialiasing, True) painter.setRenderHint(QPainter.SmoothPixmapTransform, True) # 绘制 path = QPainterPath() diameter = min(self.width(), self.height()) if self.shape == self.Circle: radius = int(diameter / 2) elif self.shape == self.Rectangle: radius = 4 halfW = self.width() / 2 halfH = self.height() / 2 painter.translate(halfW, halfH) path.addRoundedRect(QRectF(-halfW, -halfH, diameter, diameter), radius, radius) painter.setClipPath(path) # 如果是动画效果 if self.rotateAnimation.state() == QPropertyAnimation.Running: painter.rotate(self._angle) # 旋转 painter.drawPixmap( QPointF(-self.pixmap.width() / 2, -self.pixmap.height() / 2), self.pixmap) else: painter.drawPixmap(-int(halfW), -int(halfH), self.pixmap) # 如果在加载 if self.loadingTimer.isActive(): diameter = 2 * self.pradius painter.setBrush( QColor(45, 140, 240, (1 - self.pradius / 10) * 255)) painter.setPen(Qt.NoPen) painter.drawRoundedRect( QRectF(-self.pradius, -self.pradius, diameter, diameter), self.pradius, self.pradius) def enterEvent(self, event): """鼠标进入动画 :param event: """ if not (self.animation and not self.isGif): return self.rotateAnimation.stop() cv = self.rotateAnimation.currentValue() or self.StartAngle self.rotateAnimation.setDuration(540 if cv == 0 else int(cv / self.EndAngle * 540)) self.rotateAnimation.setStartValue(cv) self.rotateAnimation.setEndValue(self.EndAngle) self.rotateAnimation.start() def leaveEvent(self, event): """鼠标离开动画 :param event: """ if not (self.animation and not self.isGif): return self.rotateAnimation.stop() cv = self.rotateAnimation.currentValue() or self.EndAngle self.rotateAnimation.setDuration(int(cv / self.EndAngle * 540)) self.rotateAnimation.setStartValue(cv) self.rotateAnimation.setEndValue(self.StartAngle) self.rotateAnimation.start() def onLoading(self): """更新进度动画 """ if self.loadingTimer.isActive(): if self.pradius > 9: self.pradius = 0 self.pradius += 1 else: self.pradius = 0 self.update() def onFinished(self): """图片下载完成 """ self.loadingTimer.stop() self.pradius = 0 reply = self.sender() if self.isGif: self._movie = QMovie(reply, b'gif', self) if self._movie.isValid(): self._movie.frameChanged.connect(self._resizeGifPixmap) self._movie.start() else: data = reply.readAll().data() reply.deleteLater() del reply self._pixmap.loadFromData(data) if self._pixmap.isNull(): self._pixmap = QPixmap(self.size()) self._pixmap.fill(QColor(204, 204, 204)) self._resizePixmap() def onError(self, code): """下载出错了 :param code: """ self._pixmap = QPixmap(self.size()) self._pixmap.fill(QColor(204, 204, 204)) self._resizePixmap() def refresh(self): """强制刷新 """ self._get(self.url) def isLoading(self): """判断是否正在加载 """ return self.loadingTimer.isActive() def setShape(self, shape): """设置形状 :param shape: 0=圆形, 1=圆角矩形 """ self.shape = shape def setUrl(self, url): """设置url,可以是本地路径,也可以是网络地址 :param url: """ self.url = url self._get(url) def setCacheDir(self, cacheDir=''): """设置本地缓存路径 :param cacheDir: """ self.cacheDir = cacheDir self._initNetWork() def setSize(self, size): """设置固定尺寸 :param size: """ if not isinstance(size, QSize): size = self.SizeMedium self.setMinimumSize(size) self.setMaximumSize(size) self._resizePixmap() @pyqtProperty(int) def angle(self): return self._angle @angle.setter def angle(self, value): self._angle = value self.update() def _resizePixmap(self): """缩放图片 """ if not self._pixmap.isNull(): self.pixmap = self._pixmap.scaled(self.width(), self.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation) self.update() def _resizeGifPixmap(self, _): """缩放动画图片 """ if self._movie: self.pixmap = self._movie.currentPixmap().scaled( self.width(), self.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation) self.update() def _initNetWork(self): """初始化异步网络库 """ if not hasattr(qApp, '_network'): network = QNetworkAccessManager(self.window()) setattr(qApp, '_network', network) # 是否需要设置缓存 if self.cacheDir and not qApp._network.cache(): cache = QNetworkDiskCache(self.window()) cache.setCacheDirectory(self.cacheDir) qApp._network.setCache(cache) def _get(self, url): """设置图片或者请求网络图片 :param url: """ if not url: self.onError('') return if url.startswith('http') and not self.loadingTimer.isActive(): url = QUrl(url) request = QNetworkRequest(url) request.setHeader(QNetworkRequest.UserAgentHeader, b'CAvatar') request.setRawHeader(b'Author', b'Irony') request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True) if qApp._network.cache(): request.setAttribute(QNetworkRequest.CacheLoadControlAttribute, QNetworkRequest.PreferNetwork) request.setAttribute(QNetworkRequest.CacheSaveControlAttribute, True) reply = qApp._network.get(request) self.pradius = 0 self.loadingTimer.start(50) # 显示进度动画 reply.finished.connect(self.onFinished) reply.error.connect(self.onError) return self.pradius = 0 if os.path.exists(url) and os.path.isfile(url): if self.isGif: self._movie = QMovie(url, parent=self) if self._movie.isValid(): self._movie.frameChanged.connect(self._resizeGifPixmap) self._movie.start() else: self._pixmap = QPixmap(url) self._resizePixmap() else: self.onError('')
class ExpandAnimator(AbstractAnimator): m_decorator: ExpandDecorator m_animation: QPropertyAnimation m_expandRect: QRect def __init__(self, _widgetForFill): super(ExpandAnimator, self).__init__(_widgetForFill) self.m_decorator = ExpandDecorator(_widgetForFill) self.m_animation = QPropertyAnimation(self.m_decorator, b"_expandRect") _widgetForFill.installEventFilter(self) self.m_animation.setDuration(400) self.m_decorator.hide() def finished(): self.setAnimatedStopped() if self.isAnimatedBackward(): self.m_decorator.hide() self.m_animation.finished.connect(finished) def setExpandRect(self, _rect): self.m_expandRect = _rect self.m_decorator.setExpandRect(_rect) def setFillColor(self, _color): self.m_decorator.setFillColor(_color) def animationDuration(self): return self.m_animation.duration() def animateForward(self): self.expandIn() def expandIn(self): if self.isAnimated() and self.isAnimatedForward(): return self.setAnimatedForward() startExpandRect = self.m_expandRect finalExpandRect = self.widgetForFill().rect() self.m_decorator.resize(self.widgetForFill().size()) self.m_decorator.move(0, 0) self.m_decorator.grabExpandRect() self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.InOutQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startExpandRect) self.m_animation.setEndValue(finalExpandRect) self.m_animation.start() def animateBackward(self): self.expandOut() def expandOut(self): if self.isAnimated() and self.isAnimatedBackward(): return self.setAnimatedBackward() startExpandRect = self.widgetForFill().rect() finalExpandRect = self.m_expandRect self.m_decorator.resize(self.widgetForFill().size()) self.m_decorator.move(0, 0) self.m_decorator.hide() self.m_decorator.grabExpandRect() self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.InOutQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startExpandRect) self.m_animation.setEndValue(finalExpandRect) self.m_animation.start() def eventFilter(self, _object, _event): if _object == self.widgetForFill() and _event.type( ) == QEvent.Resize and self.m_decorator.isVisible(): self.m_decorator.resize(self.widgetForFill().size()) self.m_animation.setEndValue(self.widgetForFill().rect()) return QObject.eventFilter(self, _object, _event) def widgetForFill(self): return self.parent()
class PopupWidget(QtWidgets.QWidget): # enum PointerPosition LeftSide = 0 RightSide = 1 TopSide = 2 BottomSide = 3 NoPointer = 4 LR_MARGIN = 10.0 #8.0 #/* left / right margin */ TB_MARGIN = 8.0 #5.5 #/* top / bottom margin */ didHide = pyqtSignal() didShow = pyqtSignal() onClick = pyqtSignal() onRightClick = pyqtSignal() def __init__(self, parent=None, timeout=None, delete_on_hide=True, activation_hides=True, dark_mode=False): ''' parent should be a window or None timeout is the amount of time, in milliseconds, to show the widget before it is auto-hidden. None is no timeout. delete_on_hide, if True, will auto-delete this widget after it is hidden due to the timeout or due to calling hide(). ''' super().__init__(parent) self.layout = QtWidgets.QGridLayout(self) if sys.platform != 'darwin': self.layout.setContentsMargins(20, 20, 20, 20) self.animation = QPropertyAnimation(self) self.final_opacity = 1.0 self.popup_opacity = 1.0 self.pointerPos = self.LeftSide self._timer = None self.activation_hides = activation_hides self.dark_mode = dark_mode and sys.platform.lower() != "darwin" #self.resize(200, 50) self.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool) self.setAttribute(Qt.WA_TranslucentBackground) self.setAttribute(Qt.WA_ShowWithoutActivating) self.animation.setTargetObject(self) self.animation.setPropertyName(b'popupOpacity') self.animation.setDuration(200) self.setLayout(self.layout) if parent: parent.installEventFilter(self) self.timeout = timeout self.delete_on_hide = delete_on_hide def getPointerPosition(self): return self.pointerPos def setPointerPosition(self, r): self.pointerPos = r self.update() @pyqtProperty( float ) # Property so that Qt animations work. You may set the actual attrbute directly and ingore this in client code def popupOpacity(self): return self.popup_opacity @popupOpacity.setter def popupOpacity(self, value): self.popup_opacity = value self.setWindowOpacity(value) @pyqtProperty( float ) # Property so that Qt animations work. You may set the actual attrbute directly and ingore this in client code def finalOpacity(self): return self.final_opacity @finalOpacity.setter def finalOpacity(self, value): self.final_opacity = value def paintEvent(self, e): #// Draw the popup here #// You can always pick an image and use drawPixmap to #// draw it in order to make things simpler painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setClipRegion(e.region()) painter.fillRect(e.rect(), QColor(0, 0, 0, 0)) #// Prepare the popup dimensions roundedRectDimensions = QRectF() roundedRectDimensions.setX(self.rect().x() + self.LR_MARGIN) roundedRectDimensions.setY(self.rect().y() + self.TB_MARGIN) roundedRectDimensions.setWidth(self.rect().width() - self.LR_MARGIN * 2.0) roundedRectDimensions.setHeight(self.rect().height() - self.TB_MARGIN * 2.0) pal = QPalette(self.palette()) painter.setBrush( QBrush( pal.color( QPalette.Window if self.dark_mode else QPalette.Mid))) pen = QPen() pen.setColor( pal.color(QPalette.Light if self.dark_mode else QPalette.Button)) pen.setWidth(3) painter.setPen(pen) #// Draw the popup body painter.drawRoundedRect(roundedRectDimensions, self.LR_MARGIN * 2.0, self.TB_MARGIN * 2.0) painter.setPen(Qt.NoPen) painter.setBrush( QBrush( pal.color( QPalette.BrightText if self.dark_mode else QPalette.Dark))) #// Draw the popup pointer based on relPos self.drawPopupPointer(painter) e.accept() def drawPopupPointer(self, p): r = QRectF(self.rect()) if self.pointerPos == self.LeftSide: PPIX_X = self.LR_MARGIN PPIX_Y = PPIX_X * 2.0 points = [ QPointF(r.x() + PPIX_X, r.height() / 2.0 - PPIX_Y / 2.0), QPointF(r.x() + PPIX_X, r.height() / 2.0 + PPIX_Y / 2.0), QPointF(r.x(), r.height() / 2.0), ] p.drawPolygon(*points) if self.pointerPos == self.RightSide: PPIX_X = self.LR_MARGIN PPIX_Y = PPIX_X * 2.0 points = [ QPointF(r.right() - PPIX_X, r.height() / 2.0 - PPIX_Y / 2.0), QPointF(r.right() - PPIX_X, r.height() / 2.0 + PPIX_Y / 2.0), QPointF(r.right(), r.height() / 2.0), ] p.drawPolygon(*points) if self.pointerPos == self.TopSide: PPIX_Y = self.TB_MARGIN PPIX_X = PPIX_Y * 2.0 points = [ QPointF(r.x() + r.width() / 2.0 - PPIX_X / 2.0, r.top() + PPIX_Y), QPointF(r.x() + r.width() / 2.0 + PPIX_X / 2.0, r.top() + PPIX_Y), QPointF(r.x() + r.width() / 2.0, r.top()), ] p.drawPolygon(*points) if self.pointerPos == self.BottomSide: PPIX_Y = self.TB_MARGIN PPIX_X = PPIX_Y * 2.0 points = [ QPointF(r.x() + r.width() / 2.0 - PPIX_X / 2.0, r.bottom() - PPIX_Y), QPointF(r.x() + r.width() / 2.0 + PPIX_X / 2.0, r.bottom() - PPIX_Y), QPointF(r.x() + r.width() / 2.0, r.bottom()), ] p.drawPolygon(*points) def showRelativeTo(self, w): s = self.size() self.moveRelativeTo(w) self.hide() self.show() if self.pointerPos == self.NoPointer: self.raise_() if s != self.size(): # show caused widget resize.. recenter self.moveRelativeTo(w) def moveRelativeTo(self, w): if not w: print( "INTERNAL ERROR: PopupWidget::showRelativeTo got passed a NULL widget pointer! Ignoring.. FIXME!" ) return p = w.mapToGlobal(QPoint(0, 0)) if self.pointerPos == self.LeftSide: p.setX(p.x() + w.width()) p.setY(p.y() - self.height() // 2 + w.height() // 2) elif self.pointerPos == self.RightSide: p.setX(p.x() - self.width()) p.setY(p.y() - self.height() // 2 + w.height() // 2) elif self.pointerPos == self.BottomSide: p.setX(p.x() + w.width() // 2 - self.width() // 2) p.setY(p.y() - self.height()) elif self.pointerPos == self.TopSide: p.setX(p.x() + w.width() // 2 - self.width() // 2) p.setY(p.y() + w.height()) else: #// just center it on the widget p.setX(p.x() + w.width() // 2 - self.width() // 2) p.setY(p.y() + w.height() // 2 - self.height() // 2) if self.isVisible(): self.raise_() self.move(p) def _killTimer(self): if self._timer: self._timer.stop() self._timer.deleteLater() self._timer = None def _startTimer(self, target): self._killTimer() self._timer = QTimer(self) self._timer.setSingleShot(True) def timeout(): self._killTimer() target() self._timer.timeout.connect(timeout) self._timer.start(int(self.timeout)) def showEvent(self, e): super().showEvent(e) if not e.isAccepted(): return if self.animation.state() == QAbstractAnimation.Running: return self.setWindowOpacity(0.0) self.animation.setStartValue(0.0) self.animation.setEndValue(self.final_opacity) self.didShow.emit() self._cleanUp() self.animation.setDirection(QAbstractAnimation.Forward) self.animation.start() if isinstance(self.timeout, (float, int)) and self.timeout > 0: def autoHide(): self._cleanUp() self._startTimer(self.hideAnimated) self.animation.finished.connect(autoHide) def hideEvent(self, e): super().hideEvent(e) if e.isAccepted(): self._cleanUp() if self.delete_on_hide: self.setParent(None) self.deleteLater() def _disconnectFinished(self): try: self.animation.finished.disconnect() except: pass def hideAnimated(self): if self.animation.state() == QAbstractAnimation.Running: return self._cleanUp() self.animation.setDirection(QAbstractAnimation.Backward) self.animation.start() def doHide(): self._cleanUp() self.hide() self.didHide.emit() self.animation.finished.connect(doHide) def eventFilter(self, obj, e): evts = (QEvent.Move, QEvent.Resize, QEvent.Close, QEvent.Hide, QEvent.Show) if self.activation_hides: evts = (*evts, QEvent.WindowStateChange, QEvent.WindowDeactivate) if e.type() in evts: # if the parent window is moved or otherwise touched, make this popup go away self.hideAnimated() return False def mousePressEvent(self, e): if e.button() == Qt.LeftButton: self.onClick.emit() e.accept() elif e.button() == Qt.RightButton: self.onRightClick.emit() e.accept() def _cleanUp(self): ''' Forces animation and timer to stop. This is essential to force the object into a known consistent state ready for deletion, restart of animations, etc. ''' self._disconnectFinished() self._killTimer() self.animation.stop()
def shouldDelete(self, elem: QPropertyAnimation): return elem.state() == QPropertyAnimation.Stopped
class MyWindow(QMainWindow, Ui_MainWindow): def __init__(self, s, parent=None): super(MyWindow, self).__init__(parent) self.setupUi(self) self.s = s # self.bursar_window = QDialog() self.bursar_window = Ui_Dialog() # self.bursar_dialog.setupUi(self.bursar_window) # main game items self.step = QtWidgets.QPushButton(self.centralwidget) self.step.setGeometry(QtCore.QRect(683, 140, 65, 56)) self.step.setStyleSheet("border-image: url(:/figure/stepbott.png);") self.step.setText("") self.step.setObjectName("step") self.yelmoney = QtWidgets.QLabel(self.centralwidget) self.yelmoney.setGeometry(QtCore.QRect(240, 184, 71, 15)) self.yelmoney.setStyleSheet("border-image: url(:/game/white.png);\n" "font: 15pt \"appo paint\";") self.yelmoney.setText("") self.yelmoney.setObjectName("yelmoney") self.yelcash = QtWidgets.QLabel(self.centralwidget) self.yelcash.setGeometry(QtCore.QRect(240, 205, 71, 15)) self.yelcash.setStyleSheet("border-image: url(:/game/white.png);\n" "font: 15pt \"appo paint\";") self.yelcash.setText("") self.yelcash.setObjectName("yelcash") self.yelhouse = QtWidgets.QLabel(self.centralwidget) self.yelhouse.setGeometry(QtCore.QRect(240, 230, 71, 15)) self.yelhouse.setStyleSheet("border-image: url(:/game/white.png);\n" "font: 15pt \"appo paint\";") self.yelhouse.setText("") self.yelhouse.setObjectName("yelhouse") self.yelname = QtWidgets.QLabel(self.centralwidget) self.yelname.setGeometry(QtCore.QRect(233, 156, 81, 21)) self.yelname.setStyleSheet("border-image: url(:/game/white.png);\n" "font: 20pt \"appo paint\";") self.yelname.setText("") self.yelname.setObjectName("yelname") self.yelloan = QtWidgets.QLabel(self.centralwidget) self.yelloan.setGeometry(QtCore.QRect(240, 253, 71, 15)) self.yelloan.setStyleSheet("border-image: url(:/game/white.png);\n" "font: 15pt \"appo paint\";") self.yelloan.setText("") self.yelloan.setObjectName("yelloan") self.bluehouse = QtWidgets.QLabel(self.centralwidget) self.bluehouse.setGeometry(QtCore.QRect(240, 401, 71, 15)) self.bluehouse.setStyleSheet("border-image: url(:/game/white.png);\n" "font: 15pt \"appo paint\";") self.bluehouse.setText("") self.bluehouse.setObjectName("bluehouse") self.bluename = QtWidgets.QLabel(self.centralwidget) self.bluename.setGeometry(QtCore.QRect(231, 329, 81, 21)) self.bluename.setStyleSheet("border-image: url(:/game/white.png);\n" "font: 20pt \"appo paint\";") self.bluename.setText("") self.bluename.setObjectName("bluename") self.bluecash = QtWidgets.QLabel(self.centralwidget) self.bluecash.setGeometry(QtCore.QRect(240, 377, 71, 15)) self.bluecash.setStyleSheet("border-image: url(:/game/white.png);\n" "font: 15pt \"appo paint\";") self.bluecash.setText("") self.bluecash.setObjectName("bluecash") self.bluemoney = QtWidgets.QLabel(self.centralwidget) self.bluemoney.setGeometry(QtCore.QRect(240, 356, 71, 15)) self.bluemoney.setStyleSheet("border-image: url(:/game/white.png);\n" "font: 15pt \"appo paint\";") self.bluemoney.setText("") self.bluemoney.setObjectName("bluemoney") self.blueloan = QtWidgets.QLabel(self.centralwidget) self.blueloan.setGeometry(QtCore.QRect(240, 425, 71, 15)) self.blueloan.setStyleSheet("border-image: url(:/game/white.png);\n" "font: 15pt \"appo paint\";") self.blueloan.setText("") self.blueloan.setObjectName("blueloan") self.info = QtWidgets.QLabel(self.centralwidget) self.info.setGeometry(QtCore.QRect(370, 351, 451, 41)) self.info.setStyleSheet("border-image: url(:/game/infoframe.png);\n" "font: 18pt \"appo paint\"") self.info.setText("") self.info.setObjectName("info") self.buy_info = QtWidgets.QLabel(self.centralwidget) self.buy_info.setGeometry(QtCore.QRect(370, 390, 240, 71)) self.buy_info.setStyleSheet( "border-image: url(:/game/infoframe.png);\n" "font: 18pt \"appo paint\"") self.buy_info.setText("") self.buy_info.setObjectName("buy_info") self.fate = QtWidgets.QLabel(self.centralwidget) self.fate.setGeometry(QtCore.QRect(370, 310, 451, 80)) self.fate.setStyleSheet("border-image: url(:/game/infoframe.png);\n" "font: 18pt \"appo paint\"") self.fate.setText("") self.fate.setObjectName("fate") self.stepnum = QtWidgets.QLabel(self.centralwidget) self.stepnum.setGeometry(QtCore.QRect(764, 142, 51, 51)) self.stepnum.setStyleSheet("font: 40pt \"appo paint\";\n" "border-image: url(:/game/white.png);") self.stepnum.setText("Na") self.stepnum.setObjectName("stepnum") self.turn = QtWidgets.QLabel(self.centralwidget) self.turn.setGeometry(QtCore.QRect(764, 221, 51, 51)) self.turn.setStyleSheet("font: 40pt \"appo paint\";\n" "border-image: url(:/game/white.png);") self.turn.setText("1") self.turn.setObjectName("turn") self.player1 = Player(self.centralwidget) self.player1.set_name("alice") self.player1.initiate_color(1) self.player1.setGeometry(QtCore.QRect(10, 55, 41, 51)) self.player1.setStyleSheet( "border-image: url(:/figure/yellowman.png);") self.player1.setText("") self.player1.setObjectName("player1") self.player2 = Player(self.centralwidget) self.player2.set_name("bob") self.player2.initiate_color(2) self.player2.setGeometry(QtCore.QRect(60, 55, 41, 51)) self.player2.setStyleSheet("border-image: url(:/figure/blueman.png);") self.player2.setText("") self.player2.setObjectName("player2") self.yes = QtWidgets.QPushButton(self.centralwidget) self.yes.setGeometry(QtCore.QRect(650, 410, 71, 31)) self.yes.setStyleSheet("border-image: url(:/figure/yes.png);") self.yes.setText("") self.yes.setObjectName("yes") self.no = QtWidgets.QPushButton(self.centralwidget) self.no.setGeometry(QtCore.QRect(750, 410, 71, 31)) self.no.setStyleSheet("border-image: url(:/figure/no.png);") self.no.setText("") self.no.setObjectName("no") # main game variables self.bdposition = [[60, 55], [150, 55], [270, 55], [360, 55], [480, 55], [580, 55], [680, 55], [780, 55], [900, 55], [900, 140], [900, 235], [900, 335], [900, 445], [900, 550], [780, 550], [680, 550], [580, 550], [480, 550], [360, 550], [270, 550], [150, 550], [60, 550], [60, 445], [60, 335], [60, 235], [60, 150]] self.player_name1 = self.player1.name self.player_name2 = self.player2.name self.turn_count = 0 # self.anim1 = QPropertyAnimation(self.player1, b"geometry") self.anim1.setDuration(1000) self.anim2 = QPropertyAnimation(self.player2, b"geometry") self.anim2.setDuration(1000) # buildings self.green_center = Building(self.centralwidget) self.green_center.setGeometry(QtCore.QRect(121, 105, 88, 15)) self.green_center.setStyleSheet( "background-color: rgb(255, 255, 255);") self.green_center.setText("") self.green_center.setObjectName("green_center") self.public_safety = Building(self.centralwidget) self.public_safety.setGeometry(QtCore.QRect(209, 105, 103, 15)) self.public_safety.setStyleSheet( "background-color: rgb(255, 255, 255);") self.public_safety.setText("") self.public_safety.setObjectName("public_safety") self.student_life = Building(self.centralwidget) self.student_life.setGeometry(QtCore.QRect(312, 105, 138, 15)) self.student_life.setStyleSheet( "background-color: rgb(255, 255, 255);") self.student_life.setText("") self.student_life.setObjectName("student_life") self.Room110_Gallery = Building(self.centralwidget) self.Room110_Gallery.setGeometry(QtCore.QRect(450, 105, 103, 15)) self.Room110_Gallery.setStyleSheet( "background-color: rgb(255, 255, 255);") self.Room110_Gallery.setText("") self.Room110_Gallery.setObjectName("Room110_Gallery") self.mail_room = Building(self.centralwidget) self.mail_room.setGeometry(QtCore.QRect(553, 105, 100, 15)) self.mail_room.setStyleSheet("background-color: rgb(255, 255, 255);") self.mail_room.setText("") self.mail_room.setObjectName("mail_room") self.luckin = Building(self.centralwidget) self.luckin.setGeometry(QtCore.QRect(653, 105, 103, 15)) self.luckin.setStyleSheet("background-color: rgb(255, 255, 255);") self.luckin.setText("") self.luckin.setObjectName("luckin") self.cafeteria = Building(self.centralwidget) self.cafeteria.setGeometry(QtCore.QRect(755, 105, 85, 15)) self.cafeteria.setStyleSheet("background-color: rgb(255, 255, 255);") self.cafeteria.setText("") self.cafeteria.setObjectName("cafeteria") self.auditorium = Building(self.centralwidget) self.auditorium.setGeometry(QtCore.QRect(842, 200, 13, 92)) self.auditorium.setStyleSheet("background-color: rgb(255, 255, 255);") self.auditorium.setText("") self.auditorium.setObjectName("auditorium") self.the_box = Building(self.centralwidget) self.the_box.setGeometry(QtCore.QRect(842, 120, 13, 80)) self.the_box.setStyleSheet("background-color: rgb(255, 255, 255);") self.the_box.setText("") self.the_box.setObjectName("the_box") self.cafe = Building(self.centralwidget) self.cafe.setGeometry(QtCore.QRect(842, 292, 13, 92)) self.cafe.setStyleSheet("background-color: rgb(255, 255, 255);") self.cafe.setText("") self.cafe.setObjectName("cafe") self.bursar = Building(self.centralwidget) self.bursar.setGeometry(QtCore.QRect(842, 384, 13, 92)) self.bursar.setStyleSheet("background-color: rgb(255, 255, 255);") self.bursar.setText("") self.bursar.setObjectName("bursar") self.IT_center = Building(self.centralwidget) self.IT_center.setGeometry(QtCore.QRect(754, 480, 85, 15)) self.IT_center.setStyleSheet("background-color: rgb(255, 255, 255);") self.IT_center.setText("") self.IT_center.setObjectName("IT_center") self.library = Building(self.centralwidget) self.library.setGeometry(QtCore.QRect(651, 480, 103, 15)) self.library.setStyleSheet("background-color: rgb(255, 255, 255);") self.library.setText("") self.library.setObjectName("library") self.arc = Building(self.centralwidget) self.arc.setGeometry(QtCore.QRect(553, 480, 99, 15)) self.arc.setStyleSheet("background-color: rgb(255, 255, 255);") self.arc.setText("") self.arc.setObjectName("arc") self.advisor_office = Building(self.centralwidget) self.advisor_office.setGeometry(QtCore.QRect(452, 480, 101, 15)) self.advisor_office.setStyleSheet( "background-color: rgb(255, 255, 255);") self.advisor_office.setText("") self.advisor_office.setObjectName("adviser_office") self.H_W_center = Building(self.centralwidget) self.H_W_center.setGeometry(QtCore.QRect(348, 480, 104, 15)) self.H_W_center.setStyleSheet("background-color: rgb(255, 255, 255);") self.H_W_center.setText("") self.H_W_center.setObjectName("H_W_center") self.CDC = Building(self.centralwidget) self.CDC.setGeometry(QtCore.QRect(208, 480, 140, 15)) self.CDC.setStyleSheet("background-color: rgb(255, 255, 255);") self.CDC.setText("") self.CDC.setObjectName("CDC") self.mac_lab = Building(self.centralwidget) self.mac_lab.setGeometry(QtCore.QRect(106, 480, 102, 15)) self.mac_lab.setStyleSheet("background-color: rgb(255, 255, 255);") self.mac_lab.setText("") self.mac_lab.setObjectName("mac_lab") self.ally_lounge = Building(self.centralwidget) self.ally_lounge.setGeometry(QtCore.QRect(0, 480, 106, 15)) self.ally_lounge.setStyleSheet("background-color: rgb(255, 255, 255);") self.ally_lounge.setText("") self.ally_lounge.setObjectName("ally_lounge") self.fos_lab = Building(self.centralwidget) self.fos_lab.setGeometry(QtCore.QRect(105, 387, 15, 94)) self.fos_lab.setStyleSheet("background-color: rgb(255, 255, 255);") self.fos_lab.setText("") self.fos_lab.setObjectName("fos_lab") self.piano_room = Building(self.centralwidget) self.piano_room.setGeometry(QtCore.QRect(105, 293, 15, 94)) self.piano_room.setStyleSheet("background-color: rgb(255, 255, 255);") self.piano_room.setText("") self.piano_room.setObjectName("piano_room") self.ima_lab = Building(self.centralwidget) self.ima_lab.setGeometry(QtCore.QRect(105, 199, 15, 94)) self.ima_lab.setStyleSheet("background-color: rgb(255, 255, 255);") self.ima_lab.setText("") self.ima_lab.setObjectName("ima_lab") self.f15 = Building(self.centralwidget) self.f15.setGeometry(QtCore.QRect(105, 122, 15, 77)) self.f15.setStyleSheet("background-color: rgb(255, 255, 255);") self.f15.setText("") self.f15.setObjectName("f15") self.bdposition = [[60, 55], [150, 55], [270, 55], [360, 55], [480, 55], [580, 55], [680, 55], [780, 55], [900, 55], [900, 140], [900, 235], [900, 335], [900, 445], [900, 550], [780, 550], [680, 550], [580, 550], [480, 550], [360, 550], [270, 550], [150, 550], [60, 550], [60, 425], [60, 335], [60, 235], [60, 150]] self.building_dic = { (60, 55): None, (150, 55): self.green_center, (270, 55): self.public_safety, (360, 55): self.student_life, (480, 55): self.Room110_Gallery, (580, 55): self.mail_room, (680, 55): self.luckin, (780, 55): self.cafeteria, (900, 55): None, (900, 140): self.the_box, (900, 235): self.auditorium, (900, 335): self.cafe, (900, 445): self.bursar, (900, 550): None, (780, 550): self.IT_center, (680, 550): self.library, (580, 550): self.arc, (480, 550): self.advisor_office, (360, 550): self.H_W_center, (270, 550): self.CDC, (150, 550): self.mac_lab, (60, 550): self.ally_lounge, (60, 425): self.fos_lab, (60, 335): self.piano_room, (60, 235): self.ima_lab, (60, 150): self.f15 } for u, v in self.building_dic.items(): x, y = u if v is not None: v.x_position = x v.y_position = y # main game set up self.hide_main_game() self.connect_buttons() def turn(self): return self.player_name1 if self.turn_count % 2 == 0 else self.player_name2 def setName1(self, name): self.player_name1 = name self.player1.name = name def setName2(self, name): self.player_name2 = name self.player2.name = name def hide_cover(self): self.exit.hide() self.play.hide() def show_cover(self): self.exit.show() self.play.show() def hide_main_game(self): self.step.hide() self.turn.hide() self.yelcash.hide() self.yelmoney.hide() self.yelname.hide() self.yelhouse.hide() self.bluecash.hide() self.bluehouse.hide() self.bluemoney.hide() self.bluename.hide() self.info.hide() self.stepnum.hide() self.player1.hide() self.player2.hide() for v in self.building_dic.values(): if v is not None: v.hide() self.yes.hide() self.no.hide() self.buy_info.hide() self.fate.hide() def set_player1_info(self): self.yelname.setText(str(self.player1.name)) self.yelcash.setText(str(int(self.player1.cash))) self.yelmoney.setText(str(int(self.player1.get_money()))) self.yelhouse.setText(str(int(self.player1.get_house()))) self.yelloan.setText(str(int(self.player1.loaned_money))) def set_player2_info(self): self.bluename.setText(str(self.player2.name)) self.bluecash.setText(str(int(self.player2.cash))) self.bluemoney.setText(str(int(self.player2.get_money()))) self.bluehouse.setText(str(int(self.player2.get_house()))) self.blueloan.setText(str(int(self.player2.loaned_money))) def show_main_game(self): self.set_player1_info() self.set_player2_info() self.step.show() self.turn.show() self.yelcash.show() self.yelmoney.show() self.yelname.show() self.yelhouse.show() self.bluecash.show() self.bluehouse.show() self.bluemoney.show() self.bluename.show() self.info.show() self.stepnum.show() self.player1.show() self.player2.show() for v in self.building_dic.values(): if v is not None: v.show() # self.yes.show() # self.no.show() self.buy_info.show() self.fate.show() def connect_buttons(self): self.play.clicked.connect(self.start) self.step.clicked.connect(self.roll) self.exit.clicked.connect(self.end) def start(self): d = {"action": "gaming", "update": "start"} mysend(self.s, json.dumps(d)) self.graphicsView.setStyleSheet( "border-image: url(:/figure/life_in_NYUSH_v2.jpg);") self.hide_cover() self.show_main_game() # self.bursar_window.show() # self.repaint() def roll(self): self.fate.hide() if self.anim1.state() != self.anim1.Running and self.anim2.state( ) != self.anim2.Running: num = random.randint(1, 6) # num = 6 if self.turn_count % 2 == 0: curr_x, curr_y = self.bdposition[self.player1.count] next_count = (self.player1.count + num) % 26 bankrupt = self.player1.bankrupt() if next_count < self.player1.count: if bankrupt: self.info.setText("{} bankrupt, {} wins.".format( self.player1.name, self.player2.name)) self.info.show() self.yes.clicked.connect(self.end) self.yes.show() else: success = self.player1.pay_debt() if success is not None: if success: self.fate.setText( "Successfully pay back you debt.") else: self.fate.setText( "No enough money to pay your debt") self.fate.show() self.set_player1_info() self.set_player2_info() else: bankrupt = False if not bankrupt: next_x, next_y = self.bdposition[next_count] self.stepnum.setText("{}".format(num)) self.player1.count = next_count self.anim1.setStartValue(QRect(curr_x, curr_y, 41, 51)) self.anim1.setEndValue(QRect(next_x, next_y, 41, 51)) self.anim1.start() self.pass_building(self.player1, next_x, next_y) else: curr_x, curr_y = self.bdposition[self.player2.count] next_count = (self.player2.count + num) % 26 bankrupt = self.player2.bankrupt() if next_count < self.player2.count: if bankrupt: self.info.setText("{} bankrupt, {} wins.".format( self.player2.name, self.player1.name)) self.info.show() self.yes.clicked.connect(self.end) self.yes.show() else: success = self.player2.pay_debt() if success is not None: if success: self.fate.setText( "Successfully pay back you debt.") else: self.fate.setText( "No enough money to pay your debt") self.fate.show() self.set_player1_info() self.set_player2_info() else: bankrupt = False if not bankrupt: next_x, next_y = self.bdposition[next_count] self.stepnum.setText("{}".format(num)) self.player2.count = next_count self.anim2.setStartValue(QRect(curr_x, curr_y, 41, 51)) self.anim2.setEndValue(QRect(next_x, next_y, 41, 51)) self.anim2.start() self.pass_building(self.player2, next_x, next_y) self.step.setEnabled(False) d = {"action": "gaming", "update": "roll", "num": num} mysend(self.s, json.dumps(d)) self.turn.setText("{}".format(self.turn_count // 2 + 1)) self.turn_count += 1 def pass_building(self, player, x, y): assert isinstance(player, Player) if player.name == self.player_name1: other = self.player2 else: other = self.player1 building = self.building_dic[(x, y)] # self.fate.setText("") self.info.setText("") self.buy_info.setText("") if building is not None: # fate part if building.objectName() == "bursar": self.bursar_window.show() self.bursar_window.set_info_text(player.get_max_loan(), player.get_money()) # player.cash += 1000 # self.fate.setText("Get funded by bursar.") self.yes.show() self.yes.clicked.connect(lambda: self.finish_loan(player)) self.buy_info.setText("Click here to finish loan.") self.buy_info.show() # other fates here elif building.objectName() == "CDC": chance, money = self.chance(player) d = { "action": "gaming", "update": "passing_building", "x": x, "y": y, "chance": chance, "money": money, "player": 1 if player.name == self.player1.name else 2 } mysend(self.s, json.dumps(d)) self.fate.setText(chance) self.fate.show() self.yes.show() self.yes.clicked.connect(self.confirm) elif building.objectName() == "student_life": destiny, money = self.destiny(player) d = { "action": "gaming", "update": "passing_building", "x": x, "y": y, "destiny": destiny, "money": money, "player": 1 if player.name == self.player1.name else 2 } mysend(self.s, json.dumps(d)) self.fate.setText(destiny) self.fate.show() self.yes.show() self.yes.clicked.connect(self.confirm) # update once after fate. # buy land # if building.objectName() not in ["bursar", "CDC", "student_life"]: else: # self.fate.setText("") if building.owner is None: building_name = building.objectName() self.info.setText( "You are passing {0}.".format(building_name)) self.buy_info.setText("Do you want to buy?") self.yes.clicked.connect( lambda: self.buy(player, building)) self.no.clicked.connect( lambda: self.reject_buy(x, y, player)) self.buy_info.show() self.info.show() self.yes.show() self.no.show() # passing others' building: fined elif player.name != building.owner: fined_money = player.fine_money(building.level) d = { "action": "gaming", "update": "passing_building", "x": x, "y": y, "player": 1 if player.name == self.player1.name else 2 } mysend(self.s, json.dumps(d)) other.cash += fined_money self.info.setText( "You are passing others building, fined ${}.".format( fined_money)) self.info.show() self.yes.show() self.yes.clicked.connect(self.confirm) # self.step.setEnabled(True) # print(self.step.isEnabled()) # self.step.repaint() # print(self.step.isEnabled()) # passing my building: upgrade elif player.name == building.owner: self.info.setText("You are passing your own building.") self.buy_info.setText("Do you want to upgrade?") self.buy_info.show() self.info.show() self.yes.clicked.connect( lambda: self.buy(player, building)) self.no.clicked.connect( lambda: self.reject_buy(x, y, player)) self.yes.show() self.no.show() self.set_player1_info() self.set_player2_info() else: d = { "action": "gaming", "update": "passing_building", "x": x, "y": y, "player": 1 if player.name == self.player1.name else 2 } mysend(self.s, json.dumps(d)) self.info.setText("No buildings to buy here.") self.info.show() self.yes.show() self.yes.clicked.connect(self.confirm) def pass_building_update(self, player, x, y, msg): assert isinstance(player, Player) if player.name == self.player_name1: other = self.player2 else: other = self.player1 building = self.building_dic[(x, y)] self.fate.setText("") self.info.setText("") self.buy_info.setText("") if building is not None: # fate part if building.objectName() == "bursar": loan_result = msg["loan"] if loan_result is not None: self.fate.setText( "Others have successfully loan ${}.".format( loan_result)) self.fate.show() player.loan_money(loan_result) player.cash += loan_result else: self.fate.setText( "The amount others claim exceeds the \nlimit he can loan." ) self.fate.show() # other fates here elif building.objectName() == "CDC": chance = msg["chance"] earn = msg["money"] self.fate.setText(chance) self.fate.show() player.cash += earn elif building.objectName() == "student_life": destiny = msg["destiny"] lost = msg["money"] self.fate.setText(destiny) self.fate.show() player.fine_money(level=None, money=lost) # update once after fate. # buy land # if building.objectName() not in ["bursar", "CDC", "student_life"]: else: # print(building.owner, player.name, msg) if building.owner is None: building_name = building.objectName() if msg["buy"]: try: player.buy_building(building) self.info.setText( "Others choose to buy {0}.".format( building_name)) self.set_player1_info() self.set_player2_info() except ValueError: self.info.setText("No enough money to buy.") else: self.info.setText( "Others choose not to buy {0}".format( building_name)) self.info.show() # passing others' building: fined elif player.name != building.owner: fined_money = player.fine_money(building.level) other.cash += fined_money self.info.setText( "Others are passing your building, get ${}.".format( fined_money)) self.info.show() # passing my building: upgrade elif player.name == building.owner: if msg["buy"]: try: player.buy_building(building) self.info.setText("Others choose to upgrade.") self.set_player1_info() self.set_player2_info() except ValueError: self.info.setText("No enough money to buy.") else: self.info.setText("Other chooses not to upgrade.") self.info.show() self.set_player1_info() self.set_player2_info() else: self.info.setText("No buildings to buy here.") self.info.show() self.step.setEnabled(True) def buy(self, player, building): d = { "action": "gaming", "update": "passing_building", "x": building.x_position, "y": building.y_position, "buy": True, "player": 1 if player.name == self.player1.name else 2 } mysend(self.s, json.dumps(d)) assert isinstance(player, Player) assert isinstance(building, Building) try: player.buy_building(building) self.info.hide() self.set_player1_info() self.set_player2_info() except ValueError: self.info.setText("No enough money to buy.") self.yes.disconnect() self.yes.hide() self.no.hide() self.no.disconnect() self.buy_info.hide() self.fate.hide() def confirm(self): self.fate.hide() self.fate.setText("") self.yes.hide() self.no.hide() self.yes.disconnect() self.buy_info.hide() self.info.hide() def reject_buy(self, x, y, player): d = { "action": "gaming", "update": "passing_building", "x": x, "y": y, "buy": False, "player": 1 if player.name == self.player1.name else 2 } mysend(self.s, json.dumps(d)) self.fate.hide() self.fate.setText("") self.yes.hide() self.no.hide() self.yes.disconnect() self.no.disconnect() self.buy_info.hide() self.info.hide() def finish_loan(self, player): assert isinstance(player, Player) loan_result = self.bursar_window.result if loan_result is not None: self.fate.setText( "You have successfully loan ${}.".format(loan_result)) self.fate.show() player.loan_money(loan_result) player.cash += loan_result else: self.fate.setText( "The amount you claim exceeds the \nlimit you can loan.") self.fate.show() self.set_player1_info() self.set_player2_info() self.yes.hide() self.no.hide() self.yes.disconnect() self.buy_info.hide() self.info.hide() d = { "action": "gaming", "update": "passing_building", "x": 900, "y": 445, "loan": loan_result, "player": 1 if player.name == self.player1.name else 2 } mysend(self.s, json.dumps(d)) def destiny(self, player): assert isinstance(player, Player) # list_destiny = [['You need to go to the ARC to revise your essay.', 2], # ['You find hair in your meal and decide to go to Health & Wellness for psychological relief.', # 1], # ['You were reported for your behavior when participating the student union election.', 1], # ['You need to go to the eastern-oasis for military training.', 2], # ['You were asked to participate in the graduation ceremony at the Oriental Pearl Tower.', 1], # ['You plan to practice your piano at the piano room.', 1], # ['You plan to buy luck-in coffee.', 1], # ['You were collapsed by the smell of the moor besom in front of AB.', 1], list_destiny = [ [ 'Your stuff in 314 has been removed \nand lost, you lost $2000.', 2000 ], ['You are informed to pay \ntuition fee for $3000.', 3000], [ 'You bought Mate Tea for $2000 during \nGPS to get rid of sleepiness.', 2000 ], [ 'You missed shuttle and get a \ntaxi back to the dorm for $5000.', 5000 ], ['You bought bubble tea for $1000.', 1000], ['You purchased textbook for $5000.', 5000], [ 'You lost your campus card, \nyou have to go to Public Safety \nto replace it for $5000.', 5000 ], [ 'You bought tickets and clothes \nfor Spring Formal for $1000.', 1000 ] ] chosen_destiny = random.choice(list_destiny) lost = chosen_destiny[1] player.fine_money(level=None, money=lost) return tuple(chosen_destiny) def chance(self, player): assert isinstance(player, Player) list_chance = [ [ 'You have been offered for the position of Residence-hall Assistant\nand get payment for $3000!', 3000 ], [ 'You have been recommended an internship by the professor\nand get payment for $2000!', 2000 ], ['You get the tuition-fee refund for $2000!', 2000], [ 'You have been offered for the position of Orientation Ambassador\nand get payment for $1500!', 1500 ], [ 'You have been offered for the position of researching assistant\nand get payment for $1000!', 1000 ], [ 'You have been offered a on-campus job and get payment for $1000!', 1000 ], ['You win the prize of $1000 by taking a survey!', 1000], [ 'You participate in an event and get free lunch of $500 in return!', 5000 ], ['You get additional $5000 voucher from Starbucks!', 5000] ] chosen_chance = random.choice(list_chance) earn = chosen_chance[1] player.cash += earn return tuple(chosen_chance) def update_board(self, msg): if msg["update"] == "roll": num = msg["num"] if self.turn_count % 2 == 0: curr_x, curr_y = self.bdposition[self.player1.count] next_count = (self.player1.count + num) % 26 bankrupt = self.player1.bankrupt() if next_count < self.player1.count: if bankrupt: self.info.setText("{} bankrupt, {} wins.".format( self.player1.name, self.player2.name)) self.info.show() self.yes.clicked.connect(self.end) self.yes.show() else: success = self.player1.pay_debt() if success is not None: if success: self.fate.setText( "{} successfully pays back his debt.". format(self.player1.name)) else: self.fate.setText( "{} has no enough money to pay debt.". format(self.player1.name)) self.fate.show() self.set_player1_info() self.set_player2_info() else: bankrupt = False if not bankrupt: next_x, next_y = self.bdposition[next_count] self.stepnum.setText("{}".format(num)) self.player1.count = next_count self.anim1.setStartValue(QRect(curr_x, curr_y, 41, 51)) self.anim1.setEndValue(QRect(next_x, next_y, 41, 51)) self.anim1.start() else: curr_x, curr_y = self.bdposition[self.player2.count] next_count = (self.player2.count + num) % 26 # pay debt and check bankrupt bankrupt = self.player2.bankrupt() if next_count < self.player2.count: if bankrupt: self.info.setText("{} bankrupt, {} wins.".format( self.player2.name, self.player1.name)) self.info.show() self.yes.clicked.connect(self.end) self.yes.show() else: success = self.player2.pay_debt() if success is not None: if success: self.fate.setText( "{} successfully pays back his debt.". format(self.player2.name)) else: self.fate.setText( "{} has no enough money to pay debt.". format(self.player2.name)) self.fate.show() self.set_player1_info() self.set_player2_info() else: bankrupt = False if not bankrupt: next_x, next_y = self.bdposition[next_count] self.stepnum.setText("{}".format(num)) self.player2.count = next_count self.anim2.setStartValue(QRect(curr_x, curr_y, 41, 51)) self.anim2.setEndValue(QRect(next_x, next_y, 41, 51)) self.anim2.start() self.turn.setText("{}".format(self.turn_count // 2 + 1)) self.turn_count += 1 elif msg["update"] == "start": self.graphicsView.setStyleSheet( "border-image: url(:/figure/life_in_NYUSH_v2.jpg);") self.hide_cover() self.show_main_game() self.step.setEnabled(False) elif msg["update"] == "stop": self.hide() elif msg["update"] == "passing_building": # print("I am here passing_building") # print(msg) if msg["player"] == 1: player = self.player1 else: player = self.player2 x = msg["x"] y = msg["y"] self.pass_building_update(player, x, y, msg) self.step.setEnabled(True) def end(self): # d = {"action": "gaming", "update": "stop"} # mysend(self.s, json.dumps(d)) self.close() self.hide()
class ScatterDataModifier(QObject): shadowQualityChanged = pyqtSignal() def __init__(self, scatter): super(ScatterDataModifier, self).__init__() self.m_graph = scatter self.m_inputHandler = CustomInputHandler() self.m_graph.activeTheme().setType(Q3DTheme.ThemeDigia) self.m_graph.setShadowQuality(QAbstract3DGraph.ShadowQualityMedium) self.m_graph.scene().activeCamera().setCameraPreset( Q3DCamera.CameraPresetFront) self.m_graph.setAxisX(QValue3DAxis()) self.m_graph.setAxisY(QValue3DAxis()) self.m_graph.setAxisZ(QValue3DAxis()) self.m_graph.axisX().setRange(-10.0, 10.0) self.m_graph.axisY().setRange(-5.0, 5.0) self.m_graph.axisZ().setRange(-5.0, 5.0) series = QScatter3DSeries() series.setItemLabelFormat("@xLabel, @yLabel, @zLabel") series.setMesh(QAbstract3DSeries.MeshCube) series.setItemSize(0.15) self.m_graph.addSeries(series) self.m_animationCameraX = QPropertyAnimation( self.m_graph.scene().activeCamera(), 'xRotation') self.m_animationCameraX.setDuration(20000) self.m_animationCameraX.setStartValue(0.0) self.m_animationCameraX.setEndValue(360.0) self.m_animationCameraX.setLoopCount(-1) upAnimation = QPropertyAnimation(self.m_graph.scene().activeCamera(), 'yRotation') upAnimation.setDuration(9000) upAnimation.setStartValue(5.0) upAnimation.setEndValue(45.0) downAnimation = QPropertyAnimation(self.m_graph.scene().activeCamera(), 'yRotation') downAnimation.setDuration(9000) downAnimation.setStartValue(45.0) downAnimation.setEndValue(5.0) self.m_animationCameraY = QSequentialAnimationGroup() self.m_animationCameraY.setLoopCount(-1) self.m_animationCameraY.addAnimation(upAnimation) self.m_animationCameraY.addAnimation(downAnimation) self.m_animationCameraX.start() self.m_animationCameraY.start() self.m_graph.setActiveInputHandler(self.m_inputHandler) self.m_selectionTimer = QTimer() self.m_selectionTimer.setInterval(10) self.m_selectionTimer.timeout.connect(self.triggerSelection) self.m_selectionTimer.start() def start(self): self.addData() def addData(self): dataArray = [] stream = QTextStream() dataFile = QFile(QFileInfo(__file__).absolutePath() + '/data.txt') if dataFile.open(QIODevice.ReadOnly | QIODevice.Text): stream.setDevice(dataFile) while not stream.atEnd(): line = stream.readLine() if line.startswith('#'): continue strList = line.split(',') # Each line has three data items: xPos, yPos and zPos values. if len(strList) < 3: continue position = QVector3D(float(strList[0]), float(strList[1]), float(strList[2])) dataArray.append(QScatterDataItem(position)) self.m_graph.seriesList()[0].dataProxy().resetArray(dataArray) def toggleCameraAnimation(self): if self.m_animationCameraX.state() != QAbstractAnimation.Paused: self.m_animationCameraX.pause() self.m_animationCameraY.pause() else: self.m_animationCameraX.resume() self.m_animationCameraY.resume() def triggerSelection(self): self.m_graph.scene().setSelectionQueryPosition( self.m_inputHandler.inputPosition()) def shadowQualityUpdatedByVisual(self, sq): self.shadowQualityChanged.emit(int(sq)) def changeShadowQuality(self, quality): self.m_graph.setShadowQuality(QAbstract3DGraph.ShadowQuality(quality))
class AnimatedToggle(QCheckBox): _transparent_pen = QPen(Qt.transparent) _light_grey_pen = QPen(Qt.lightGray) def __init__(self, parent=None, bar_color=Qt.gray, checked_color="#00B0FF", handle_color=Qt.white, pulse_unchecked_color="#44999999", pulse_checked_color="#4400B0EE" ): super().__init__(parent) # Save our properties on the object via self, so we can access them later # in the paintEvent. self._bar_brush = QBrush(bar_color) self._bar_checked_brush = QBrush(QColor(checked_color).lighter()) self._handle_brush = QBrush(handle_color) self._handle_checked_brush = QBrush(QColor(checked_color)) self._pulse_unchecked_animation = QBrush(QColor(pulse_unchecked_color)) self._pulse_checked_animation = QBrush(QColor(pulse_checked_color)) # Setup the rest of the widget. self.setContentsMargins(8, 0, 8, 0) self._handle_position = 0 self._pulse_radius = 0 self.animation = QPropertyAnimation(self, b"handle_position", self) self.animation.setEasingCurve(QEasingCurve.InOutCubic) self.animation.setDuration(200) # time in ms self.pulse_anim = QPropertyAnimation(self, b"pulse_radius", self) self.pulse_anim.setDuration(350) # time in ms self.pulse_anim.setStartValue(10) self.pulse_anim.setEndValue(20) self.animations_group = QSequentialAnimationGroup() self.animations_group.addAnimation(self.animation) self.animations_group.addAnimation(self.pulse_anim) self.stateChanged.connect(self.setup_animation) def sizeHint(self): return QSize(58, 45) def hitButton(self, pos: QPoint): return self.contentsRect().contains(pos) @pyqtSlot(int) def setup_animation(self, value): self.animations_group.stop() if value: self.animation.setEndValue(1) else: self.animation.setEndValue(0) self.animations_group.start() def paintEvent(self, e: QPaintEvent): contRect = self.contentsRect() handleRadius = round(0.24 * contRect.height()) p = QPainter(self) p.setRenderHint(QPainter.Antialiasing) p.setPen(self._transparent_pen) barRect = QRectF( 0, 0, contRect.width() - handleRadius, 0.40 * contRect.height() ) barRect.moveCenter(contRect.center()) rounding = barRect.height() / 2 # the handle will move along this line trailLength = contRect.width() - 2 * handleRadius xPos = contRect.x() + handleRadius + trailLength * self._handle_position if self.pulse_anim.state() == QPropertyAnimation.Running: p.setBrush( self._pulse_checked_animation if self.isChecked() else self._pulse_unchecked_animation) p.drawEllipse(QPointF(xPos, barRect.center().y()), self._pulse_radius, self._pulse_radius) if self.isChecked(): p.setBrush(self._bar_checked_brush) p.drawRoundedRect(barRect, rounding, rounding) p.setBrush(self._handle_checked_brush) else: p.setBrush(self._bar_brush) p.drawRoundedRect(barRect, rounding, rounding) p.setPen(self._light_grey_pen) p.setBrush(self._handle_brush) p.drawEllipse( QPointF(xPos, barRect.center().y()), handleRadius, handleRadius) p.end() @pyqtProperty(float) def handle_position(self): return self._handle_position @handle_position.setter def handle_position(self, pos): """change the property we need to trigger QWidget.update() method, either by: 1- calling it here [ what we doing ]. 2- connecting the QPropertyAnimation.valueChanged() signal to it. """ self._handle_position = pos self.update() @pyqtProperty(float) def pulse_radius(self): return self._pulse_radius @pulse_radius.setter def pulse_radius(self, pos): self._pulse_radius = pos self.update()
class NodeBodyItem(GraphicsPathObject): """ The central part (body) of the `NodeItem`. """ def __init__(self, parent=None): GraphicsPathObject.__init__(self, parent) assert(isinstance(parent, NodeItem)) self.__processingState = 0 self.__progress = -1 self.__animationEnabled = False self.__isSelected = False self.__hasFocus = False self.__hover = False self.__shapeRect = QRectF(-10, -10, 20, 20) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setPen(QPen(Qt.NoPen)) self.setPalette(default_palette()) self.shadow = QGraphicsDropShadowEffect( blurRadius=3, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0), ) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(True) self.__blurAnimation = QPropertyAnimation(self.shadow, b"blurRadius", self) self.__blurAnimation.setDuration(100) self.__blurAnimation.finished.connect(self.__on_finished) self.__pingAnimation = QPropertyAnimation(self, b"scale", self) self.__pingAnimation.setDuration(250) self.__pingAnimation.setKeyValues([(0.0, 1.0), (0.5, 1.1), (1.0, 1.0)]) # TODO: The body item should allow the setting of arbitrary painter # paths (for instance rounded rect, ...) def setShapeRect(self, rect): """ Set the item's shape `rect`. The item should be confined within this rect. """ path = QPainterPath() path.addEllipse(rect) self.setPath(path) self.__shapeRect = rect def setPalette(self, palette): """ Set the body color palette (:class:`QPalette`). """ self.palette = palette self.__updateBrush() def setAnimationEnabled(self, enabled): """ Set the node animation enabled. """ if self.__animationEnabled != enabled: self.__animationEnabled = enabled def setProcessingState(self, state): """ Set the processing state of the node. """ if self.__processingState != state: self.__processingState = state if not state and self.__animationEnabled: self.ping() def setProgress(self, progress): """ Set the progress indicator state of the node. `progress` should be a number between 0 and 100. """ self.__progress = progress self.update() def ping(self): """ Trigger a 'ping' animation. """ animation_restart(self.__pingAnimation) def hoverEnterEvent(self, event): self.__hover = True self.__updateShadowState() return GraphicsPathObject.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): self.__hover = False self.__updateShadowState() return GraphicsPathObject.hoverLeaveEvent(self, event) def paint(self, painter, option, widget): """ Paint the shape and a progress meter. """ # Let the default implementation draw the shape if option.state & QStyle.State_Selected: # Prevent the default bounding rect selection indicator. option.state = option.state ^ QStyle.State_Selected GraphicsPathObject.paint(self, painter, option, widget) if self.__progress >= 0: # Draw the progress meter over the shape. # Set the clip to shape so the meter does not overflow the shape. painter.setClipPath(self.shape(), Qt.ReplaceClip) color = self.palette.color(QPalette.ButtonText) pen = QPen(color, 5) painter.save() painter.setPen(pen) painter.setRenderHints(QPainter.Antialiasing) span = max(1, int(self.__progress * 57.60)) painter.drawArc(self.__shapeRect, 90 * 16, -span) painter.restore() def __updateShadowState(self): if self.__hasFocus: color = QColor(FOCUS_OUTLINE_COLOR) self.setPen(QPen(color, 1.5)) else: self.setPen(QPen(Qt.NoPen)) radius = 3 enabled = False if self.__isSelected: enabled = True radius = 7 if self.__hover: radius = 17 enabled = True if enabled and not self.shadow.isEnabled(): self.shadow.setEnabled(enabled) if self.__animationEnabled: if self.__blurAnimation.state() == QPropertyAnimation.Running: self.__blurAnimation.pause() self.__blurAnimation.setStartValue(self.shadow.blurRadius()) self.__blurAnimation.setEndValue(radius) self.__blurAnimation.start() else: self.shadow.setBlurRadius(radius) def __updateBrush(self): palette = self.palette if self.__isSelected: cg = QPalette.Active else: cg = QPalette.Inactive palette.setCurrentColorGroup(cg) c1 = palette.color(QPalette.Light) c2 = palette.color(QPalette.Button) grad = radial_gradient(c2, c1) self.setBrush(QBrush(grad)) # TODO: The selected and focus states should be set using the # QStyle flags (State_Selected. State_HasFocus) def setSelected(self, selected): """ Set the `selected` state. .. note:: The item does not have `QGraphicsItem.ItemIsSelectable` flag. This property is instead controlled by the parent NodeItem. """ self.__isSelected = selected self.__updateBrush() def setHasFocus(self, focus): """ Set the `has focus` state. .. note:: The item does not have `QGraphicsItem.ItemIsFocusable` flag. This property is instead controlled by the parent NodeItem. """ self.__hasFocus = focus self.__updateShadowState() def __on_finished(self): if self.shadow.blurRadius() == 0: self.shadow.setEnabled(False)