frontengine 1.0.1__tar.gz → 1.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. {frontengine-1.0.1 → frontengine-1.0.3}/PKG-INFO +1 -1
  2. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/__init__.py +3 -1
  3. frontengine-1.0.3/frontengine/show/pet/desktop_pet.py +297 -0
  4. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/dialog/choose_file_dialog.py +10 -0
  5. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/main_ui.py +6 -1
  6. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/menu/preset_menu.py +1 -0
  7. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/control_center/control_center_ui.py +9 -2
  8. frontengine-1.0.3/frontengine/ui/page/pet/pet_setting_ui.py +168 -0
  9. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/multi_language/english.py +11 -0
  10. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/multi_language/traditional_chinese.py +11 -0
  11. frontengine-1.0.3/frontengine/utils/theme_schedule/__init__.py +0 -0
  12. frontengine-1.0.3/frontengine/worker/__init__.py +0 -0
  13. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine.egg-info/PKG-INFO +1 -1
  14. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine.egg-info/SOURCES.txt +4 -0
  15. {frontengine-1.0.1 → frontengine-1.0.3}/pyproject.toml +1 -1
  16. {frontengine-1.0.1 → frontengine-1.0.3}/LICENSE +0 -0
  17. {frontengine-1.0.1 → frontengine-1.0.3}/README.md +0 -0
  18. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/__main__.py +0 -0
  19. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/__init__.py +0 -0
  20. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/base_widget.py +0 -0
  21. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/gif/__init__.py +0 -0
  22. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/gif/paint_gif.py +0 -0
  23. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/image/__init__.py +0 -0
  24. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/image/paint_image.py +0 -0
  25. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/load/__init__.py +0 -0
  26. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/load/load_someone_make_ui.py +0 -0
  27. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/overlay_factory.py +0 -0
  28. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/particle/__init__.py +0 -0
  29. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/particle/particle_ui.py +0 -0
  30. {frontengine-1.0.1/frontengine/show/scene → frontengine-1.0.3/frontengine/show/pet}/__init__.py +0 -0
  31. {frontengine-1.0.1/frontengine/show/sound_player → frontengine-1.0.3/frontengine/show/scene}/__init__.py +0 -0
  32. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/scene/extend_graphic_scene.py +0 -0
  33. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/scene/extend_graphic_view.py +0 -0
  34. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/scene/scene.py +0 -0
  35. {frontengine-1.0.1/frontengine/show/text → frontengine-1.0.3/frontengine/show/sound_player}/__init__.py +0 -0
  36. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/sound_player/sound_effect.py +0 -0
  37. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/sound_player/sound_player.py +0 -0
  38. {frontengine-1.0.1/frontengine/show/video → frontengine-1.0.3/frontengine/show/text}/__init__.py +0 -0
  39. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/text/draw_text.py +0 -0
  40. {frontengine-1.0.1/frontengine/show/web → frontengine-1.0.3/frontengine/show/video}/__init__.py +0 -0
  41. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/video/video_player.py +0 -0
  42. {frontengine-1.0.1/frontengine/system_tray → frontengine-1.0.3/frontengine/show/web}/__init__.py +0 -0
  43. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/web/webview.py +0 -0
  44. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/show/window_helpers.py +0 -0
  45. {frontengine-1.0.1/frontengine/ui → frontengine-1.0.3/frontengine/system_tray}/__init__.py +0 -0
  46. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/system_tray/extend_system_tray.py +0 -0
  47. {frontengine-1.0.1/frontengine/ui/color → frontengine-1.0.3/frontengine/ui}/__init__.py +0 -0
  48. {frontengine-1.0.1/frontengine/ui/dialog → frontengine-1.0.3/frontengine/ui/color}/__init__.py +0 -0
  49. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/color/global_color.py +0 -0
  50. {frontengine-1.0.1/frontengine/ui/menu → frontengine-1.0.3/frontengine/ui/dialog}/__init__.py +0 -0
  51. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
  52. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/dialog/save_file_dialog.py +0 -0
  53. {frontengine-1.0.1/frontengine/ui/page → frontengine-1.0.3/frontengine/ui/menu}/__init__.py +0 -0
  54. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/menu/help_menu.py +0 -0
  55. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/menu/how_to_menu.py +0 -0
  56. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/menu/language_menu.py +0 -0
  57. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/menu/settings_menu.py +0 -0
  58. {frontengine-1.0.1/frontengine/ui/page/control_center → frontengine-1.0.3/frontengine/ui/page}/__init__.py +0 -0
  59. {frontengine-1.0.1/frontengine/ui/page/gif → frontengine-1.0.3/frontengine/ui/page/control_center}/__init__.py +0 -0
  60. {frontengine-1.0.1/frontengine/ui/page/image → frontengine-1.0.3/frontengine/ui/page/gif}/__init__.py +0 -0
  61. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
  62. {frontengine-1.0.1/frontengine/ui/page/particle → frontengine-1.0.3/frontengine/ui/page/image}/__init__.py +0 -0
  63. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/image/image_setting_ui.py +0 -0
  64. {frontengine-1.0.1/frontengine/ui/page/scene_setting → frontengine-1.0.3/frontengine/ui/page/particle}/__init__.py +0 -0
  65. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
  66. {frontengine-1.0.1/frontengine/ui/page/scene_setting/scene_page → frontengine-1.0.3/frontengine/ui/page/pet}/__init__.py +0 -0
  67. {frontengine-1.0.1/frontengine/ui/page/sound_player → frontengine-1.0.3/frontengine/ui/page/scene_setting}/__init__.py +0 -0
  68. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
  69. {frontengine-1.0.1/frontengine/ui/page/text → frontengine-1.0.3/frontengine/ui/page/scene_setting/scene_page}/__init__.py +0 -0
  70. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
  71. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
  72. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
  73. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
  74. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
  75. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
  76. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
  77. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
  78. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
  79. {frontengine-1.0.1/frontengine/ui/page/video → frontengine-1.0.3/frontengine/ui/page/sound_player}/__init__.py +0 -0
  80. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
  81. {frontengine-1.0.1/frontengine/ui/page/web → frontengine-1.0.3/frontengine/ui/page/text}/__init__.py +0 -0
  82. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/text/text_setting_ui.py +0 -0
  83. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/utils.py +0 -0
  84. {frontengine-1.0.1/frontengine/user_setting → frontengine-1.0.3/frontengine/ui/page/video}/__init__.py +0 -0
  85. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/video/video_setting_ui.py +0 -0
  86. {frontengine-1.0.1/frontengine/utils → frontengine-1.0.3/frontengine/ui/page/web}/__init__.py +0 -0
  87. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/ui/page/web/web_setting_ui.py +0 -0
  88. {frontengine-1.0.1/frontengine/utils/browser → frontengine-1.0.3/frontengine/user_setting}/__init__.py +0 -0
  89. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/user_setting/preset_repository.py +0 -0
  90. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/user_setting/scene_setting.py +0 -0
  91. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/user_setting/user_setting_file.py +0 -0
  92. {frontengine-1.0.1/frontengine/utils/critical_exit → frontengine-1.0.3/frontengine/utils}/__init__.py +0 -0
  93. {frontengine-1.0.1/frontengine/utils/file → frontengine-1.0.3/frontengine/utils/browser}/__init__.py +0 -0
  94. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/browser/browser.py +0 -0
  95. {frontengine-1.0.1/frontengine/utils/hotkey → frontengine-1.0.3/frontengine/utils/critical_exit}/__init__.py +0 -0
  96. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
  97. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/critical_exit/critical_exit.py +0 -0
  98. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/critical_exit/win32_vk.py +0 -0
  99. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/exception/__init__.py +0 -0
  100. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/exception/exception_tags.py +0 -0
  101. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/exception/exceptions.py +0 -0
  102. {frontengine-1.0.1/frontengine/utils/json → frontengine-1.0.3/frontengine/utils/file}/__init__.py +0 -0
  103. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/file/open/__init__.py +0 -0
  104. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/file/open/open_file.py +0 -0
  105. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/file/save/__init__.py +0 -0
  106. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/file/save/save_file.py +0 -0
  107. {frontengine-1.0.1/frontengine/utils/logging → frontengine-1.0.3/frontengine/utils/hotkey}/__init__.py +0 -0
  108. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/hotkey/hotkey_service.py +0 -0
  109. {frontengine-1.0.1/frontengine/utils/multi_language → frontengine-1.0.3/frontengine/utils/json}/__init__.py +0 -0
  110. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/json/json_file.py +0 -0
  111. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/json/json_repository.py +0 -0
  112. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/json_format/__init__.py +0 -0
  113. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/json_format/json_process.py +0 -0
  114. {frontengine-1.0.1/frontengine/utils/preset_schedule → frontengine-1.0.3/frontengine/utils/logging}/__init__.py +0 -0
  115. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/logging/loggin_instance.py +0 -0
  116. {frontengine-1.0.1/frontengine/utils/redirect_manager → frontengine-1.0.3/frontengine/utils/multi_language}/__init__.py +0 -0
  117. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/multi_language/france.py +0 -0
  118. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/multi_language/germany.py +0 -0
  119. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/multi_language/italy.py +0 -0
  120. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/multi_language/language_wrapper.py +0 -0
  121. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/multi_language/russian.py +0 -0
  122. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
  123. {frontengine-1.0.1/frontengine/utils/smart_pause → frontengine-1.0.3/frontengine/utils/preset_schedule}/__init__.py +0 -0
  124. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
  125. {frontengine-1.0.1/frontengine/utils/theme_schedule → frontengine-1.0.3/frontengine/utils/redirect_manager}/__init__.py +0 -0
  126. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
  127. {frontengine-1.0.1/frontengine/worker → frontengine-1.0.3/frontengine/utils/smart_pause}/__init__.py +0 -0
  128. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
  129. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
  130. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/utils/web_url.py +0 -0
  131. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine/worker/qthread_worker.py +0 -0
  132. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine.egg-info/dependency_links.txt +0 -0
  133. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine.egg-info/entry_points.txt +0 -0
  134. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine.egg-info/requires.txt +0 -0
  135. {frontengine-1.0.1 → frontengine-1.0.3}/frontengine.egg-info/top_level.txt +0 -0
  136. {frontengine-1.0.1 → frontengine-1.0.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frontengine
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: FrontEngine is use-define frontview or only use like screen saver
5
5
  Author-email: JE-Chen <jechenmailman@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/Intergration-Automation-Testing/FrontEngine
@@ -4,6 +4,7 @@ from frontengine.show.load.load_someone_make_ui import load_extend_ui_file
4
4
  from frontengine.show.load.load_someone_make_ui import load_ui_file
5
5
  from frontengine.show.sound_player.sound_effect import SoundEffectWidget
6
6
  from frontengine.show.sound_player.sound_player import SoundPlayer
7
+ from frontengine.show.pet.desktop_pet import DesktopPetWidget
7
8
  from frontengine.show.text.draw_text import TextWidget
8
9
  from frontengine.show.video.video_player import VideoWidget
9
10
  from frontengine.show.web.webview import WebWidget
@@ -16,6 +17,7 @@ from frontengine.ui.page.image.image_setting_ui import ImageSettingUI
16
17
  from frontengine.ui.page.sound_player.sound_player_setting_ui import SoundPlayerSettingUI
17
18
  from frontengine.ui.page.video.video_setting_ui import VideoSettingUI
18
19
  from frontengine.ui.page.control_center.control_center_ui import ControlCenterUI
20
+ from frontengine.ui.page.pet.pet_setting_ui import PetSettingUI
19
21
  from frontengine.ui.page.scene_setting.scene_setting_ui import SceneSettingUI
20
22
  from frontengine.ui.main_ui import FrontEngineMainUI
21
23
  from frontengine.ui.main_ui import FrontEngine_EXTEND_TAB
@@ -26,5 +28,5 @@ __all__ = [
26
28
  "WebWidget", "ImageWidget", "VideoWidget", "language_wrapper", "load_extend_ui_file", "load_ui_file",
27
29
  "TextSettingUI", "WEBSettingUI", "GIFSettingUI", "ImageSettingUI", "SoundPlayerSettingUI",
28
30
  "VideoSettingUI", "ControlCenterUI", "SceneSettingUI", "FrontEngineMainUI",
29
- "FrontEngine_EXTEND_TAB", "RedirectManager"
31
+ "FrontEngine_EXTEND_TAB", "RedirectManager", "DesktopPetWidget", "PetSettingUI"
30
32
  ]
@@ -0,0 +1,297 @@
1
+ import random as _random_module
2
+ from pathlib import Path
3
+ from typing import Optional, Tuple
4
+
5
+ from PySide6.QtCore import Qt, QRect, QTimer
6
+ from PySide6.QtGui import QAction, QCursor, QMovie, QPainter, QPixmap, QTransform
7
+ from PySide6.QtWidgets import QMenu, QMessageBox
8
+
9
+ from frontengine.show.base_widget import BaseWidget
10
+ from frontengine.show.window_helpers import apply_overlay_window_flags
11
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
12
+ from frontengine.utils.multi_language.language_wrapper import language_wrapper
13
+
14
+ # 行為模式 / Behaviour modes
15
+ BEHAVIOUR_FLOOR = "floor" # walk on the floor with gravity, throw & bounce
16
+ BEHAVIOUR_WANDER = "wander" # free 2D bounce, no gravity
17
+ BEHAVIOUR_CHASE = "chase" # chase the mouse cursor, sleep when caught
18
+
19
+
20
+ class PetMotion:
21
+ """
22
+ 純邏輯的寵物移動模型(不依賴 Qt,完全可測試)。包含:
23
+ - 重力落下、落地、彈跳(受動量丟出後)
24
+ - 走路/發呆的隨機行為狀態機(rng 可注入以利測試)
25
+ - 追逐游標模式(追到後進入睡眠,游標移動即醒)
26
+ A pure, Qt-free motion model: gravity fall/land/bounce (after a throw),
27
+ a random walk/idle behaviour state machine (injectable rng), and a
28
+ cursor-chase mode that sleeps once it catches the target.
29
+ """
30
+
31
+ GRAVITY = 2.0
32
+ BOUNCE_DAMPING = 0.55
33
+ WALL_DAMPING = 0.6
34
+ BOUNCE_THRESHOLD = 8.0
35
+ CHASE_STOP_DISTANCE = 10.0
36
+ CHASE_SPEED_FACTOR = 2.0
37
+
38
+ def __init__(
39
+ self,
40
+ x: int,
41
+ y: int,
42
+ width: int,
43
+ height: int,
44
+ bounds: Tuple[int, int, int, int],
45
+ speed: int = 3,
46
+ behaviour: str = BEHAVIOUR_FLOOR,
47
+ rng=None,
48
+ ) -> None:
49
+ self.x = float(x)
50
+ self.y = float(y)
51
+ self.width = int(width)
52
+ self.height = int(height)
53
+ self.bounds = bounds # (left, top, right, bottom)
54
+ self.speed = max(1, int(speed))
55
+ self.behaviour = behaviour
56
+ self._rng = rng if rng is not None else _random_module.SystemRandom()
57
+ self.vx = float(self.speed)
58
+ self.vy = 0.0 if behaviour != BEHAVIOUR_WANDER else float(self.speed)
59
+ self._airborne = False
60
+ self.state = "walk" # walk | idle
61
+ self._state_ticks = 0
62
+ self.target: Optional[Tuple[float, float]] = None
63
+ self.asleep = False
64
+
65
+ @property
66
+ def facing_left(self) -> bool:
67
+ return self.vx < 0
68
+
69
+ def set_bounds(self, bounds: Tuple[int, int, int, int]) -> None:
70
+ self.bounds = bounds
71
+
72
+ def set_target(self, x: float, y: float) -> None:
73
+ self.target = (float(x), float(y))
74
+
75
+ def throw(self, vx: float, vy: float) -> None:
76
+ """拖曳放開後以動量丟出(只在重力模式有意義)。"""
77
+ self.vx = float(vx)
78
+ self.vy = float(vy)
79
+ self._airborne = True
80
+ self.asleep = False
81
+
82
+ def step(self) -> Tuple[int, int]:
83
+ left, top, right, bottom = self.bounds
84
+ floor_y = bottom - self.height
85
+ if self.behaviour == BEHAVIOUR_CHASE:
86
+ return self._step_chase(left, top, right, bottom)
87
+ if self.behaviour == BEHAVIOUR_WANDER:
88
+ return self._step_wander(left, top, right, bottom)
89
+ return self._step_floor(left, top, right, bottom, floor_y)
90
+
91
+ # --- floor / gravity ---
92
+ def _step_floor(self, left, top, right, bottom, floor_y) -> Tuple[int, int]:
93
+ airborne = self._airborne or self.y < floor_y - 0.5
94
+ if airborne:
95
+ self.vy += self.GRAVITY
96
+ self.x += self.vx
97
+ self.y += self.vy
98
+ if self.x <= left:
99
+ self.x = float(left)
100
+ self.vx = abs(self.vx) * self.WALL_DAMPING
101
+ elif self.x + self.width >= right:
102
+ self.x = float(right - self.width)
103
+ self.vx = -abs(self.vx) * self.WALL_DAMPING
104
+ if self.y <= top:
105
+ self.y = float(top)
106
+ self.vy = abs(self.vy) * self.WALL_DAMPING
107
+ if self.y >= floor_y:
108
+ self.y = float(floor_y)
109
+ if abs(self.vy) > self.BOUNCE_THRESHOLD:
110
+ self.vy = -self.vy * self.BOUNCE_DAMPING
111
+ else:
112
+ self.vy = 0.0
113
+ self._airborne = False
114
+ self.vx = float(self.speed) if self.vx >= 0 else float(-self.speed)
115
+ self._new_state(force_walk=True)
116
+ return int(self.x), int(self.y)
117
+
118
+ # grounded: random walk / idle
119
+ self.y = float(floor_y)
120
+ if self._state_ticks <= 0:
121
+ self._new_state()
122
+ self._state_ticks -= 1
123
+ if self.state == "walk":
124
+ self.x += self.vx
125
+ if self.x <= left:
126
+ self.x = float(left)
127
+ self.vx = abs(self.vx)
128
+ elif self.x + self.width >= right:
129
+ self.x = float(right - self.width)
130
+ self.vx = -abs(self.vx)
131
+ return int(self.x), int(self.y)
132
+
133
+ def _new_state(self, force_walk: bool = False) -> None:
134
+ if force_walk or self._rng.random() < 0.65:
135
+ self.state = "walk"
136
+ if not force_walk:
137
+ self.vx = float(self.speed) if self._rng.random() < 0.5 else float(-self.speed)
138
+ self._state_ticks = self._rng.randint(30, 120)
139
+ else:
140
+ self.state = "idle"
141
+ self._state_ticks = self._rng.randint(20, 80)
142
+
143
+ # --- free wander ---
144
+ def _step_wander(self, left, top, right, bottom) -> Tuple[int, int]:
145
+ if abs(self.vy) < 1e-9:
146
+ self.vy = float(self.speed)
147
+ self.x += self.vx
148
+ self.y += self.vy
149
+ if self.x <= left:
150
+ self.x = float(left)
151
+ self.vx = abs(self.vx)
152
+ elif self.x + self.width >= right:
153
+ self.x = float(right - self.width)
154
+ self.vx = -abs(self.vx)
155
+ if self.y <= top:
156
+ self.y = float(top)
157
+ self.vy = abs(self.vy)
158
+ elif self.y + self.height >= bottom:
159
+ self.y = float(bottom - self.height)
160
+ self.vy = -abs(self.vy)
161
+ return int(self.x), int(self.y)
162
+
163
+ # --- chase cursor ---
164
+ def _step_chase(self, left, top, right, bottom) -> Tuple[int, int]:
165
+ if self.target is None:
166
+ return int(self.x), int(self.y)
167
+ target_x, target_y = self.target
168
+ dx = target_x - (self.x + self.width / 2)
169
+ dy = target_y - (self.y + self.height / 2)
170
+ distance = (dx * dx + dy * dy) ** 0.5
171
+ if distance <= self.CHASE_STOP_DISTANCE:
172
+ self.asleep = True
173
+ return int(self.x), int(self.y)
174
+ self.asleep = False
175
+ step = float(self.speed) * self.CHASE_SPEED_FACTOR
176
+ self.x += step * (dx / distance)
177
+ self.y += step * (dy / distance)
178
+ self.vx = abs(self.vx) if dx >= 0 else -abs(self.vx)
179
+ self.x = min(max(self.x, float(left)), float(right - self.width))
180
+ self.y = min(max(self.y, float(top)), float(bottom - self.height))
181
+ return int(self.x), int(self.y)
182
+
183
+
184
+ class DesktopPetWidget(BaseWidget):
185
+ """
186
+ 桌面寵物:可自主走動、可拖曳丟出、可追游標的動畫精靈。支援 GIF 與靜態圖。
187
+ A draggable, throwable, cursor-chasing animated sprite (GIF or image).
188
+ """
189
+
190
+ def __init__(self, image_path: str, size: int = 128, speed: int = 3,
191
+ behaviour: str = BEHAVIOUR_FLOOR):
192
+ front_engine_logger.info(
193
+ f"[DesktopPetWidget] Init | path={image_path}, size={size}, speed={speed}, behaviour={behaviour}"
194
+ )
195
+ super().__init__()
196
+ self.opacity = 1.0
197
+ self.pet_size: int = max(16, int(size))
198
+ self.image_path: Path = Path(image_path)
199
+ self.movie: Optional[QMovie] = None
200
+ self.pixmap: Optional[QPixmap] = None
201
+ self._dragging: bool = False
202
+ self._drag_offset = None
203
+ self._last_move_delta: Tuple[int, int] = (0, 0)
204
+
205
+ if self.image_path.exists() and self.image_path.is_file():
206
+ if self.image_path.suffix.lower() in (".gif", ".webp"):
207
+ self.movie = QMovie(str(self.image_path))
208
+ self.movie.frameChanged.connect(self.update)
209
+ self.movie.start()
210
+ else:
211
+ self.pixmap = QPixmap(str(self.image_path))
212
+ else:
213
+ front_engine_logger.error(f"[DesktopPetWidget] File not found: {self.image_path}")
214
+ message_box = QMessageBox(self)
215
+ message_box.setText(language_wrapper.language_word_dict.get("pet_message_box_text", "Pet image not found"))
216
+ message_box.show()
217
+
218
+ self.resize(self.pet_size, self.pet_size)
219
+ self.motion = PetMotion(0, 0, self.pet_size, self.pet_size, (0, 0, 1920, 1080), speed, behaviour)
220
+
221
+ self._timer = QTimer(self)
222
+ self._timer.timeout.connect(self._on_tick)
223
+
224
+ self.close_action = QAction(language_wrapper.language_word_dict.get("control_center_close_all", "Close"), self)
225
+ self.close_action.triggered.connect(self.close)
226
+ self.menu = QMenu(self)
227
+ self.menu.addAction(self.close_action)
228
+
229
+ def set_pet_window_flag(self) -> None:
230
+ """寵物需接收滑鼠以便拖曳,故 allow_input=True 並置頂。"""
231
+ apply_overlay_window_flags(self, show_on_bottom=False, allow_input=True)
232
+
233
+ def _current_pixmap(self) -> Optional[QPixmap]:
234
+ if self.movie is not None:
235
+ return self.movie.currentPixmap()
236
+ return self.pixmap
237
+
238
+ def draw_content(self, painter: QPainter) -> None:
239
+ pixmap = self._current_pixmap()
240
+ if pixmap is None or pixmap.isNull():
241
+ return
242
+ if self.motion.facing_left:
243
+ pixmap = pixmap.transformed(QTransform().scale(-1, 1))
244
+ painter.drawPixmap(QRect(0, 0, self.width(), self.height()), pixmap)
245
+
246
+ def start_moving(self, bounds: Tuple[int, int, int, int], interval_ms: int = 33) -> None:
247
+ front_engine_logger.info(f"[DesktopPetWidget] start_moving | bounds={bounds}")
248
+ left, top, right, bottom = bounds
249
+ self.motion.set_bounds(bounds)
250
+ self.motion.x = float(left)
251
+ self.motion.y = float(bottom - self.pet_size)
252
+ self.move(int(self.motion.x), int(self.motion.y))
253
+ self._timer.start(max(10, int(interval_ms)))
254
+
255
+ def _on_tick(self) -> None:
256
+ if self._dragging:
257
+ return
258
+ if self.motion.behaviour == BEHAVIOUR_CHASE:
259
+ cursor = QCursor.pos()
260
+ self.motion.set_target(cursor.x(), cursor.y())
261
+ x, y = self.motion.step()
262
+ self.move(x, y)
263
+
264
+ # --- dragging + throwing ---
265
+ def mousePressEvent(self, event) -> None:
266
+ if event.button() == Qt.MouseButton.LeftButton:
267
+ self._dragging = True
268
+ self._last_move_delta = (0, 0)
269
+ self._drag_offset = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
270
+ super().mousePressEvent(event)
271
+
272
+ def mouseMoveEvent(self, event) -> None:
273
+ if self._dragging and self._drag_offset is not None:
274
+ new_top_left = event.globalPosition().toPoint() - self._drag_offset
275
+ self._last_move_delta = (new_top_left.x() - self.x(), new_top_left.y() - self.y())
276
+ self.move(new_top_left)
277
+ super().mouseMoveEvent(event)
278
+
279
+ def mouseReleaseEvent(self, event) -> None:
280
+ if self._dragging:
281
+ self._dragging = False
282
+ self.motion.x = float(self.x())
283
+ self.motion.y = float(self.y())
284
+ # Fling with the momentum of the last drag movement (floor mode).
285
+ if self.motion.behaviour == BEHAVIOUR_FLOOR:
286
+ self.motion.throw(self._last_move_delta[0], self._last_move_delta[1])
287
+ super().mouseReleaseEvent(event)
288
+
289
+ def contextMenuEvent(self, event) -> None:
290
+ self.menu.popup(event.globalPos())
291
+
292
+ def closeEvent(self, event) -> None:
293
+ if self._timer.isActive():
294
+ self._timer.stop()
295
+ if self.movie is not None:
296
+ self.movie.stop()
297
+ super().closeEvent(event)
@@ -85,6 +85,16 @@ def choose_player_sound(trigger_ui: QWidget) -> Optional[str]:
85
85
  )
86
86
 
87
87
 
88
+ def choose_pet(trigger_ui: QWidget) -> Optional[str]:
89
+ """選擇桌面寵物圖片 (gif/webp/png/jpg) / Choose a desktop-pet sprite."""
90
+ return choose_file(
91
+ trigger_ui,
92
+ file_filter="Pet sprite (*.gif;*.webp;*.png;*.jpg)",
93
+ extensions=[".gif", ".webp", ".png", ".jpg"],
94
+ warning_message=language_wrapper.language_word_dict.get("gif_setting_message_box")
95
+ )
96
+
97
+
88
98
  def choose_video(trigger_ui: QWidget) -> Optional[str]:
89
99
  """選擇影片檔案 (mp4) / Choose video file"""
90
100
  return choose_file(
@@ -18,6 +18,7 @@ from frontengine.ui.page.control_center.control_center_ui import ControlCenterUI
18
18
  from frontengine.ui.page.gif.gif_setting_ui import GIFSettingUI
19
19
  from frontengine.ui.page.image.image_setting_ui import ImageSettingUI
20
20
  from frontengine.ui.page.particle.particle_setting_ui import ParticleSettingUI
21
+ from frontengine.ui.page.pet.pet_setting_ui import PetSettingUI
21
22
  from frontengine.ui.page.scene_setting.scene_setting_ui import SceneSettingUI
22
23
  from frontengine.ui.page.sound_player.sound_player_setting_ui import SoundPlayerSettingUI
23
24
  from frontengine.ui.page.text.text_setting_ui import TextSettingUI
@@ -98,6 +99,7 @@ class FrontEngineMainUI(QMainWindow):
98
99
  self.text_setting_ui = TextSettingUI()
99
100
  self.scene_setting_ui = SceneSettingUI()
100
101
  self.particle_setting_ui = ParticleSettingUI()
102
+ self.pet_setting_ui = PetSettingUI()
101
103
 
102
104
  # 控制中心
103
105
  # Control Center
@@ -110,7 +112,8 @@ class FrontEngineMainUI(QMainWindow):
110
112
  self.text_setting_ui,
111
113
  self.scene_setting_ui,
112
114
  self.particle_setting_ui,
113
- redirect_output
115
+ redirect_output,
116
+ pet_setting_ui=self.pet_setting_ui,
114
117
  )
115
118
 
116
119
  # Menu Bar
@@ -200,6 +203,7 @@ class FrontEngineMainUI(QMainWindow):
200
203
  (self.text_setting_ui, "tab_text_text"),
201
204
  (self.scene_setting_ui, "tab_scene_text"),
202
205
  (self.particle_setting_ui, "tab_particle_text"),
206
+ (self.pet_setting_ui, "tab_pet_text"),
203
207
  (self.control_center_ui, "tab_control_center_text"),
204
208
  ]
205
209
 
@@ -351,6 +355,7 @@ class FrontEngineMainUI(QMainWindow):
351
355
  self.sound_player_setting_ui.sound_widget_list.clear()
352
356
  self.text_setting_ui.text_widget_list.clear()
353
357
  self.particle_setting_ui.particle_list.clear()
358
+ self.pet_setting_ui.pet_list.clear()
354
359
  self.scene_setting_ui.close_scene()
355
360
  super().close()
356
361
  if self.main_app:
@@ -23,6 +23,7 @@ _PRESET_PAGES = (
23
23
  ("sound", "sound_player_setting_ui"),
24
24
  ("text", "text_setting_ui"),
25
25
  ("particle", "particle_setting_ui"),
26
+ ("pet", "pet_setting_ui"),
26
27
  )
27
28
 
28
29
 
@@ -31,7 +31,8 @@ class ControlCenterUI(QWidget):
31
31
  text_setting_ui: TextSettingUI,
32
32
  scene_setting_ui: SceneSettingUI,
33
33
  particle_setting_ui: ParticleSettingUI,
34
- redirect_output: bool = True
34
+ redirect_output: bool = True,
35
+ pet_setting_ui=None,
35
36
  ):
36
37
  front_engine_logger.info(
37
38
  f"[ControlCenterUI] Init | video={video_setting_ui}, image={image_setting_ui}, web={web_setting_ui}, "
@@ -54,6 +55,7 @@ class ControlCenterUI(QWidget):
54
55
  self.text_setting_ui = text_setting_ui
55
56
  self.scene_setting_ui = scene_setting_ui
56
57
  self.particle_setting_ui = particle_setting_ui
58
+ self.pet_setting_ui = pet_setting_ui
57
59
 
58
60
  # Buttons
59
61
  self.clear_video_button = self._create_button("control_center_close_all_video", self.clear_video)
@@ -151,11 +153,13 @@ class ControlCenterUI(QWidget):
151
153
  self.sound_player_setting_ui.sound_widget_list.clear()
152
154
  self.text_setting_ui.text_widget_list.clear()
153
155
  self.particle_setting_ui.particle_list.clear()
156
+ if self.pet_setting_ui is not None:
157
+ self.pet_setting_ui.pet_list.clear()
154
158
  self.scene_setting_ui.close_scene()
155
159
 
156
160
  def _all_overlay_widget_lists(self) -> list:
157
161
  """回傳所有覆蓋層 widget 清單 / All overlay widget lists."""
158
- return [
162
+ lists = [
159
163
  self.video_setting_ui.video_widget_list,
160
164
  self.image_setting_ui.image_widget_list,
161
165
  self.web_setting_ui.web_widget_list,
@@ -164,6 +168,9 @@ class ControlCenterUI(QWidget):
164
168
  self.text_setting_ui.text_widget_list,
165
169
  self.particle_setting_ui.particle_list,
166
170
  ]
171
+ if self.pet_setting_ui is not None:
172
+ lists.append(self.pet_setting_ui.pet_list)
173
+ return lists
167
174
 
168
175
  def _for_each_overlay(self, action) -> None:
169
176
  """
@@ -0,0 +1,168 @@
1
+ from typing import Optional
2
+
3
+ from PySide6.QtGui import QGuiApplication
4
+ from PySide6.QtWidgets import (
5
+ QWidget, QGridLayout, QLabel, QPushButton, QMessageBox, QComboBox,
6
+ )
7
+
8
+ from frontengine.show.pet.desktop_pet import (
9
+ DesktopPetWidget, BEHAVIOUR_FLOOR, BEHAVIOUR_WANDER, BEHAVIOUR_CHASE,
10
+ )
11
+ from frontengine.ui.dialog.choose_file_dialog import choose_pet
12
+ from frontengine.ui.page.utils import (
13
+ build_recent_combobox,
14
+ enable_file_drop,
15
+ reload_recent_combobox,
16
+ )
17
+ from frontengine.user_setting.user_setting_file import add_recent_file
18
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
19
+ from frontengine.utils.multi_language.language_wrapper import language_wrapper
20
+
21
+ _PET_EXTENSIONS = (".gif", ".webp", ".png", ".jpg")
22
+
23
+
24
+ class PetSettingUI(QWidget):
25
+ def __init__(self):
26
+ front_engine_logger.info("[PetSettingUI] Init")
27
+ super().__init__()
28
+ self.grid_layout = QGridLayout(self)
29
+ self.grid_layout.setContentsMargins(0, 0, 0, 0)
30
+
31
+ # Init variable
32
+ self.pet_list: list = []
33
+ self.ready_to_play = False
34
+ self.pet_image_path: Optional[str] = None
35
+
36
+ # Choose file
37
+ self.choose_file_button = QPushButton(
38
+ language_wrapper.language_word_dict.get("pet_choose_file", "Choose pet sprite")
39
+ )
40
+ self.choose_file_button.clicked.connect(self.choose_and_play)
41
+ self.ready_label = QLabel(language_wrapper.language_word_dict.get("Not Ready"))
42
+
43
+ # Size
44
+ self.size_label = QLabel(language_wrapper.language_word_dict.get("pet_size_label", "Size"))
45
+ self.size_combobox = QComboBox()
46
+ self.size_combobox.addItems(["64", "96", "128", "192", "256"])
47
+ self.size_combobox.setCurrentText("128")
48
+
49
+ # Speed
50
+ self.speed_label = QLabel(language_wrapper.language_word_dict.get("pet_speed_label", "Speed"))
51
+ self.speed_combobox = QComboBox()
52
+ self.speed_combobox.addItems([str(n) for n in range(1, 11)])
53
+ self.speed_combobox.setCurrentText("3")
54
+
55
+ # Behaviour
56
+ self.behaviour_label = QLabel(language_wrapper.language_word_dict.get("pet_behaviour_label", "Behaviour"))
57
+ self.behaviour_combobox = QComboBox()
58
+ self.behaviour_combobox.addItem(
59
+ language_wrapper.language_word_dict.get("pet_behaviour_floor", "Walk on floor"), BEHAVIOUR_FLOOR)
60
+ self.behaviour_combobox.addItem(
61
+ language_wrapper.language_word_dict.get("pet_behaviour_wander", "Free wander"), BEHAVIOUR_WANDER)
62
+ self.behaviour_combobox.addItem(
63
+ language_wrapper.language_word_dict.get("pet_behaviour_chase", "Chase cursor"), BEHAVIOUR_CHASE)
64
+
65
+ # Start
66
+ self.start_button = QPushButton(language_wrapper.language_word_dict.get("pet_start", "Spawn pet"))
67
+ self.start_button.clicked.connect(self.start_play_pet)
68
+
69
+ # Recent files
70
+ self.recent_files_label = QLabel(language_wrapper.language_word_dict.get("recent_files_label", "Recent"))
71
+ self.recent_files_combobox = build_recent_combobox("pet")
72
+ self.recent_files_combobox.activated.connect(self._apply_recent_file)
73
+
74
+ # Drag and drop
75
+ self._drop_filter = enable_file_drop(self, _PET_EXTENSIONS, self._on_file_dropped)
76
+
77
+ # Layout
78
+ self.grid_layout.addWidget(self.choose_file_button, 0, 0)
79
+ self.grid_layout.addWidget(self.ready_label, 0, 1)
80
+ self.grid_layout.addWidget(self.size_label, 1, 0)
81
+ self.grid_layout.addWidget(self.size_combobox, 1, 1)
82
+ self.grid_layout.addWidget(self.speed_label, 2, 0)
83
+ self.grid_layout.addWidget(self.speed_combobox, 2, 1)
84
+ self.grid_layout.addWidget(self.behaviour_label, 3, 0)
85
+ self.grid_layout.addWidget(self.behaviour_combobox, 3, 1)
86
+ self.grid_layout.addWidget(self.start_button, 4, 0)
87
+ self.grid_layout.addWidget(self.recent_files_label, 5, 0)
88
+ self.grid_layout.addWidget(self.recent_files_combobox, 5, 1)
89
+
90
+ def _create_pet_widget(self) -> DesktopPetWidget:
91
+ pet = DesktopPetWidget(
92
+ image_path=self.pet_image_path,
93
+ size=int(self.size_combobox.currentText()),
94
+ speed=int(self.speed_combobox.currentText()),
95
+ behaviour=self.behaviour_combobox.currentData(),
96
+ )
97
+ pet.set_pet_window_flag()
98
+ self.pet_list.append(pet)
99
+ return pet
100
+
101
+ def start_play_pet(self) -> None:
102
+ front_engine_logger.info("[PetSettingUI] start_play_pet")
103
+ if not self.pet_image_path or not self.ready_to_play:
104
+ message_box = QMessageBox(self)
105
+ message_box.setText(language_wrapper.language_word_dict.get("not_prepare"))
106
+ message_box.exec()
107
+ return
108
+ pet = self._create_pet_widget()
109
+ screen = QGuiApplication.primaryScreen()
110
+ geometry = screen.availableGeometry() if screen is not None else None
111
+ pet.show()
112
+ if geometry is not None:
113
+ bounds = (geometry.left(), geometry.top(), geometry.right(), geometry.bottom())
114
+ pet.start_moving(bounds)
115
+
116
+ def choose_and_play(self) -> None:
117
+ front_engine_logger.info("[PetSettingUI] choose_and_play")
118
+ self.ready_label.setText(language_wrapper.language_word_dict.get("Not Ready"))
119
+ self.ready_to_play = False
120
+ self.pet_image_path = choose_pet(self)
121
+ if self.pet_image_path:
122
+ self.ready_label.setText(language_wrapper.language_word_dict.get("Ready"))
123
+ self.ready_to_play = True
124
+ add_recent_file("pet", self.pet_image_path)
125
+ reload_recent_combobox(self.recent_files_combobox, "pet")
126
+
127
+ def _apply_recent_file(self, _index: int = 0) -> None:
128
+ path = self.recent_files_combobox.currentData()
129
+ self.recent_files_combobox.setCurrentIndex(0)
130
+ if not path:
131
+ return
132
+ self.pet_image_path = path
133
+ self.ready_to_play = True
134
+ self.ready_label.setText(language_wrapper.language_word_dict.get("Ready"))
135
+
136
+ def _on_file_dropped(self, path: str) -> None:
137
+ front_engine_logger.info(f"[PetSettingUI] _on_file_dropped | path={path}")
138
+ self.pet_image_path = path
139
+ self.ready_to_play = True
140
+ self.ready_label.setText(language_wrapper.language_word_dict.get("Ready"))
141
+ add_recent_file("pet", path)
142
+ reload_recent_combobox(self.recent_files_combobox, "pet")
143
+
144
+ def get_state(self) -> dict:
145
+ return {
146
+ "pet_image_path": self.pet_image_path,
147
+ "size": self.size_combobox.currentText(),
148
+ "speed": self.speed_combobox.currentText(),
149
+ "behaviour": self.behaviour_combobox.currentData(),
150
+ }
151
+
152
+ def set_state(self, state: dict) -> None:
153
+ if state.get("pet_image_path"):
154
+ self.pet_image_path = state["pet_image_path"]
155
+ self.ready_to_play = True
156
+ self.ready_label.setText(language_wrapper.language_word_dict.get("Ready"))
157
+ for combobox, key in ((self.size_combobox, "size"), (self.speed_combobox, "speed")):
158
+ if state.get(key) is not None:
159
+ index = combobox.findText(str(state[key]))
160
+ if index >= 0:
161
+ combobox.setCurrentIndex(index)
162
+ behaviour = state.get("behaviour")
163
+ if behaviour is None and "gravity" in state: # back-compat with old presets
164
+ behaviour = BEHAVIOUR_FLOOR if state.get("gravity") else BEHAVIOUR_WANDER
165
+ if behaviour is not None:
166
+ index = self.behaviour_combobox.findData(behaviour)
167
+ if index >= 0:
168
+ self.behaviour_combobox.setCurrentIndex(index)
@@ -214,4 +214,15 @@ english_word_dict = {
214
214
  "web_zoom_label": "Zoom",
215
215
  "web_refresh_label": "Auto refresh",
216
216
  "web_refresh_off": "Off",
217
+ # Desktop pet
218
+ "tab_pet_text": "Pet",
219
+ "pet_choose_file": "Choose pet sprite",
220
+ "pet_size_label": "Size",
221
+ "pet_speed_label": "Speed",
222
+ "pet_behaviour_label": "Behaviour",
223
+ "pet_behaviour_floor": "Walk on floor",
224
+ "pet_behaviour_wander": "Free wander",
225
+ "pet_behaviour_chase": "Chase cursor",
226
+ "pet_start": "Spawn pet",
227
+ "pet_message_box_text": "Pet image not found",
217
228
  }
@@ -215,4 +215,15 @@ traditional_chinese_word_dict = {
215
215
  "web_zoom_label": "縮放",
216
216
  "web_refresh_label": "自動重新整理",
217
217
  "web_refresh_off": "關閉",
218
+ # Desktop pet
219
+ "tab_pet_text": "桌面寵物",
220
+ "pet_choose_file": "選擇寵物圖片",
221
+ "pet_size_label": "大小",
222
+ "pet_speed_label": "速度",
223
+ "pet_behaviour_label": "行為",
224
+ "pet_behaviour_floor": "沿底部行走",
225
+ "pet_behaviour_wander": "自由漫遊",
226
+ "pet_behaviour_chase": "追逐游標",
227
+ "pet_start": "召喚寵物",
228
+ "pet_message_box_text": "找不到寵物圖片",
218
229
  }
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frontengine
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: FrontEngine is use-define frontview or only use like screen saver
5
5
  Author-email: JE-Chen <jechenmailman@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/Intergration-Automation-Testing/FrontEngine
@@ -21,6 +21,8 @@ frontengine/show/load/__init__.py
21
21
  frontengine/show/load/load_someone_make_ui.py
22
22
  frontengine/show/particle/__init__.py
23
23
  frontengine/show/particle/particle_ui.py
24
+ frontengine/show/pet/__init__.py
25
+ frontengine/show/pet/desktop_pet.py
24
26
  frontengine/show/scene/__init__.py
25
27
  frontengine/show/scene/extend_graphic_scene.py
26
28
  frontengine/show/scene/extend_graphic_view.py
@@ -60,6 +62,8 @@ frontengine/ui/page/image/__init__.py
60
62
  frontengine/ui/page/image/image_setting_ui.py
61
63
  frontengine/ui/page/particle/__init__.py
62
64
  frontengine/ui/page/particle/particle_setting_ui.py
65
+ frontengine/ui/page/pet/__init__.py
66
+ frontengine/ui/page/pet/pet_setting_ui.py
63
67
  frontengine/ui/page/scene_setting/__init__.py
64
68
  frontengine/ui/page/scene_setting/scene_manager.py
65
69
  frontengine/ui/page/scene_setting/scene_setting_ui.py
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "frontengine"
9
- version = "1.0.1"
9
+ version = "1.0.3"
10
10
  authors = [
11
11
  { name = "JE-Chen", email = "jechenmailman@gmail.com" },
12
12
  ]
File without changes
File without changes
File without changes