frontengine 1.0.21__tar.gz → 1.0.23__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.21 → frontengine-1.0.23}/PKG-INFO +1 -1
  2. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/pet/desktop_pet.py +119 -3
  3. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/pet/pet_setting_ui.py +7 -0
  4. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/user_setting/user_setting_file.py +3 -0
  5. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/multi_language/english.py +1 -0
  6. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/multi_language/traditional_chinese.py +1 -0
  7. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine.egg-info/PKG-INFO +1 -1
  8. {frontengine-1.0.21 → frontengine-1.0.23}/pyproject.toml +1 -1
  9. {frontengine-1.0.21 → frontengine-1.0.23}/LICENSE +0 -0
  10. {frontengine-1.0.21 → frontengine-1.0.23}/README.md +0 -0
  11. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/__init__.py +0 -0
  12. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/__main__.py +0 -0
  13. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/__init__.py +0 -0
  14. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/base_widget.py +0 -0
  15. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/gif/__init__.py +0 -0
  16. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/gif/paint_gif.py +0 -0
  17. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/image/__init__.py +0 -0
  18. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/image/paint_image.py +0 -0
  19. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/load/__init__.py +0 -0
  20. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/load/load_someone_make_ui.py +0 -0
  21. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/overlay_factory.py +0 -0
  22. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/particle/__init__.py +0 -0
  23. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/particle/particle_ui.py +0 -0
  24. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/pet/__init__.py +0 -0
  25. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/scene/__init__.py +0 -0
  26. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/scene/extend_graphic_scene.py +0 -0
  27. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/scene/extend_graphic_view.py +0 -0
  28. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/scene/scene.py +0 -0
  29. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/sound_player/__init__.py +0 -0
  30. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/sound_player/sound_effect.py +0 -0
  31. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/sound_player/sound_player.py +0 -0
  32. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/text/__init__.py +0 -0
  33. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/text/draw_text.py +0 -0
  34. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/video/__init__.py +0 -0
  35. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/video/video_player.py +0 -0
  36. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/web/__init__.py +0 -0
  37. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/web/webview.py +0 -0
  38. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/show/window_helpers.py +0 -0
  39. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/system_tray/__init__.py +0 -0
  40. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/system_tray/extend_system_tray.py +0 -0
  41. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/__init__.py +0 -0
  42. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/color/__init__.py +0 -0
  43. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/color/global_color.py +0 -0
  44. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/dialog/__init__.py +0 -0
  45. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
  46. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
  47. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/dialog/save_file_dialog.py +0 -0
  48. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/main_ui.py +0 -0
  49. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/menu/__init__.py +0 -0
  50. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/menu/help_menu.py +0 -0
  51. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/menu/how_to_menu.py +0 -0
  52. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/menu/language_menu.py +0 -0
  53. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/menu/preset_menu.py +0 -0
  54. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/menu/settings_menu.py +0 -0
  55. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/__init__.py +0 -0
  56. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/control_center/__init__.py +0 -0
  57. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/control_center/control_center_ui.py +0 -0
  58. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/gif/__init__.py +0 -0
  59. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
  60. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/image/__init__.py +0 -0
  61. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/image/image_setting_ui.py +0 -0
  62. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/particle/__init__.py +0 -0
  63. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
  64. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/pet/__init__.py +0 -0
  65. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/scene_setting/__init__.py +0 -0
  66. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
  67. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/scene_setting/scene_page/__init__.py +0 -0
  68. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
  69. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
  70. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
  71. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
  72. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
  73. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
  74. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
  75. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
  76. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
  77. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/sound_player/__init__.py +0 -0
  78. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
  79. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/text/__init__.py +0 -0
  80. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/text/text_setting_ui.py +0 -0
  81. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/utils.py +0 -0
  82. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/video/__init__.py +0 -0
  83. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/video/video_setting_ui.py +0 -0
  84. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/web/__init__.py +0 -0
  85. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/ui/page/web/web_setting_ui.py +0 -0
  86. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/user_setting/__init__.py +0 -0
  87. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/user_setting/preset_repository.py +0 -0
  88. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/user_setting/scene_setting.py +0 -0
  89. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/__init__.py +0 -0
  90. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/browser/__init__.py +0 -0
  91. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/browser/browser.py +0 -0
  92. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/critical_exit/__init__.py +0 -0
  93. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
  94. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/critical_exit/critical_exit.py +0 -0
  95. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/critical_exit/win32_vk.py +0 -0
  96. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/exception/__init__.py +0 -0
  97. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/exception/exception_tags.py +0 -0
  98. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/exception/exceptions.py +0 -0
  99. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/file/__init__.py +0 -0
  100. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/file/open/__init__.py +0 -0
  101. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/file/open/open_file.py +0 -0
  102. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/file/save/__init__.py +0 -0
  103. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/file/save/save_file.py +0 -0
  104. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/hotkey/__init__.py +0 -0
  105. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/hotkey/hotkey_service.py +0 -0
  106. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/json/__init__.py +0 -0
  107. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/json/json_file.py +0 -0
  108. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/json/json_repository.py +0 -0
  109. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/json_format/__init__.py +0 -0
  110. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/json_format/json_process.py +0 -0
  111. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/logging/__init__.py +0 -0
  112. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/logging/loggin_instance.py +0 -0
  113. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/multi_language/__init__.py +0 -0
  114. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/multi_language/france.py +0 -0
  115. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/multi_language/germany.py +0 -0
  116. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/multi_language/italy.py +0 -0
  117. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/multi_language/language_wrapper.py +0 -0
  118. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/multi_language/russian.py +0 -0
  119. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
  120. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/preset_schedule/__init__.py +0 -0
  121. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
  122. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/redirect_manager/__init__.py +0 -0
  123. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
  124. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/smart_pause/__init__.py +0 -0
  125. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
  126. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/theme_schedule/__init__.py +0 -0
  127. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
  128. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/utils/web_url.py +0 -0
  129. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/worker/__init__.py +0 -0
  130. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine/worker/qthread_worker.py +0 -0
  131. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine.egg-info/SOURCES.txt +0 -0
  132. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine.egg-info/dependency_links.txt +0 -0
  133. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine.egg-info/entry_points.txt +0 -0
  134. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine.egg-info/requires.txt +0 -0
  135. {frontengine-1.0.21 → frontengine-1.0.23}/frontengine.egg-info/top_level.txt +0 -0
  136. {frontengine-1.0.21 → frontengine-1.0.23}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frontengine
3
- Version: 1.0.21
3
+ Version: 1.0.23
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
@@ -168,6 +168,46 @@ def idle_seconds():
168
168
  return None
169
169
 
170
170
 
171
+ class PetTimeline:
172
+ """
173
+ 寵物動作時間軸:一連串 (offset_seconds, action_dict),依經過秒數依序觸發。
174
+ A scene-timeline of (offset_seconds, action_dict) events fired in order as
175
+ elapsed time passes. Events are sorted; pop_due advances an internal index.
176
+ """
177
+
178
+ def __init__(self, events) -> None:
179
+ self._events = sorted(list(events), key=lambda item: item[0])
180
+ self._index = 0
181
+
182
+ def __len__(self) -> int:
183
+ return len(self._events)
184
+
185
+ def reset(self) -> None:
186
+ self._index = 0
187
+
188
+ def pop_due(self, elapsed: float) -> list:
189
+ due = []
190
+ while self._index < len(self._events) and self._events[self._index][0] <= elapsed:
191
+ due.append(self._events[self._index][1])
192
+ self._index += 1
193
+ return due
194
+
195
+
196
+ def parse_timeline(raw) -> list:
197
+ """把設定裡的 [{'at': 秒, ...}] 轉成 (offset, action_dict) 清單,忽略格式錯誤者。"""
198
+ events = []
199
+ if isinstance(raw, list):
200
+ for item in raw:
201
+ if not isinstance(item, dict) or "at" not in item:
202
+ continue
203
+ try:
204
+ offset = float(item["at"])
205
+ except (TypeError, ValueError):
206
+ continue
207
+ events.append((offset, item))
208
+ return events
209
+
210
+
171
211
  def pop_due_reminders(now, reminders):
172
212
  """
173
213
  依 now 分出已到期與未到期的提醒。reminders 為 (due_datetime, text) 清單。
@@ -329,6 +369,15 @@ def format_status(level: int, mood_level: str, hunger_level: str, labels: dict)
329
369
  return f"{level_label}{level} · {mood_text} · {hunger_text}"
330
370
 
331
371
 
372
+ def audio_pulse_scale(level, base: float = 0.7, amplitude: float = 0.3) -> float:
373
+ """把音訊音量 (0~1) 轉成脈動縮放比例 / Map an audio level (0..1) to a pulse scale."""
374
+ try:
375
+ clamped = max(0.0, min(1.0, float(level)))
376
+ except (TypeError, ValueError):
377
+ return 1.0
378
+ return base + amplitude * clamped
379
+
380
+
332
381
  def size_for_level(base_size: int, level: int) -> int:
333
382
  """依等級放大基礎尺寸(每級 +8%,上限 +50%)/ Grow base size by level (cap +50%)."""
334
383
  factor = min(1.5, 1.0 + 0.08 * max(0, int(level) - 1))
@@ -798,7 +847,7 @@ class DesktopPetWidget(BaseWidget):
798
847
  def __init__(self, image_path: str, size: int = 128, speed: int = 3,
799
848
  behaviour: str = BEHAVIOUR_FLOOR, climb: bool = True, talk: bool = True,
800
849
  sound_path: Optional[str] = None, sit_on_windows: bool = True,
801
- volume: float = 1.0):
850
+ volume: float = 1.0, audio_react: bool = False):
802
851
  front_engine_logger.info(
803
852
  f"[DesktopPetWidget] Init | path={image_path}, size={size}, speed={speed}, "
804
853
  f"behaviour={behaviour}, climb={climb}, talk={talk}, sound={sound_path}, "
@@ -848,6 +897,10 @@ class DesktopPetWidget(BaseWidget):
848
897
  self._init_sound(sound_path)
849
898
  self._sit_on_windows = bool(sit_on_windows)
850
899
  self._platform_tick = 0
900
+ # 音訊視覺化:依可注入的音量 provider 脈動 / Audio-reactive pulse
901
+ self._audio_react = bool(audio_react)
902
+ self._audio_level_provider = None # callable -> level 0..1 or None
903
+ self._audio_scale = 1.0
851
904
  # 多寵物互動 / Multi-pet interaction
852
905
  self._peers_provider = None
853
906
  self._peer_greeted = False
@@ -862,7 +915,10 @@ class DesktopPetWidget(BaseWidget):
862
915
  self._now_provider = datetime.now
863
916
  self._reminders: list = []
864
917
  self._reminder_timer = QTimer(self)
865
- self._reminder_timer.timeout.connect(self._check_reminders)
918
+ self._reminder_timer.timeout.connect(self._on_schedule_tick)
919
+ # 場景時間軸 / Scene timeline of scheduled actions
920
+ self._timeline = PetTimeline(parse_timeline(user_setting_dict.get("pet_timeline", [])))
921
+ self._timeline_start = None
866
922
  self._bubble = SpeechBubble()
867
923
  self._chatter_timer = QTimer(self)
868
924
  self._chatter_timer.setSingleShot(True)
@@ -983,13 +1039,35 @@ class DesktopPetWidget(BaseWidget):
983
1039
  kind, obj = self._active
984
1040
  return obj.currentPixmap() if kind == "movie" else obj
985
1041
 
1042
+ def set_audio_level_provider(self, provider) -> None:
1043
+ """設定回傳目前音量 (0~1 或 None) 的函式 / Provider of the current audio level."""
1044
+ self._audio_level_provider = provider
1045
+
1046
+ def _update_audio_scale(self) -> None:
1047
+ if not self._audio_react or self._audio_level_provider is None:
1048
+ self._audio_scale = 1.0
1049
+ return
1050
+ try:
1051
+ level = self._audio_level_provider()
1052
+ except Exception: # pragma: no cover - defensive
1053
+ level = None
1054
+ self._audio_scale = 1.0 if level is None else audio_pulse_scale(level)
1055
+
986
1056
  def draw_content(self, painter: QPainter) -> None:
987
1057
  pixmap = self._current_pixmap()
988
1058
  if pixmap is None or pixmap.isNull():
989
1059
  return
990
1060
  if self.motion.facing_left:
991
1061
  pixmap = pixmap.transformed(QTransform().scale(-1, 1))
992
- painter.drawPixmap(QRect(0, 0, self.width(), self.height()), pixmap)
1062
+ scale = self._audio_scale if self._audio_react else 1.0
1063
+ if scale >= 0.999:
1064
+ painter.drawPixmap(QRect(0, 0, self.width(), self.height()), pixmap)
1065
+ else:
1066
+ # Anchor to the bottom so the feet stay on the ground while it pulses.
1067
+ width = int(self.width() * scale)
1068
+ height = int(self.height() * scale)
1069
+ painter.drawPixmap(
1070
+ QRect((self.width() - width) // 2, self.height() - height, width, height), pixmap)
993
1071
 
994
1072
  def start_moving(self, bounds: Tuple[int, int, int, int], interval_ms: int = 33) -> None:
995
1073
  front_engine_logger.info(f"[DesktopPetWidget] start_moving | bounds={bounds}")
@@ -1001,6 +1079,8 @@ class DesktopPetWidget(BaseWidget):
1001
1079
  self._timer.start(max(10, int(interval_ms)))
1002
1080
  self._schedule_chatter()
1003
1081
  self._reminder_timer.start(10000)
1082
+ if self._timeline_start is None:
1083
+ self._timeline_start = self._now_provider()
1004
1084
 
1005
1085
  def _load_messages(self) -> dict:
1006
1086
  d = language_wrapper.language_word_dict
@@ -1069,6 +1149,39 @@ class DesktopPetWidget(BaseWidget):
1069
1149
  self._reminders.append((due, str(text)))
1070
1150
  front_engine_logger.info(f"[DesktopPetWidget] reminder set | due={due}, text={text}")
1071
1151
 
1152
+ def _on_schedule_tick(self) -> None:
1153
+ self._check_reminders()
1154
+ self._check_timeline()
1155
+
1156
+ def set_timeline(self, raw) -> None:
1157
+ """設定場景時間軸並從現在起算 / Set the timeline and start its clock now."""
1158
+ self._timeline = PetTimeline(parse_timeline(raw))
1159
+ self._timeline_start = self._now_provider()
1160
+
1161
+ def _check_timeline(self) -> None:
1162
+ if self._timeline is None or self._timeline_start is None or len(self._timeline) == 0:
1163
+ return
1164
+ elapsed = (self._now_provider() - self._timeline_start).total_seconds()
1165
+ for action in self._timeline.pop_due(elapsed):
1166
+ self._run_timeline_action(action)
1167
+
1168
+ def _run_timeline_action(self, action) -> None:
1169
+ if not isinstance(action, dict):
1170
+ return
1171
+ if action.get("say"):
1172
+ self.say(str(action["say"]))
1173
+ if action.get("sleep"):
1174
+ self.motion.sleep()
1175
+ if action.get("wake"):
1176
+ self.motion.wake()
1177
+ if action.get("feed"):
1178
+ self.feed()
1179
+ if action.get("status"):
1180
+ self.show_status()
1181
+ behaviour = action.get("behaviour")
1182
+ if behaviour in (BEHAVIOUR_FLOOR, BEHAVIOUR_WANDER, BEHAVIOUR_CHASE):
1183
+ self.motion.behaviour = behaviour
1184
+
1072
1185
  def _check_reminders(self) -> None:
1073
1186
  if not self._reminders:
1074
1187
  return
@@ -1228,6 +1341,9 @@ class DesktopPetWidget(BaseWidget):
1228
1341
  x, y = self.motion.step()
1229
1342
  self.move(x, y)
1230
1343
  self._set_active_sprite(self._visual_state())
1344
+ if self._audio_react:
1345
+ self._update_audio_scale()
1346
+ self.update()
1231
1347
  self._peer_tick += 1
1232
1348
  if self._peer_tick >= 5:
1233
1349
  self._peer_tick = 0
@@ -90,6 +90,8 @@ class PetSettingUI(QWidget):
90
90
  for label, value in (("0%", 0), ("25%", 25), ("50%", 50), ("75%", 75), ("100%", 100)):
91
91
  self.volume_combobox.addItem(label, value)
92
92
  self.volume_combobox.setCurrentText("100%")
93
+ self.audio_react_checkbox = QCheckBox(
94
+ language_wrapper.language_word_dict.get("pet_audio_label", "React to audio"))
93
95
 
94
96
  # Start
95
97
  self.start_button = QPushButton(language_wrapper.language_word_dict.get("pet_start", "Spawn pet"))
@@ -124,6 +126,7 @@ class PetSettingUI(QWidget):
124
126
  self.grid_layout.addWidget(self.target_monitor_combobox, 6, 1)
125
127
  self.grid_layout.addWidget(self.volume_label, 6, 2)
126
128
  self.grid_layout.addWidget(self.volume_combobox, 7, 2)
129
+ self.grid_layout.addWidget(self.audio_react_checkbox, 7, 0)
127
130
 
128
131
  def _spawn_pet(self) -> None:
129
132
  """建立、顯示並開始移動一隻寵物(供 Start 與右鍵複製共用)。"""
@@ -139,6 +142,7 @@ class PetSettingUI(QWidget):
139
142
  sound_path=self.pet_sound_path,
140
143
  sit_on_windows=self.sit_checkbox.isChecked(),
141
144
  volume=int(self.volume_combobox.currentData()) / 100.0,
145
+ audio_react=self.audio_react_checkbox.isChecked(),
142
146
  )
143
147
  pet.clone_requested.connect(self._spawn_pet)
144
148
  pet.set_peers_provider(lambda me=pet: self._peer_centers(me))
@@ -253,6 +257,7 @@ class PetSettingUI(QWidget):
253
257
  "sit": self.sit_checkbox.isChecked(),
254
258
  "target_monitor": self.target_monitor_combobox.currentText(),
255
259
  "volume": self.volume_combobox.currentText(),
260
+ "audio_react": self.audio_react_checkbox.isChecked(),
256
261
  }
257
262
 
258
263
  def set_state(self, state: dict) -> None:
@@ -288,3 +293,5 @@ class PetSettingUI(QWidget):
288
293
  index = self.volume_combobox.findText(str(state["volume"]))
289
294
  if index >= 0:
290
295
  self.volume_combobox.setCurrentIndex(index)
296
+ if "audio_react" in state:
297
+ self.audio_react_checkbox.setChecked(bool(state["audio_react"]))
@@ -42,6 +42,9 @@ user_setting_dict: Dict[str, Any] = {
42
42
  # 桌面寵物親密度(累積互動)
43
43
  # Desktop pet affection (accumulated interactions)
44
44
  "pet_affection": 0,
45
+ # 桌面寵物場景時間軸:[{"at": 秒, "say"/"sleep"/"feed"/"behaviour": ...}, ...]
46
+ # Desktop pet scene timeline of scheduled actions
47
+ "pet_timeline": [],
45
48
  }
46
49
 
47
50
 
@@ -247,6 +247,7 @@ english_word_dict = {
247
247
  "pet_talk_label": "Speech bubbles",
248
248
  "pet_sit_label": "Sit on windows",
249
249
  "pet_volume_label": "Volume",
250
+ "pet_audio_label": "React to audio",
250
251
  "pet_chatter_morning": "Good morning!|Rise and shine!|Ready for the day?",
251
252
  "pet_chatter_afternoon": "Good afternoon!|Taking a break?|Keep it up!",
252
253
  "pet_chatter_evening": "Good evening!|How was your day?|Almost done?",
@@ -248,6 +248,7 @@ traditional_chinese_word_dict = {
248
248
  "pet_talk_label": "對話泡泡",
249
249
  "pet_sit_label": "站在視窗上",
250
250
  "pet_volume_label": "音量",
251
+ "pet_audio_label": "隨音訊律動",
251
252
  "pet_chatter_morning": "早安!|新的一天開始囉!|準備好了嗎?",
252
253
  "pet_chatter_afternoon": "午安!|要不要休息一下?|繼續加油!",
253
254
  "pet_chatter_evening": "晚上好!|今天過得如何?|快完成了嗎?",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frontengine
3
- Version: 1.0.21
3
+ Version: 1.0.23
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
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "frontengine"
9
- version = "1.0.21"
9
+ version = "1.0.23"
10
10
  authors = [
11
11
  { name = "JE-Chen", email = "jechenmailman@gmail.com" },
12
12
  ]
File without changes
File without changes
File without changes