frontengine 1.0.9__tar.gz → 1.0.10__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.9 → frontengine-1.0.10}/PKG-INFO +1 -1
  2. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/pet/desktop_pet.py +156 -29
  3. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/pet/pet_setting_ui.py +7 -0
  4. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/multi_language/english.py +1 -0
  5. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/multi_language/traditional_chinese.py +1 -0
  6. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine.egg-info/PKG-INFO +1 -1
  7. {frontengine-1.0.9 → frontengine-1.0.10}/pyproject.toml +1 -1
  8. {frontengine-1.0.9 → frontengine-1.0.10}/LICENSE +0 -0
  9. {frontengine-1.0.9 → frontengine-1.0.10}/README.md +0 -0
  10. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/__init__.py +0 -0
  11. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/__main__.py +0 -0
  12. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/__init__.py +0 -0
  13. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/base_widget.py +0 -0
  14. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/gif/__init__.py +0 -0
  15. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/gif/paint_gif.py +0 -0
  16. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/image/__init__.py +0 -0
  17. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/image/paint_image.py +0 -0
  18. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/load/__init__.py +0 -0
  19. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/load/load_someone_make_ui.py +0 -0
  20. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/overlay_factory.py +0 -0
  21. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/particle/__init__.py +0 -0
  22. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/particle/particle_ui.py +0 -0
  23. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/pet/__init__.py +0 -0
  24. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/scene/__init__.py +0 -0
  25. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/scene/extend_graphic_scene.py +0 -0
  26. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/scene/extend_graphic_view.py +0 -0
  27. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/scene/scene.py +0 -0
  28. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/sound_player/__init__.py +0 -0
  29. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/sound_player/sound_effect.py +0 -0
  30. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/sound_player/sound_player.py +0 -0
  31. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/text/__init__.py +0 -0
  32. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/text/draw_text.py +0 -0
  33. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/video/__init__.py +0 -0
  34. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/video/video_player.py +0 -0
  35. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/web/__init__.py +0 -0
  36. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/web/webview.py +0 -0
  37. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/show/window_helpers.py +0 -0
  38. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/system_tray/__init__.py +0 -0
  39. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/system_tray/extend_system_tray.py +0 -0
  40. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/__init__.py +0 -0
  41. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/color/__init__.py +0 -0
  42. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/color/global_color.py +0 -0
  43. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/dialog/__init__.py +0 -0
  44. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
  45. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
  46. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/dialog/save_file_dialog.py +0 -0
  47. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/main_ui.py +0 -0
  48. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/menu/__init__.py +0 -0
  49. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/menu/help_menu.py +0 -0
  50. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/menu/how_to_menu.py +0 -0
  51. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/menu/language_menu.py +0 -0
  52. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/menu/preset_menu.py +0 -0
  53. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/menu/settings_menu.py +0 -0
  54. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/__init__.py +0 -0
  55. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/control_center/__init__.py +0 -0
  56. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/control_center/control_center_ui.py +0 -0
  57. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/gif/__init__.py +0 -0
  58. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
  59. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/image/__init__.py +0 -0
  60. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/image/image_setting_ui.py +0 -0
  61. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/particle/__init__.py +0 -0
  62. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
  63. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/pet/__init__.py +0 -0
  64. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/scene_setting/__init__.py +0 -0
  65. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
  66. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/scene_setting/scene_page/__init__.py +0 -0
  67. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
  68. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
  69. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
  70. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
  71. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
  72. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
  73. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
  74. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
  75. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
  76. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/sound_player/__init__.py +0 -0
  77. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
  78. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/text/__init__.py +0 -0
  79. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/text/text_setting_ui.py +0 -0
  80. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/utils.py +0 -0
  81. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/video/__init__.py +0 -0
  82. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/video/video_setting_ui.py +0 -0
  83. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/web/__init__.py +0 -0
  84. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/ui/page/web/web_setting_ui.py +0 -0
  85. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/user_setting/__init__.py +0 -0
  86. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/user_setting/preset_repository.py +0 -0
  87. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/user_setting/scene_setting.py +0 -0
  88. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/user_setting/user_setting_file.py +0 -0
  89. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/__init__.py +0 -0
  90. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/browser/__init__.py +0 -0
  91. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/browser/browser.py +0 -0
  92. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/critical_exit/__init__.py +0 -0
  93. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
  94. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/critical_exit/critical_exit.py +0 -0
  95. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/critical_exit/win32_vk.py +0 -0
  96. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/exception/__init__.py +0 -0
  97. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/exception/exception_tags.py +0 -0
  98. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/exception/exceptions.py +0 -0
  99. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/file/__init__.py +0 -0
  100. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/file/open/__init__.py +0 -0
  101. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/file/open/open_file.py +0 -0
  102. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/file/save/__init__.py +0 -0
  103. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/file/save/save_file.py +0 -0
  104. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/hotkey/__init__.py +0 -0
  105. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/hotkey/hotkey_service.py +0 -0
  106. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/json/__init__.py +0 -0
  107. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/json/json_file.py +0 -0
  108. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/json/json_repository.py +0 -0
  109. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/json_format/__init__.py +0 -0
  110. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/json_format/json_process.py +0 -0
  111. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/logging/__init__.py +0 -0
  112. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/logging/loggin_instance.py +0 -0
  113. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/multi_language/__init__.py +0 -0
  114. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/multi_language/france.py +0 -0
  115. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/multi_language/germany.py +0 -0
  116. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/multi_language/italy.py +0 -0
  117. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/multi_language/language_wrapper.py +0 -0
  118. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/multi_language/russian.py +0 -0
  119. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
  120. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/preset_schedule/__init__.py +0 -0
  121. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
  122. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/redirect_manager/__init__.py +0 -0
  123. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
  124. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/smart_pause/__init__.py +0 -0
  125. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
  126. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/theme_schedule/__init__.py +0 -0
  127. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
  128. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/utils/web_url.py +0 -0
  129. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/worker/__init__.py +0 -0
  130. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine/worker/qthread_worker.py +0 -0
  131. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine.egg-info/SOURCES.txt +0 -0
  132. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine.egg-info/dependency_links.txt +0 -0
  133. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine.egg-info/entry_points.txt +0 -0
  134. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine.egg-info/requires.txt +0 -0
  135. {frontengine-1.0.9 → frontengine-1.0.10}/frontengine.egg-info/top_level.txt +0 -0
  136. {frontengine-1.0.9 → frontengine-1.0.10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frontengine
3
- Version: 1.0.9
3
+ Version: 1.0.10
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
@@ -1,4 +1,5 @@
1
1
  import random as _random_module
2
+ import sys
2
3
  from datetime import datetime
3
4
  from pathlib import Path
4
5
  from typing import Optional, Tuple
@@ -96,6 +97,53 @@ def derive_visual_state(dragging: bool, airborne: bool, surface: str, motion_sta
96
97
  return STATE_WALK
97
98
 
98
99
 
100
+ # 列舉可站立視窗時要略過的外殼視窗類別
101
+ # Shell window classes skipped when enumerating standable windows.
102
+ _PLATFORM_SKIP_CLASSES = {"WorkerW", "Progman", "Shell_TrayWnd", "Button"}
103
+
104
+
105
+ def windows_platforms(exclude_hwnds=()) -> list:
106
+ """
107
+ 回傳目前可見頂層視窗的上緣做為可站立平台 (left, right, top_y)。僅 Windows
108
+ 有效,其餘平台或發生錯誤時回傳空清單。
109
+ Return the top edges of visible top-level windows as standable platforms
110
+ (left, right, top_y). Windows only; returns [] elsewhere or on error.
111
+ """
112
+ if sys.platform != "win32":
113
+ return []
114
+ try:
115
+ import ctypes
116
+ from ctypes import wintypes
117
+
118
+ user32 = ctypes.windll.user32
119
+ exclude = {int(h) for h in exclude_hwnds}
120
+ platforms: list = []
121
+
122
+ @ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
123
+ def _collect(hwnd, _lparam):
124
+ if int(hwnd) in exclude or not user32.IsWindowVisible(hwnd):
125
+ return True
126
+ rect = wintypes.RECT()
127
+ if not user32.GetWindowRect(hwnd, ctypes.byref(rect)):
128
+ return True
129
+ width = rect.right - rect.left
130
+ height = rect.bottom - rect.top
131
+ if width < 80 or height < 40:
132
+ return True
133
+ class_buffer = ctypes.create_unicode_buffer(64)
134
+ user32.GetClassNameW(hwnd, class_buffer, 64)
135
+ if class_buffer.value in _PLATFORM_SKIP_CLASSES:
136
+ return True
137
+ platforms.append((rect.left, rect.right, rect.top))
138
+ return True
139
+
140
+ user32.EnumWindows(_collect, 0)
141
+ return platforms
142
+ except Exception as error: # pragma: no cover - Win32 boundary
143
+ front_engine_logger.warning(f"[windows_platforms] error: {error!r}")
144
+ return []
145
+
146
+
99
147
  def message_bucket(hour: int) -> str:
100
148
  """依小時回傳時段 / Time-of-day bucket for the given hour."""
101
149
  hour = int(hour) % 24
@@ -252,6 +300,12 @@ class PetMotion:
252
300
  self.asleep = False
253
301
  self.climb = bool(climb)
254
302
  self.surface = SURFACE_FLOOR
303
+ # 可站立的視窗平台 (left, right, top_y);站在平台上時的狀態
304
+ # Standable window platforms (left, right, top_y) and platform state.
305
+ self.platforms: list = []
306
+ self.on_platform = False
307
+ self.platform_span: Optional[Tuple[float, float]] = None
308
+ self.ground_feet = 0.0
255
309
 
256
310
  @property
257
311
  def facing_left(self) -> bool:
@@ -260,6 +314,10 @@ class PetMotion:
260
314
  def set_bounds(self, bounds: Tuple[int, int, int, int]) -> None:
261
315
  self.bounds = bounds
262
316
 
317
+ def set_platforms(self, platforms) -> None:
318
+ """設定可站立的視窗上緣平台 / Set standable window top-edge platforms."""
319
+ self.platforms = list(platforms)
320
+
263
321
  def set_target(self, x: float, y: float) -> None:
264
322
  self.target = (float(x), float(y))
265
323
 
@@ -270,6 +328,8 @@ class PetMotion:
270
328
  self._airborne = True
271
329
  self.asleep = False
272
330
  self.surface = SURFACE_FLOOR
331
+ self.on_platform = False
332
+ self.platform_span = None
273
333
 
274
334
  def step(self) -> Tuple[int, int]:
275
335
  left, top, right, bottom = self.bounds
@@ -280,38 +340,19 @@ class PetMotion:
280
340
  return self._step_wander(left, top, right, bottom)
281
341
  return self._step_floor(left, top, right, bottom, floor_y)
282
342
 
283
- # --- floor / gravity / climbing ---
343
+ # --- floor / gravity / climbing / window platforms ---
284
344
  def _step_floor(self, left, top, right, bottom, floor_y) -> Tuple[int, int]:
285
- airborne = self._airborne or (self.surface == SURFACE_FLOOR and self.y < floor_y - 0.5)
345
+ airborne = self._airborne or (
346
+ not self.on_platform and self.surface == SURFACE_FLOOR and self.y < floor_y - 0.5
347
+ )
286
348
  if airborne:
287
- self.vy += self.GRAVITY
288
- self.x += self.vx
289
- self.y += self.vy
290
- if self.x <= left:
291
- self.x = float(left)
292
- self.vx = abs(self.vx) * self.WALL_DAMPING
293
- elif self.x + self.width >= right:
294
- self.x = float(right - self.width)
295
- self.vx = -abs(self.vx) * self.WALL_DAMPING
296
- if self.y <= top:
297
- self.y = float(top)
298
- self.vy = abs(self.vy) * self.WALL_DAMPING
299
- if self.y >= floor_y:
300
- self.y = float(floor_y)
301
- if abs(self.vy) > self.BOUNCE_THRESHOLD:
302
- self.vy = -self.vy * self.BOUNCE_DAMPING
303
- else:
304
- self.vy = 0.0
305
- self._airborne = False
306
- self.surface = SURFACE_FLOOR
307
- self.vx = float(self.speed) if self.vx >= 0 else float(-self.speed)
308
- self._new_state(force_walk=True)
309
- return int(self.x), int(self.y)
310
-
349
+ return self._step_fall(left, top, right, bottom, floor_y)
350
+ if self.on_platform:
351
+ return self._step_platform(left, top, right, bottom, floor_y)
311
352
  if self.surface != SURFACE_FLOOR:
312
353
  return self._step_climb(left, top, right, bottom, floor_y)
313
354
 
314
- # grounded on the floor: random walk / idle, with a chance to climb a wall
355
+ # grounded on the screen floor: random walk / idle, with a chance to climb a wall
315
356
  self.y = float(floor_y)
316
357
  if self._state_ticks <= 0:
317
358
  self._new_state()
@@ -335,6 +376,84 @@ class PetMotion:
335
376
  self.vx = -abs(self.vx)
336
377
  return int(self.x), int(self.y)
337
378
 
379
+ def _step_fall(self, left, top, right, bottom, floor_y) -> Tuple[int, int]:
380
+ """落下:撞牆/天花板反彈,並在越過視窗平台或螢幕地板時落地。"""
381
+ prev_feet = self.y + self.height
382
+ self.vy += self.GRAVITY
383
+ self.x += self.vx
384
+ self.y += self.vy
385
+ if self.x <= left:
386
+ self.x = float(left)
387
+ self.vx = abs(self.vx) * self.WALL_DAMPING
388
+ elif self.x + self.width >= right:
389
+ self.x = float(right - self.width)
390
+ self.vx = -abs(self.vx) * self.WALL_DAMPING
391
+ if self.y <= top:
392
+ self.y = float(top)
393
+ self.vy = abs(self.vy) * self.WALL_DAMPING
394
+ new_feet = self.y + self.height
395
+ center_x = self.x + self.width / 2.0
396
+
397
+ land_feet = None
398
+ land_span = None
399
+ for platform_left, platform_right, platform_y in self.platforms:
400
+ if platform_left <= center_x <= platform_right and prev_feet <= platform_y <= new_feet:
401
+ if land_feet is None or platform_y < land_feet:
402
+ land_feet = float(platform_y)
403
+ land_span = (float(platform_left), float(platform_right))
404
+ if prev_feet <= bottom <= new_feet and (land_feet is None or bottom < land_feet):
405
+ land_feet = float(bottom)
406
+ land_span = None
407
+
408
+ if land_feet is not None:
409
+ self.y = float(land_feet - self.height)
410
+ if abs(self.vy) > self.BOUNCE_THRESHOLD:
411
+ self.vy = -self.vy * self.BOUNCE_DAMPING
412
+ else:
413
+ self.vy = 0.0
414
+ self._airborne = False
415
+ self.surface = SURFACE_FLOOR
416
+ self.on_platform = land_span is not None
417
+ self.platform_span = land_span
418
+ self.ground_feet = land_feet
419
+ self.vx = float(self.speed) if self.vx >= 0 else float(-self.speed)
420
+ self._new_state(force_walk=True)
421
+ return int(self.x), int(self.y)
422
+
423
+ def _platform_under(self) -> Optional[Tuple[float, float]]:
424
+ """回傳目前站立平台的水平範圍;平台已消失(視窗移走/關閉)則回傳 None。"""
425
+ body_left = self.x
426
+ body_right = self.x + self.width
427
+ for platform_left, platform_right, platform_y in self.platforms:
428
+ if abs(platform_y - self.ground_feet) <= 2.0 and not (platform_right < body_left or platform_left > body_right):
429
+ return (float(platform_left), float(platform_right))
430
+ return None
431
+
432
+ def _step_platform(self, left, top, right, bottom, floor_y) -> Tuple[int, int]:
433
+ """站在視窗平台上走動;到平台兩端轉向;平台消失則掉落。"""
434
+ span = self._platform_under()
435
+ if span is None:
436
+ self._airborne = True
437
+ self.on_platform = False
438
+ self.platform_span = None
439
+ return self._step_fall(left, top, right, bottom, floor_y)
440
+ self.platform_span = span
441
+ span_left, span_right = span
442
+ self.y = float(self.ground_feet - self.height)
443
+ if self._state_ticks <= 0:
444
+ self._new_state()
445
+ self._state_ticks -= 1
446
+ if self.state != "walk":
447
+ return int(self.x), int(self.y)
448
+ self.x += self.vx
449
+ if self.x <= span_left:
450
+ self.x = float(span_left)
451
+ self.vx = abs(self.vx)
452
+ elif self.x + self.width >= span_right:
453
+ self.x = float(span_right - self.width)
454
+ self.vx = -abs(self.vx)
455
+ return int(self.x), int(self.y)
456
+
338
457
  def _step_climb(self, left, top, right, bottom, floor_y) -> Tuple[int, int]:
339
458
  """沿螢幕邊緣爬行;隨機放手則掉落 / Crawl the screen perimeter; may let go and fall."""
340
459
  if self._rng.random() < self.DROP_CHANCE:
@@ -453,10 +572,11 @@ class DesktopPetWidget(BaseWidget):
453
572
 
454
573
  def __init__(self, image_path: str, size: int = 128, speed: int = 3,
455
574
  behaviour: str = BEHAVIOUR_FLOOR, climb: bool = True, talk: bool = True,
456
- sound_path: Optional[str] = None):
575
+ sound_path: Optional[str] = None, sit_on_windows: bool = True):
457
576
  front_engine_logger.info(
458
577
  f"[DesktopPetWidget] Init | path={image_path}, size={size}, speed={speed}, "
459
- f"behaviour={behaviour}, climb={climb}, talk={talk}, sound={sound_path}"
578
+ f"behaviour={behaviour}, climb={climb}, talk={talk}, sound={sound_path}, "
579
+ f"sit_on_windows={sit_on_windows}"
460
580
  )
461
581
  super().__init__()
462
582
  self.opacity = 1.0
@@ -494,6 +614,8 @@ class DesktopPetWidget(BaseWidget):
494
614
  self._messages = self._load_messages()
495
615
  self._sound: Optional[QSoundEffect] = None
496
616
  self._init_sound(sound_path)
617
+ self._sit_on_windows = bool(sit_on_windows)
618
+ self._platform_tick = 0
497
619
  self._bubble = SpeechBubble()
498
620
  self._chatter_timer = QTimer(self)
499
621
  self._chatter_timer.setSingleShot(True)
@@ -645,6 +767,11 @@ class DesktopPetWidget(BaseWidget):
645
767
  if self.motion.behaviour == BEHAVIOUR_CHASE:
646
768
  cursor = QCursor.pos()
647
769
  self.motion.set_target(cursor.x(), cursor.y())
770
+ elif self._sit_on_windows and self.motion.behaviour == BEHAVIOUR_FLOOR:
771
+ self._platform_tick += 1
772
+ if self._platform_tick >= 10:
773
+ self._platform_tick = 0
774
+ self.motion.set_platforms(windows_platforms(exclude_hwnds=(int(self.winId()),)))
648
775
  x, y = self.motion.step()
649
776
  self.move(x, y)
650
777
  self._set_active_sprite(self._visual_state())
@@ -76,6 +76,8 @@ class PetSettingUI(QWidget):
76
76
  self.climb_checkbox.setChecked(True)
77
77
  self.talk_checkbox = QCheckBox(language_wrapper.language_word_dict.get("pet_talk_label", "Speech bubbles"))
78
78
  self.talk_checkbox.setChecked(True)
79
+ self.sit_checkbox = QCheckBox(language_wrapper.language_word_dict.get("pet_sit_label", "Sit on windows"))
80
+ self.sit_checkbox.setChecked(True)
79
81
 
80
82
  # Start
81
83
  self.start_button = QPushButton(language_wrapper.language_word_dict.get("pet_start", "Spawn pet"))
@@ -102,6 +104,7 @@ class PetSettingUI(QWidget):
102
104
  self.grid_layout.addWidget(self.behaviour_combobox, 3, 1)
103
105
  self.grid_layout.addWidget(self.climb_checkbox, 3, 2)
104
106
  self.grid_layout.addWidget(self.talk_checkbox, 2, 2)
107
+ self.grid_layout.addWidget(self.sit_checkbox, 4, 2)
105
108
  self.grid_layout.addWidget(self.start_button, 4, 0)
106
109
  self.grid_layout.addWidget(self.recent_files_label, 5, 0)
107
110
  self.grid_layout.addWidget(self.recent_files_combobox, 5, 1)
@@ -118,6 +121,7 @@ class PetSettingUI(QWidget):
118
121
  climb=self.climb_checkbox.isChecked(),
119
122
  talk=self.talk_checkbox.isChecked(),
120
123
  sound_path=self.pet_sound_path,
124
+ sit_on_windows=self.sit_checkbox.isChecked(),
121
125
  )
122
126
  pet.clone_requested.connect(self._spawn_pet)
123
127
  pet.set_pet_window_flag()
@@ -203,6 +207,7 @@ class PetSettingUI(QWidget):
203
207
  "climb": self.climb_checkbox.isChecked(),
204
208
  "talk": self.talk_checkbox.isChecked(),
205
209
  "sound": self.pet_sound_path,
210
+ "sit": self.sit_checkbox.isChecked(),
206
211
  }
207
212
 
208
213
  def set_state(self, state: dict) -> None:
@@ -228,3 +233,5 @@ class PetSettingUI(QWidget):
228
233
  self.talk_checkbox.setChecked(bool(state["talk"]))
229
234
  if state.get("sound"):
230
235
  self.pet_sound_path = str(state["sound"])
236
+ if "sit" in state:
237
+ self.sit_checkbox.setChecked(bool(state["sit"]))
@@ -229,6 +229,7 @@ english_word_dict = {
229
229
  "pet_choose_sound": "Choose sound (optional)",
230
230
  "pet_pack_empty": "No sprites (walk/idle/sleep/climb/fall/drag) found in that folder.",
231
231
  "pet_talk_label": "Speech bubbles",
232
+ "pet_sit_label": "Sit on windows",
232
233
  "pet_chatter_morning": "Good morning!|Rise and shine!|Ready for the day?",
233
234
  "pet_chatter_afternoon": "Good afternoon!|Taking a break?|Keep it up!",
234
235
  "pet_chatter_evening": "Good evening!|How was your day?|Almost done?",
@@ -230,6 +230,7 @@ traditional_chinese_word_dict = {
230
230
  "pet_choose_sound": "選擇音效(選填)",
231
231
  "pet_pack_empty": "該資料夾找不到精靈檔(walk/idle/sleep/climb/fall/drag)。",
232
232
  "pet_talk_label": "對話泡泡",
233
+ "pet_sit_label": "站在視窗上",
233
234
  "pet_chatter_morning": "早安!|新的一天開始囉!|準備好了嗎?",
234
235
  "pet_chatter_afternoon": "午安!|要不要休息一下?|繼續加油!",
235
236
  "pet_chatter_evening": "晚上好!|今天過得如何?|快完成了嗎?",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frontengine
3
- Version: 1.0.9
3
+ Version: 1.0.10
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.9"
9
+ version = "1.0.10"
10
10
  authors = [
11
11
  { name = "JE-Chen", email = "jechenmailman@gmail.com" },
12
12
  ]
File without changes
File without changes
File without changes