commit d30d6f290dc05b50556455c2f095f144dbb30535 Author: phil616 Date: Fri Jun 27 15:59:08 2025 +0800 update repo diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..82b660a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.gif filter=lfs diff=lfs merge=lfs -text +*.svg filter=lfs diff=lfs merge=lfs -text \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5509140 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.DS_Store diff --git a/GIF/angry.gif b/GIF/angry.gif new file mode 100644 index 0000000..dba4b44 --- /dev/null +++ b/GIF/angry.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2eeae80ba146a482e091e5a5b25ce7d0cfb51205ed892d6d9901ef7f3af0be5 +size 27104 diff --git a/GIF/appear.gif b/GIF/appear.gif new file mode 100644 index 0000000..c8a4c9c --- /dev/null +++ b/GIF/appear.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd45f2dd0e72e7b8ef148caedd4d0501522374122e5a530f1066e82aa917385e +size 73122 diff --git a/GIF/baji.gif b/GIF/baji.gif new file mode 100644 index 0000000..3aeebf8 --- /dev/null +++ b/GIF/baji.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc976797e88048106db2fac2cd917368727c2e90890db57ce7919eadc1165531 +size 50789 diff --git a/GIF/boring.gif b/GIF/boring.gif new file mode 100644 index 0000000..65129e9 --- /dev/null +++ b/GIF/boring.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32f0b8458082e678f53f26882bfbfc7f088dd40e752c1f1760b4ab5c5a34a326 +size 30980 diff --git a/GIF/cake.gif b/GIF/cake.gif new file mode 100644 index 0000000..14c9352 --- /dev/null +++ b/GIF/cake.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d31191ec67e69f48ff852185a44bffd57a99b4cb1961f8ae3d9858ddcccd4e3a +size 144620 diff --git a/GIF/call.gif b/GIF/call.gif new file mode 100644 index 0000000..d92f180 --- /dev/null +++ b/GIF/call.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:725c5ea8ae54882e8ed6736e0e2232acb14ae40ea4a7f9be01dae35d415db368 +size 25647 diff --git a/GIF/charge.gif b/GIF/charge.gif new file mode 100644 index 0000000..b002ce4 --- /dev/null +++ b/GIF/charge.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:699b245480151fb5fb6dc59be635be074b6f4cc6966cdc329dec430e5da32e39 +size 267219 diff --git a/GIF/crying.gif b/GIF/crying.gif new file mode 100644 index 0000000..9bd4584 --- /dev/null +++ b/GIF/crying.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ad4309472a75856e7d533d113b499927b31ad34dbaefd5772051776f543b1e6 +size 75254 diff --git a/GIF/crying2.gif b/GIF/crying2.gif new file mode 100644 index 0000000..c954836 --- /dev/null +++ b/GIF/crying2.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d620cf74d1fe2045d9d86eab6865d4e01fdca3e778956f6582dd6909bc998225 +size 56038 diff --git a/GIF/exercise.gif b/GIF/exercise.gif new file mode 100644 index 0000000..9856826 --- /dev/null +++ b/GIF/exercise.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41e88140d2a796a2442e83644ecbecfc2a7d106d116455907d2c38743a55bb4c +size 216941 diff --git a/GIF/normal.gif b/GIF/normal.gif new file mode 100644 index 0000000..f612626 --- /dev/null +++ b/GIF/normal.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:880ed3d23eb692dd10fafdad892d50a24d91c45507e4d986fbae426497610218 +size 270507 diff --git a/GIF/stick.gif b/GIF/stick.gif new file mode 100644 index 0000000..0a172c5 --- /dev/null +++ b/GIF/stick.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e733706153e286180ded68e6a4ddb5b345d6d646cb538e2fc59e3e4a9faa8ab +size 127026 diff --git a/GIF/walkdog.gif b/GIF/walkdog.gif new file mode 100644 index 0000000..f057db7 --- /dev/null +++ b/GIF/walkdog.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e90e077437f1230a7ae41e803d838c216cdd9165f8b3e6676e21ef33c97acedb +size 102239 diff --git a/GIF/working.gif b/GIF/working.gif new file mode 100644 index 0000000..e2544cc --- /dev/null +++ b/GIF/working.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6e38a80854781039a56b90bf5bc9e8df2e260e534694ffa0c088b0c1f50dbde +size 112218 diff --git a/GIF/working2.gif b/GIF/working2.gif new file mode 100644 index 0000000..cb7c959 --- /dev/null +++ b/GIF/working2.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:135f1e6236bfb3119839b314ef40dd9a5ee4a23b8597b4f6da6a52bc99bf6224 +size 46744 diff --git a/Image/Energy.svg b/Image/Energy.svg new file mode 100644 index 0000000..86765c2 --- /dev/null +++ b/Image/Energy.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:631d8f8ddeb66b3b0aace272779d6fda449a53846274402cff5bb03184a48787 +size 2141 diff --git a/Image/Happiness.svg b/Image/Happiness.svg new file mode 100644 index 0000000..5c9c9f9 --- /dev/null +++ b/Image/Happiness.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56f76cf07081e5a123748687ee9f250e0ad9f9372bcbf691895d1142b1036c5e +size 869 diff --git a/Image/MenuIcon.jpg b/Image/MenuIcon.jpg new file mode 100644 index 0000000..8bcf213 Binary files /dev/null and b/Image/MenuIcon.jpg differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..8ebe287 --- /dev/null +++ b/main.py @@ -0,0 +1,382 @@ +import sys +import os +import random +import datetime +from PyQt5.QtGui import QIcon, QMovie, QCursor, QPixmap +from PyQt5.QtWidgets import (QWidget, QApplication, QSystemTrayIcon, + QMenu, QAction, QLabel, QDesktopWidget, + QProgressBar, QVBoxLayout, QHBoxLayout) +from PyQt5.QtCore import Qt, QSize, QTimer + +class DesktopPet(QWidget): + def __init__(self, parent=None, **kwargs): + super(DesktopPet, self).__init__(parent) + self.init() + self.initPall() + self.action_timer = QTimer(self) + self.action_timer.setSingleShot(True) + self.action_timer.timeout.connect(self.checkInitialGif) + self.status_timer = QTimer(self) + self.status_timer.timeout.connect(self.statusTimer) + self.happiness_accumulated = 0 + self.energy_accumulated = 0 + self.working_timer = QTimer(self) + self.working_timer.timeout.connect(self.updateWorking) + self.boring_timer = QTimer(self) + self.boring_timer.setSingleShot(True) + self.boring_timer.timeout.connect(self.setBoring) + self.is_boring = False + self.resetBoringTimer() + self.resurrect_timer = QTimer(self) + self.resurrect_timer.setSingleShot(True) + self.resurrect_timer.timeout.connect(self.resurrectPet) + self.is_dead = False + self.initPetImage() + + def init(self): + self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool | Qt.NoDropShadowWindowHint) + self.setAutoFillBackground(False) + self.setAttribute(Qt.WA_TranslucentBackground, True) + self.repaint() + + def initPall(self): + icons = os.path.join('Image/MenuIcon.jpg') + self.quit_action = QAction(u'退出', self, triggered=self.quit) + self.showing = QAction(u'显示', self, triggered=self.showup) + self.hideup = QAction(u'隐藏', self, triggered=self.hide) + self.hidestats = QAction(u'显示/隐藏状态栏', self, triggered=self.hideStatsBar) + self.tray_icon_menu = QMenu(self) + self.tray_icon_menu.addAction(self.quit_action) + self.tray_icon_menu.addAction(self.showing) + self.tray_icon_menu.addAction(self.hideup) + self.tray_icon_menu.addAction(self.hidestats) + self.tray_icon = QSystemTrayIcon(self) + self.tray_icon.setIcon(QIcon(icons)) + self.tray_icon.setContextMenu(self.tray_icon_menu) + self.tray_icon.show() + + def initPetImage(self): + self.main_layout = QVBoxLayout(self) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.setSpacing(0) + + self.stats_visible = True + self.happiness_layout = QHBoxLayout() + self.happiness_layout.setContentsMargins(0, 0, 0, 0) + self.happiness_layout.setSpacing(0) + self.happiness_icon = QLabel() + self.happiness_icon.setContentsMargins(0, 0, 0, 0) + self.happiness_icon_pixmap = QPixmap("Image/Happiness.svg") + self.happiness_icon_pixmap = self.happiness_icon_pixmap.scaled(QSize(20, 20), Qt.KeepAspectRatio, Qt.SmoothTransformation) + self.happiness_icon.setPixmap(self.happiness_icon_pixmap) + self.happiness_layout.addWidget(self.happiness_icon, alignment=Qt.AlignCenter) + self.happiness_bar = QProgressBar() + self.happiness_bar.setRange(0, 100) + self.happiness_bar.setValue(80) + self.happiness_bar.setFixedHeight(12) + self.happiness_bar.setFixedWidth(150) + self.happiness_bar.setStyleSheet(""" + QProgressBar { + border: 1px solid #ccc; + border-radius: 5px; + text-align: center; + } + QProgressBar::chunk { + background-color: #FF69B4; + } + """) + self.happiness_layout.addWidget(self.happiness_bar, alignment=Qt.AlignCenter) + + self.energy_layout = QHBoxLayout() + self.energy_layout.setContentsMargins(0, 0, 0, 0) + self.energy_layout.setSpacing(0) + self.energy_label = QLabel() + self.energy_label.setContentsMargins(0, 0, 0, 0) + self.energy_icon_pixmap = QPixmap("Image/Energy.svg") + self.energy_icon_pixmap = self.energy_icon_pixmap.scaled(QSize(20, 20), Qt.KeepAspectRatio, Qt.SmoothTransformation) + self.energy_label.setPixmap(self.energy_icon_pixmap) + self.energy_layout.addWidget(self.energy_label, alignment=Qt.AlignCenter) + self.energy_bar = QProgressBar() + self.energy_bar.setRange(0, 100) + self.energy_bar.setValue(80) + self.energy_bar.setFixedHeight(12) + self.energy_bar.setFixedWidth(150) + self.energy_bar.setStyleSheet(""" + QProgressBar { + border: 1px solid #ccc; + border-radius: 5px; + text-align: center; + } + QProgressBar::chunk { + background-color: #1E90FF; + } + """) + self.energy_layout.addWidget(self.energy_bar, alignment=Qt.AlignCenter) + + self.image = QLabel(self) + self.image.setContentsMargins(0, 0, 0, 0) + self.checkInitialGif() + + self.main_layout.addWidget(self.image) + self.main_layout.addLayout(self.happiness_layout) + self.main_layout.addLayout(self.energy_layout) + + self.resize(200, 240) + self.randomPosition() + self.show() + self.pet1 = [] + for filename in os.listdir("GIF"): + self.pet1.append(os.path.join("GIF", filename)) + + def showMenu(self): + menu = QMenu() + menu.addAction(u"贴贴", self.stick) + menu.addAction(u"拍一拍", self.call) + menu.addAction(u"锻炼", self.exercise) + menu.addAction(u"充电", self.charge) + menu.addAction(u"投喂小白", self.cake) + menu.addAction(u"吧唧", self.baji) + menu.addAction(u"随机出现", self.appear) + menu.addAction(u"遛小鸡毛", self.walkDog) + right_pos = self.mapToGlobal(self.rect().topRight()) + menu.exec_(right_pos) + + def stick(self): + if self.is_dead: + return + self.resetBoringTimer() + self.working_timer.stop() + self.changeGif("GIF/stick.gif") + self.action_timer.start(10 * 60 * 1000) + self.updateStatus(15, -5, 10 * 60 * 1000) + + def call(self): + if self.is_dead: + return + self.resetBoringTimer() + self.working_timer.stop() + self.changeGif("GIF/call.gif") + self.action_timer.start(2000) + + def exercise(self): + if self.is_dead: + return + self.resetBoringTimer() + self.working_timer.stop() + self.changeGif("GIF/exercise.gif") + self.action_timer.start(10 * 60 * 1000) + self.updateStatus(5, -10, 10 * 60 * 1000) + + def charge(self): + if self.is_dead: + return + self.resetBoringTimer() + self.working_timer.stop() + self.changeGif("GIF/charge.gif") + self.action_timer.start(45 * 60 * 1000) + self.updateStatus(30, 30, 30 * 60 * 1000) + + def cake(self): + if self.is_dead: + return + self.resetBoringTimer() + self.working_timer.stop() + self.changeGif("GIF/cake.gif") + self.action_timer.start(5 * 60 * 1000) + self.updateStatus(10, 5, 5 * 60 * 1000) + + def baji(self): + if self.is_dead: + return + self.resetBoringTimer() + self.working_timer.stop() + self.changeGif("GIF/baji.gif") + self.action_timer.start(5 * 60 * 1000) + self.updateStatus(10, 10, 5 * 60 * 1000) + + def appear(self): + if self.is_dead: + return + self.resetBoringTimer() + self.working_timer.stop() + self.changeGif("GIF/appear.gif") + self.action_timer.start(3500) + self.randomPosition() + + def walkDog(self): + if self.is_dead: + return + self.resetBoringTimer() + self.working_timer.stop() + self.changeGif("GIF/walkdog.gif") + self.action_timer.start(10 * 60 * 1000) + self.updateStatus(15, -10, 10 * 60 * 1000) + + def checkInitialGif(self): + current_time = datetime.datetime.now() + current_hour = current_time.hour + weekday = current_time.weekday() + self.is_working_time = (0 <= weekday <= 4) and (10 <= current_hour < 18) + happiness = self.happiness_bar.value() + energy = self.energy_bar.value() + + if happiness == 0 and energy == 0: + self.petDied() + elif happiness < 20 and energy < 20: + if self.is_working_time: + self.working_timer.stop() + self.working_timer.start(60 * 1000) + return self.changeGif("GIF/angry.gif") + elif happiness < 20: + if self.is_working_time: + self.working_timer.stop() + self.working_timer.start(2 * 60 * 1000) + return self.changeGif("GIF/crying2.gif") + elif energy < 20: + if self.is_working_time: + self.working_timer.stop() + self.working_timer.start(2 * 10 * 1000) + return self.changeGif("GIF/crying.gif") + + if self.is_working_time: + self.working_timer.start(3 * 60 * 1000) + return self.changeGif("GIF/working2.gif") + else: + self.working_timer.stop() + return self.changeGif("GIF/normal.gif") + + def updateWorking(self): + self.updateHappiness(-1) + self.updateEnergy(-1) + if self.happiness_bar.value() <= 20 or self.energy_bar.value() <= 20: + self.checkInitialGif() + + def updateStatus(self, happinesschange, energychange, duration): + self.total_happiness_change = happinesschange + self.total_energy_change = energychange + interval = 100 + self.remaining = duration / interval + self.happiness_change_step = happinesschange / self.remaining + self.energy_change_step = energychange / self.remaining + self.status_timer.start(interval) + + def statusTimer(self): + if self.remaining <= 0: + self.status_timer.stop() + return + self.happiness_accumulated += self.happiness_change_step + self.energy_accumulated += self.energy_change_step + if self.happiness_accumulated >= 1 or self.happiness_accumulated <= -1: + self.updateHappiness(int(self.happiness_accumulated)) + self.happiness_accumulated = 0 + if self.energy_accumulated >= 1 or self.energy_accumulated <= -1: + self.updateEnergy(int(self.energy_accumulated)) + self.energy_accumulated = 0 + self.remaining -= 1 + + def setBoring(self): + if not self.is_boring and not self.is_working_time: + self.is_boring = True + self.changeGif("GIF/boring.gif") + self.updateHappiness(-5) + + def resetBoringTimer(self): + if self.is_boring: + self.is_boring = False + self.checkInitialGif() + self.boring_timer.stop() + self.boring_timer.start(60 * 60 * 1000) + + def petDied(self): + if self.is_dead: + return + self.is_dead = True + self.working_timer.stop() + self.boring_timer.stop() + self.action_timer.stop() + self.status_timer.stop() + self.showing.setEnabled(False) + self.hidestats.setEnabled(False) + self.hideup.setEnabled(False) + self.hide() + self.resurrect_timer.start(5000) + + def resurrectPet(self): + self.is_dead = False + self.happiness_bar.setValue(50) + self.energy_bar.setValue(50) + self.showing.setEnabled(True) + self.hidestats.setEnabled(True) + self.hideup.setEnabled(True) + self.showup() + self.resetBoringTimer() + self.checkInitialGif() + + def randomPosition(self): + screen_geometry = QDesktopWidget().screenGeometry() + pet_geometry = self.geometry() + width = int((screen_geometry.width() - pet_geometry.width()) * random.random()) + height = int((screen_geometry.height() - pet_geometry.height()) * random.random()) + self.move(width, height) + + def changeGif(self, path): + self.movie = QMovie(path) + self.movie.setScaledSize(QSize(200, 200)) + self.image.setMovie(self.movie) + self.movie.start() + + def mousePressEvent(self, event): + self.condition = 1 + if event.button() == Qt.LeftButton: + self.is_follow_mouse = True + self.mouse_drag_pos = event.globalPos() - self.pos() + self.setCursor(QCursor(Qt.OpenHandCursor)) + elif event.button() == Qt.RightButton: + self.showMenu() + event.accept() + + def mouseMoveEvent(self, event): + if Qt.LeftButton and self.is_follow_mouse: + self.move(event.globalPos() - self.mouse_drag_pos) + event.accept() + + def mouseReleaseEvent(self, event): + self.is_follow_mouse = False + self.setCursor(QCursor(Qt.ArrowCursor)) + + def enterEvent(self, event): + self.setCursor(Qt.ClosedHandCursor) + + def updateHappiness(self, value): + current_value = self.happiness_bar.value() + new_value = max(0, min(100, current_value + value)) + self.happiness_bar.setValue(new_value) + + def updateEnergy(self, value): + current_value = self.energy_bar.value() + new_value = max(0, min(100, current_value + value)) + self.energy_bar.setValue(new_value) + + def hideStatsBar(self): + if self.is_dead: + return + self.stats_visible = not self.stats_visible + self.happiness_icon.setVisible(self.stats_visible) + self.happiness_bar.setVisible(self.stats_visible) + self.energy_label.setVisible(self.stats_visible) + self.energy_bar.setVisible(self.stats_visible) + + def quit(self): + self.close() + sys.exit() + + def showup(self): + self.setWindowOpacity(1) + + def hide(self): + self.setWindowOpacity(0) + +if __name__ == '__main__': + app = QApplication(sys.argv) + pet = DesktopPet() + sys.exit(app.exec_()) \ No newline at end of file