frontengine 1.0.5__tar.gz → 1.0.7__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.
- {frontengine-1.0.5 → frontengine-1.0.7}/PKG-INFO +1 -1
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/pet/desktop_pet.py +158 -10
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/pet/pet_setting_ui.py +7 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/multi_language/english.py +7 -1
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/multi_language/traditional_chinese.py +7 -1
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine.egg-info/PKG-INFO +1 -1
- {frontengine-1.0.5 → frontengine-1.0.7}/pyproject.toml +1 -1
- {frontengine-1.0.5 → frontengine-1.0.7}/LICENSE +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/README.md +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/__main__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/base_widget.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/gif/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/gif/paint_gif.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/image/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/image/paint_image.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/load/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/load/load_someone_make_ui.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/overlay_factory.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/particle/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/particle/particle_ui.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/pet/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/scene/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/scene/extend_graphic_scene.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/scene/extend_graphic_view.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/scene/scene.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/sound_player/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/sound_player/sound_effect.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/sound_player/sound_player.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/text/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/text/draw_text.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/video/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/video/video_player.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/web/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/web/webview.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/show/window_helpers.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/system_tray/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/system_tray/extend_system_tray.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/color/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/color/global_color.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/dialog/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/dialog/save_file_dialog.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/main_ui.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/menu/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/menu/help_menu.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/menu/how_to_menu.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/menu/language_menu.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/menu/preset_menu.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/menu/settings_menu.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/control_center/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/control_center/control_center_ui.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/gif/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/image/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/image/image_setting_ui.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/particle/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/pet/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/sound_player/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/text/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/text/text_setting_ui.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/utils.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/video/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/video/video_setting_ui.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/web/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/web/web_setting_ui.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/user_setting/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/user_setting/preset_repository.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/user_setting/scene_setting.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/user_setting/user_setting_file.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/browser/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/browser/browser.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/critical_exit/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/critical_exit/critical_exit.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/critical_exit/win32_vk.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/exception/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/exception/exception_tags.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/exception/exceptions.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/file/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/file/open/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/file/open/open_file.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/file/save/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/file/save/save_file.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/hotkey/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/hotkey/hotkey_service.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/json/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/json/json_file.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/json/json_repository.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/json_format/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/json_format/json_process.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/logging/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/logging/loggin_instance.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/multi_language/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/multi_language/france.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/multi_language/germany.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/multi_language/italy.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/multi_language/language_wrapper.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/multi_language/russian.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/preset_schedule/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/redirect_manager/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/smart_pause/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/theme_schedule/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/web_url.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/worker/__init__.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine/worker/qthread_worker.py +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine.egg-info/SOURCES.txt +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine.egg-info/dependency_links.txt +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine.egg-info/entry_points.txt +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine.egg-info/requires.txt +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/frontengine.egg-info/top_level.txt +0 -0
- {frontengine-1.0.5 → frontengine-1.0.7}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: frontengine
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.7
|
|
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,10 +1,11 @@
|
|
|
1
1
|
import random as _random_module
|
|
2
|
+
from datetime import datetime
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from typing import Optional, Tuple
|
|
4
5
|
|
|
5
6
|
from PySide6.QtCore import Qt, QRect, QTimer, Signal
|
|
6
|
-
from PySide6.QtGui import QAction, QCursor, QMovie, QPainter, QPixmap, QTransform
|
|
7
|
-
from PySide6.QtWidgets import QMenu, QMessageBox
|
|
7
|
+
from PySide6.QtGui import QAction, QColor, QCursor, QFont, QFontMetrics, QMovie, QPainter, QPixmap, QTransform
|
|
8
|
+
from PySide6.QtWidgets import QMenu, QMessageBox, QWidget
|
|
8
9
|
|
|
9
10
|
from frontengine.show.base_widget import BaseWidget
|
|
10
11
|
from frontengine.show.window_helpers import apply_overlay_window_flags
|
|
@@ -25,6 +26,7 @@ SURFACE_RIGHT = "right"
|
|
|
25
26
|
# 視覺狀態(決定顯示哪張精靈)/ Visual states (which sprite to show)
|
|
26
27
|
STATE_WALK = "walk"
|
|
27
28
|
STATE_IDLE = "idle"
|
|
29
|
+
STATE_SLEEP = "sleep"
|
|
28
30
|
STATE_CLIMB = "climb"
|
|
29
31
|
STATE_FALL = "fall"
|
|
30
32
|
STATE_DRAG = "drag"
|
|
@@ -33,13 +35,23 @@ STATE_DRAG = "drag"
|
|
|
33
35
|
# Accepted file stems per state inside a pet pack folder (first match wins).
|
|
34
36
|
_PACK_STATE_ALIASES = {
|
|
35
37
|
STATE_WALK: ("walk", "run", "move"),
|
|
36
|
-
STATE_IDLE: ("idle", "sit", "stand"
|
|
38
|
+
STATE_IDLE: ("idle", "sit", "stand"),
|
|
39
|
+
STATE_SLEEP: ("sleep", "zzz", "rest"),
|
|
37
40
|
STATE_CLIMB: ("climb", "grab", "wall"),
|
|
38
41
|
STATE_FALL: ("fall", "jump"),
|
|
39
42
|
STATE_DRAG: ("drag", "pinch", "grabbed", "held"),
|
|
40
43
|
}
|
|
41
44
|
_PACK_EXTS = (".gif", ".webp", ".png", ".jpg", ".jpeg")
|
|
42
45
|
|
|
46
|
+
# 缺該狀態精靈時的退回順序 / Fallback order when a state's sprite is missing
|
|
47
|
+
_STATE_FALLBACKS = {
|
|
48
|
+
STATE_SLEEP: (STATE_IDLE, STATE_WALK),
|
|
49
|
+
STATE_IDLE: (STATE_WALK,),
|
|
50
|
+
STATE_CLIMB: (STATE_WALK,),
|
|
51
|
+
STATE_FALL: (STATE_WALK,),
|
|
52
|
+
STATE_DRAG: (STATE_WALK,),
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
|
|
44
56
|
def scan_pet_pack(folder: str) -> dict:
|
|
45
57
|
"""
|
|
@@ -75,11 +87,81 @@ def derive_visual_state(dragging: bool, airborne: bool, surface: str, motion_sta
|
|
|
75
87
|
return STATE_FALL
|
|
76
88
|
if surface in (SURFACE_LEFT, SURFACE_RIGHT, SURFACE_CEILING):
|
|
77
89
|
return STATE_CLIMB
|
|
90
|
+
if surface == SURFACE_FLOOR and motion_state == STATE_SLEEP:
|
|
91
|
+
return STATE_SLEEP
|
|
78
92
|
if surface == SURFACE_FLOOR and motion_state == "idle":
|
|
79
93
|
return STATE_IDLE
|
|
80
94
|
return STATE_WALK
|
|
81
95
|
|
|
82
96
|
|
|
97
|
+
def message_bucket(hour: int) -> str:
|
|
98
|
+
"""依小時回傳時段 / Time-of-day bucket for the given hour."""
|
|
99
|
+
hour = int(hour) % 24
|
|
100
|
+
if 5 <= hour < 12:
|
|
101
|
+
return "morning"
|
|
102
|
+
if 12 <= hour < 18:
|
|
103
|
+
return "afternoon"
|
|
104
|
+
if 18 <= hour < 22:
|
|
105
|
+
return "evening"
|
|
106
|
+
return "night"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def pick_message(hour: int, rng, messages: dict) -> str:
|
|
110
|
+
"""
|
|
111
|
+
依時段從對應句庫(加上通用句庫)隨機挑一句台詞;句庫皆空回傳空字串。
|
|
112
|
+
Pick a chatter line from the time bucket plus the generic pool. rng needs
|
|
113
|
+
a ``randrange`` method (injectable for tests).
|
|
114
|
+
"""
|
|
115
|
+
pool = list(messages.get(message_bucket(hour), [])) + list(messages.get("any", []))
|
|
116
|
+
if not pool:
|
|
117
|
+
return ""
|
|
118
|
+
return pool[rng.randrange(len(pool))]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class SpeechBubble(QWidget):
|
|
122
|
+
"""寵物頭上的小對話泡泡,數秒後自動消失 / A small speech bubble that auto-hides."""
|
|
123
|
+
|
|
124
|
+
def __init__(self) -> None:
|
|
125
|
+
super().__init__()
|
|
126
|
+
self._text = ""
|
|
127
|
+
self._font = QFont()
|
|
128
|
+
self._font.setPointSize(11)
|
|
129
|
+
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
|
130
|
+
apply_overlay_window_flags(self, show_on_bottom=False, allow_input=False)
|
|
131
|
+
self._hide_timer = QTimer(self)
|
|
132
|
+
self._hide_timer.setSingleShot(True)
|
|
133
|
+
self._hide_timer.timeout.connect(self.hide)
|
|
134
|
+
|
|
135
|
+
def show_message(self, text: str, anchor: QWidget, duration_ms: int = 4000) -> None:
|
|
136
|
+
self._text = text
|
|
137
|
+
width = min(260, QFontMetrics(self._font).horizontalAdvance(text) + 28)
|
|
138
|
+
self.resize(max(60, width), 44)
|
|
139
|
+
self.reposition(anchor)
|
|
140
|
+
self.show()
|
|
141
|
+
self.raise_()
|
|
142
|
+
self.update()
|
|
143
|
+
self._hide_timer.start(max(500, int(duration_ms)))
|
|
144
|
+
|
|
145
|
+
def reposition(self, anchor: QWidget) -> None:
|
|
146
|
+
geo = anchor.frameGeometry()
|
|
147
|
+
self.move(geo.center().x() - self.width() // 2, geo.top() - self.height() - 6)
|
|
148
|
+
|
|
149
|
+
def paintEvent(self, event) -> None:
|
|
150
|
+
painter = QPainter(self)
|
|
151
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
152
|
+
rect = self.rect().adjusted(1, 1, -1, -1)
|
|
153
|
+
painter.setBrush(QColor(255, 255, 240, 235))
|
|
154
|
+
painter.setPen(QColor(90, 90, 90))
|
|
155
|
+
painter.drawRoundedRect(rect, 8, 8)
|
|
156
|
+
painter.setPen(QColor(30, 30, 30))
|
|
157
|
+
painter.setFont(self._font)
|
|
158
|
+
painter.drawText(
|
|
159
|
+
rect.adjusted(8, 2, -8, -2),
|
|
160
|
+
int(Qt.AlignmentFlag.AlignCenter | Qt.TextFlag.TextWordWrap),
|
|
161
|
+
self._text,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
83
165
|
class PetMotion:
|
|
84
166
|
"""
|
|
85
167
|
純邏輯的寵物移動模型(不依賴 Qt,完全可測試)。包含:
|
|
@@ -123,8 +205,9 @@ class PetMotion:
|
|
|
123
205
|
self.vx = float(self.speed)
|
|
124
206
|
self.vy = 0.0 if behaviour != BEHAVIOUR_WANDER else float(self.speed)
|
|
125
207
|
self._airborne = False
|
|
126
|
-
self.state = "walk" # walk | idle
|
|
208
|
+
self.state = "walk" # walk | idle | sleep
|
|
127
209
|
self._state_ticks = 0
|
|
210
|
+
self._idle_count = 0
|
|
128
211
|
self.target: Optional[Tuple[float, float]] = None
|
|
129
212
|
self.asleep = False
|
|
130
213
|
self.climb = bool(climb)
|
|
@@ -260,13 +343,25 @@ class PetMotion:
|
|
|
260
343
|
def _new_state(self, force_walk: bool = False) -> None:
|
|
261
344
|
if force_walk or self._rng.random() < 0.65:
|
|
262
345
|
self.state = "walk"
|
|
346
|
+
self._idle_count = 0
|
|
263
347
|
if not force_walk:
|
|
264
348
|
self.vx = float(self.speed) if self._rng.random() < 0.5 else float(-self.speed)
|
|
265
349
|
self._state_ticks = self._rng.randint(30, 120)
|
|
350
|
+
elif self._idle_count >= 2:
|
|
351
|
+
# been idling a while -> doze off
|
|
352
|
+
self.state = STATE_SLEEP
|
|
353
|
+
self._idle_count = 0
|
|
354
|
+
self._state_ticks = self._rng.randint(120, 300)
|
|
266
355
|
else:
|
|
267
356
|
self.state = "idle"
|
|
357
|
+
self._idle_count += 1
|
|
268
358
|
self._state_ticks = self._rng.randint(20, 80)
|
|
269
359
|
|
|
360
|
+
def wake(self) -> None:
|
|
361
|
+
"""喚醒寵物並恢復走動(互動時呼叫)/ Wake the pet and resume walking."""
|
|
362
|
+
self._idle_count = 0
|
|
363
|
+
self._new_state(force_walk=True)
|
|
364
|
+
|
|
270
365
|
# --- free wander ---
|
|
271
366
|
def _step_wander(self, left, top, right, bottom) -> Tuple[int, int]:
|
|
272
367
|
if abs(self.vy) < 1e-9:
|
|
@@ -317,10 +412,10 @@ class DesktopPetWidget(BaseWidget):
|
|
|
317
412
|
clone_requested = Signal()
|
|
318
413
|
|
|
319
414
|
def __init__(self, image_path: str, size: int = 128, speed: int = 3,
|
|
320
|
-
behaviour: str = BEHAVIOUR_FLOOR, climb: bool = True):
|
|
415
|
+
behaviour: str = BEHAVIOUR_FLOOR, climb: bool = True, talk: bool = True):
|
|
321
416
|
front_engine_logger.info(
|
|
322
417
|
f"[DesktopPetWidget] Init | path={image_path}, size={size}, speed={speed}, "
|
|
323
|
-
f"behaviour={behaviour}, climb={climb}"
|
|
418
|
+
f"behaviour={behaviour}, climb={climb}, talk={talk}"
|
|
324
419
|
)
|
|
325
420
|
super().__init__()
|
|
326
421
|
self.opacity = 1.0
|
|
@@ -350,6 +445,16 @@ class DesktopPetWidget(BaseWidget):
|
|
|
350
445
|
self._timer = QTimer(self)
|
|
351
446
|
self._timer.timeout.connect(self._on_tick)
|
|
352
447
|
|
|
448
|
+
# Speech bubbles / chatter
|
|
449
|
+
self._talk = bool(talk)
|
|
450
|
+
self._moved = False
|
|
451
|
+
self._chatter_rng = _random_module.SystemRandom()
|
|
452
|
+
self._messages = self._load_messages()
|
|
453
|
+
self._bubble = SpeechBubble()
|
|
454
|
+
self._chatter_timer = QTimer(self)
|
|
455
|
+
self._chatter_timer.setSingleShot(True)
|
|
456
|
+
self._chatter_timer.timeout.connect(self._on_chatter)
|
|
457
|
+
|
|
353
458
|
self.menu = QMenu(self)
|
|
354
459
|
self.clone_action = QAction(language_wrapper.language_word_dict.get("pet_clone", "Clone"), self)
|
|
355
460
|
self.clone_action.triggered.connect(self.clone_requested.emit)
|
|
@@ -382,7 +487,7 @@ class DesktopPetWidget(BaseWidget):
|
|
|
382
487
|
return self._sprites["default"]
|
|
383
488
|
if state in self._sprites:
|
|
384
489
|
return self._sprites[state]
|
|
385
|
-
for fallback in (STATE_WALK,
|
|
490
|
+
for fallback in _STATE_FALLBACKS.get(state, (STATE_WALK,)):
|
|
386
491
|
if fallback in self._sprites:
|
|
387
492
|
return self._sprites[fallback]
|
|
388
493
|
return next(iter(self._sprites.values()), None)
|
|
@@ -426,6 +531,38 @@ class DesktopPetWidget(BaseWidget):
|
|
|
426
531
|
self.motion.y = float(bottom - self.pet_size)
|
|
427
532
|
self.move(int(self.motion.x), int(self.motion.y))
|
|
428
533
|
self._timer.start(max(10, int(interval_ms)))
|
|
534
|
+
self._schedule_chatter()
|
|
535
|
+
|
|
536
|
+
def _load_messages(self) -> dict:
|
|
537
|
+
d = language_wrapper.language_word_dict
|
|
538
|
+
|
|
539
|
+
def lines(key: str, fallback: str) -> list:
|
|
540
|
+
return [part.strip() for part in str(d.get(key, fallback)).split("|") if part.strip()]
|
|
541
|
+
|
|
542
|
+
return {
|
|
543
|
+
"morning": lines("pet_chatter_morning", "Good morning!|Ready for the day?"),
|
|
544
|
+
"afternoon": lines("pet_chatter_afternoon", "Good afternoon!|Taking a break?"),
|
|
545
|
+
"evening": lines("pet_chatter_evening", "Good evening!|How was your day?"),
|
|
546
|
+
"night": lines("pet_chatter_night", "It's late - get some rest!|Still up?"),
|
|
547
|
+
"any": lines("pet_chatter_any", "Hi there!|(^_^)|Keep going!"),
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
def _schedule_chatter(self) -> None:
|
|
551
|
+
if self._talk:
|
|
552
|
+
self._chatter_timer.start(self._chatter_rng.randint(15000, 35000))
|
|
553
|
+
|
|
554
|
+
def _on_chatter(self) -> None:
|
|
555
|
+
self.say()
|
|
556
|
+
self._schedule_chatter()
|
|
557
|
+
|
|
558
|
+
def say(self, text: Optional[str] = None) -> None:
|
|
559
|
+
"""顯示一句台詞(text 為 None 時依時段隨機挑)。"""
|
|
560
|
+
if not self._talk:
|
|
561
|
+
return
|
|
562
|
+
if text is None:
|
|
563
|
+
text = pick_message(datetime.now().hour, self._chatter_rng, self._messages)
|
|
564
|
+
if text:
|
|
565
|
+
self._bubble.show_message(text, self)
|
|
429
566
|
|
|
430
567
|
def _on_tick(self) -> None:
|
|
431
568
|
if self._dragging:
|
|
@@ -437,13 +574,17 @@ class DesktopPetWidget(BaseWidget):
|
|
|
437
574
|
x, y = self.motion.step()
|
|
438
575
|
self.move(x, y)
|
|
439
576
|
self._set_active_sprite(self._visual_state())
|
|
577
|
+
if self._bubble.isVisible():
|
|
578
|
+
self._bubble.reposition(self)
|
|
440
579
|
|
|
441
580
|
# --- dragging + throwing ---
|
|
442
581
|
def mousePressEvent(self, event) -> None:
|
|
443
582
|
if event.button() == Qt.MouseButton.LeftButton:
|
|
444
583
|
self._dragging = True
|
|
584
|
+
self._moved = False
|
|
445
585
|
self._last_move_delta = (0, 0)
|
|
446
586
|
self._drag_offset = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
|
|
587
|
+
self.motion.wake() # interacting wakes a sleeping pet
|
|
447
588
|
self._set_active_sprite(STATE_DRAG)
|
|
448
589
|
super().mousePressEvent(event)
|
|
449
590
|
|
|
@@ -451,6 +592,7 @@ class DesktopPetWidget(BaseWidget):
|
|
|
451
592
|
if self._dragging and self._drag_offset is not None:
|
|
452
593
|
new_top_left = event.globalPosition().toPoint() - self._drag_offset
|
|
453
594
|
self._last_move_delta = (new_top_left.x() - self.x(), new_top_left.y() - self.y())
|
|
595
|
+
self._moved = True
|
|
454
596
|
self.move(new_top_left)
|
|
455
597
|
super().mouseMoveEvent(event)
|
|
456
598
|
|
|
@@ -459,9 +601,12 @@ class DesktopPetWidget(BaseWidget):
|
|
|
459
601
|
self._dragging = False
|
|
460
602
|
self.motion.x = float(self.x())
|
|
461
603
|
self.motion.y = float(self.y())
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
self.motion.
|
|
604
|
+
if self._moved:
|
|
605
|
+
# Fling with the momentum of the last drag movement (floor mode).
|
|
606
|
+
if self.motion.behaviour == BEHAVIOUR_FLOOR:
|
|
607
|
+
self.motion.throw(self._last_move_delta[0], self._last_move_delta[1])
|
|
608
|
+
else:
|
|
609
|
+
self.say() # a click (no drag) makes the pet talk
|
|
465
610
|
super().mouseReleaseEvent(event)
|
|
466
611
|
|
|
467
612
|
def contextMenuEvent(self, event) -> None:
|
|
@@ -470,6 +615,9 @@ class DesktopPetWidget(BaseWidget):
|
|
|
470
615
|
def closeEvent(self, event) -> None:
|
|
471
616
|
if self._timer.isActive():
|
|
472
617
|
self._timer.stop()
|
|
618
|
+
if self._chatter_timer.isActive():
|
|
619
|
+
self._chatter_timer.stop()
|
|
620
|
+
self._bubble.close()
|
|
473
621
|
for kind, obj in self._sprites.values():
|
|
474
622
|
if kind == "movie":
|
|
475
623
|
obj.stop()
|
|
@@ -69,6 +69,8 @@ class PetSettingUI(QWidget):
|
|
|
69
69
|
# Climb walls / ceiling (only meaningful in floor mode)
|
|
70
70
|
self.climb_checkbox = QCheckBox(language_wrapper.language_word_dict.get("pet_climb_label", "Climb walls"))
|
|
71
71
|
self.climb_checkbox.setChecked(True)
|
|
72
|
+
self.talk_checkbox = QCheckBox(language_wrapper.language_word_dict.get("pet_talk_label", "Speech bubbles"))
|
|
73
|
+
self.talk_checkbox.setChecked(True)
|
|
72
74
|
|
|
73
75
|
# Start
|
|
74
76
|
self.start_button = QPushButton(language_wrapper.language_word_dict.get("pet_start", "Spawn pet"))
|
|
@@ -93,6 +95,7 @@ class PetSettingUI(QWidget):
|
|
|
93
95
|
self.grid_layout.addWidget(self.behaviour_label, 3, 0)
|
|
94
96
|
self.grid_layout.addWidget(self.behaviour_combobox, 3, 1)
|
|
95
97
|
self.grid_layout.addWidget(self.climb_checkbox, 3, 2)
|
|
98
|
+
self.grid_layout.addWidget(self.talk_checkbox, 2, 2)
|
|
96
99
|
self.grid_layout.addWidget(self.start_button, 4, 0)
|
|
97
100
|
self.grid_layout.addWidget(self.recent_files_label, 5, 0)
|
|
98
101
|
self.grid_layout.addWidget(self.recent_files_combobox, 5, 1)
|
|
@@ -107,6 +110,7 @@ class PetSettingUI(QWidget):
|
|
|
107
110
|
speed=int(self.speed_combobox.currentText()),
|
|
108
111
|
behaviour=self.behaviour_combobox.currentData(),
|
|
109
112
|
climb=self.climb_checkbox.isChecked(),
|
|
113
|
+
talk=self.talk_checkbox.isChecked(),
|
|
110
114
|
)
|
|
111
115
|
pet.clone_requested.connect(self._spawn_pet)
|
|
112
116
|
pet.set_pet_window_flag()
|
|
@@ -183,6 +187,7 @@ class PetSettingUI(QWidget):
|
|
|
183
187
|
"speed": self.speed_combobox.currentText(),
|
|
184
188
|
"behaviour": self.behaviour_combobox.currentData(),
|
|
185
189
|
"climb": self.climb_checkbox.isChecked(),
|
|
190
|
+
"talk": self.talk_checkbox.isChecked(),
|
|
186
191
|
}
|
|
187
192
|
|
|
188
193
|
def set_state(self, state: dict) -> None:
|
|
@@ -204,3 +209,5 @@ class PetSettingUI(QWidget):
|
|
|
204
209
|
self.behaviour_combobox.setCurrentIndex(index)
|
|
205
210
|
if "climb" in state:
|
|
206
211
|
self.climb_checkbox.setChecked(bool(state["climb"]))
|
|
212
|
+
if "talk" in state:
|
|
213
|
+
self.talk_checkbox.setChecked(bool(state["talk"]))
|
|
@@ -226,7 +226,13 @@ english_word_dict = {
|
|
|
226
226
|
"pet_climb_label": "Climb walls",
|
|
227
227
|
"pet_clone": "Clone",
|
|
228
228
|
"pet_choose_pack": "Choose pet pack...",
|
|
229
|
-
"pet_pack_empty": "No sprites (walk/idle/climb/fall/drag) found in that folder.",
|
|
229
|
+
"pet_pack_empty": "No sprites (walk/idle/sleep/climb/fall/drag) found in that folder.",
|
|
230
|
+
"pet_talk_label": "Speech bubbles",
|
|
231
|
+
"pet_chatter_morning": "Good morning!|Rise and shine!|Ready for the day?",
|
|
232
|
+
"pet_chatter_afternoon": "Good afternoon!|Taking a break?|Keep it up!",
|
|
233
|
+
"pet_chatter_evening": "Good evening!|How was your day?|Almost done?",
|
|
234
|
+
"pet_chatter_night": "It's late - get some rest!|Still up?|Sweet dreams soon~",
|
|
235
|
+
"pet_chatter_any": "Hi there!|(^_^)|Keep going!|You've got this!",
|
|
230
236
|
"pet_start": "Spawn pet",
|
|
231
237
|
"pet_message_box_text": "Pet image not found",
|
|
232
238
|
}
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/multi_language/traditional_chinese.py
RENAMED
|
@@ -227,7 +227,13 @@ traditional_chinese_word_dict = {
|
|
|
227
227
|
"pet_climb_label": "攀爬牆壁",
|
|
228
228
|
"pet_clone": "複製",
|
|
229
229
|
"pet_choose_pack": "選擇動作包資料夾...",
|
|
230
|
-
"pet_pack_empty": "該資料夾找不到精靈檔(walk/idle/climb/fall/drag)。",
|
|
230
|
+
"pet_pack_empty": "該資料夾找不到精靈檔(walk/idle/sleep/climb/fall/drag)。",
|
|
231
|
+
"pet_talk_label": "對話泡泡",
|
|
232
|
+
"pet_chatter_morning": "早安!|新的一天開始囉!|準備好了嗎?",
|
|
233
|
+
"pet_chatter_afternoon": "午安!|要不要休息一下?|繼續加油!",
|
|
234
|
+
"pet_chatter_evening": "晚上好!|今天過得如何?|快完成了嗎?",
|
|
235
|
+
"pet_chatter_night": "很晚了,早點休息!|還沒睡嗎?|祝好夢~",
|
|
236
|
+
"pet_chatter_any": "嗨!|(^_^)|加油!|你可以的!",
|
|
231
237
|
"pet_start": "召喚寵物",
|
|
232
238
|
"pet_message_box_text": "找不到寵物圖片",
|
|
233
239
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: frontengine
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.7
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/control_center/control_center_ui.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/image.py
RENAMED
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/registry.py
RENAMED
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/sound.py
RENAMED
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/text.py
RENAMED
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_page/video.py
RENAMED
|
File without changes
|
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/scene_setting/scene_setting_ui.py
RENAMED
|
File without changes
|
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/ui/page/sound_player/sound_player_setting_ui.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/critical_exit/check_key_is_press.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/multi_language/language_wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/multi_language/simplified_chinese.py
RENAMED
|
File without changes
|
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/preset_schedule/preset_schedule_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/redirect_manager/redirect_manager_class.py
RENAMED
|
File without changes
|
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/smart_pause/smart_pause_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{frontengine-1.0.5 → frontengine-1.0.7}/frontengine/utils/theme_schedule/theme_schedule_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|