frontengine 1.0.29__tar.gz → 1.0.30__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.29 → frontengine-1.0.30}/PKG-INFO +1 -1
- frontengine-1.0.30/frontengine/show/base_widget.py +125 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/pet/desktop_pet.py +2 -1
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/text/draw_text.py +40 -0
- frontengine-1.0.30/frontengine/show/window_helpers.py +68 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/dialog/hotkey_settings_dialog.py +2 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/main_ui.py +4 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/control_center/control_center_ui.py +67 -2
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/text/text_setting_ui.py +81 -1
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/web/web_setting_ui.py +114 -2
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/user_setting/user_setting_file.py +34 -1
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/multi_language/english.py +29 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/multi_language/traditional_chinese.py +29 -0
- frontengine-1.0.30/frontengine/utils/system_stats/system_stats.py +309 -0
- frontengine-1.0.30/frontengine/utils/text_source/text_source.py +186 -0
- frontengine-1.0.30/frontengine/utils/theme_schedule/__init__.py +0 -0
- frontengine-1.0.30/frontengine/utils/weather/__init__.py +0 -0
- frontengine-1.0.30/frontengine/utils/weather/weather_service.py +184 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/web_url.py +26 -0
- frontengine-1.0.30/frontengine/worker/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine.egg-info/PKG-INFO +1 -1
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine.egg-info/SOURCES.txt +6 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/pyproject.toml +1 -1
- frontengine-1.0.29/frontengine/show/base_widget.py +0 -47
- frontengine-1.0.29/frontengine/show/window_helpers.py +0 -40
- {frontengine-1.0.29 → frontengine-1.0.30}/LICENSE +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/README.md +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/__main__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/gif/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/gif/paint_gif.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/image/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/image/paint_image.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/load/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/load/load_someone_make_ui.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/overlay_factory.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/particle/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/particle/particle_ui.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/pet/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/scene/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/scene/extend_graphic_scene.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/scene/extend_graphic_view.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/scene/scene.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/sound_player/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/sound_player/sound_effect.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/sound_player/sound_player.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/text/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/video/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/video/video_player.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/web/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/show/web/webview.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/system_tray/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/system_tray/extend_system_tray.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/color/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/color/global_color.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/dialog/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/dialog/save_file_dialog.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/menu/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/menu/help_menu.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/menu/how_to_menu.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/menu/language_menu.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/menu/preset_menu.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/menu/settings_menu.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/control_center/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/gif/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/image/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/image/image_setting_ui.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/particle/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/pet/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/pet/pet_setting_ui.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/scene_setting/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/scene_setting/scene_page/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/sound_player/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/text/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/utils.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/video/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/video/video_setting_ui.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/web/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/user_setting/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/user_setting/preset_repository.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/user_setting/scene_setting.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/audio_meter/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/audio_meter/audio_envelope.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/audio_meter/screen_audio.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/audio_meter/system_audio_meter.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/browser/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/browser/browser.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/critical_exit/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/critical_exit/critical_exit.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/critical_exit/win32_vk.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/exception/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/exception/exception_tags.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/exception/exceptions.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/file/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/file/open/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/file/open/open_file.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/file/save/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/file/save/save_file.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/hotkey/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/hotkey/hotkey_service.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/json/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/json/json_file.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/json/json_repository.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/json_format/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/json_format/json_process.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/logging/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/logging/loggin_instance.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/multi_language/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/multi_language/france.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/multi_language/germany.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/multi_language/italy.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/multi_language/language_wrapper.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/multi_language/russian.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/preset_schedule/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/redirect_manager/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/smart_pause/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
- {frontengine-1.0.29/frontengine/utils/theme_schedule → frontengine-1.0.30/frontengine/utils/system_stats}/__init__.py +0 -0
- {frontengine-1.0.29/frontengine/worker → frontengine-1.0.30/frontengine/utils/text_source}/__init__.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine/worker/qthread_worker.py +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine.egg-info/dependency_links.txt +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine.egg-info/entry_points.txt +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine.egg-info/requires.txt +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/frontengine.egg-info/top_level.txt +0 -0
- {frontengine-1.0.29 → frontengine-1.0.30}/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.30
|
|
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
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from PySide6.QtCore import Qt, QTimer
|
|
5
|
+
from PySide6.QtGui import QColor, QPainter
|
|
6
|
+
from PySide6.QtWidgets import QWidget
|
|
7
|
+
|
|
8
|
+
from frontengine.show.window_helpers import apply_overlay_window_flags, load_overlay_icon
|
|
9
|
+
from frontengine.user_setting.user_setting_file import get_overlay_geometry, save_overlay_geometry
|
|
10
|
+
from frontengine.utils.logging.loggin_instance import front_engine_logger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseWidget(QWidget):
|
|
14
|
+
"""
|
|
15
|
+
BaseWidget: 提供共用 UI 屬性與事件處理的基底類別
|
|
16
|
+
BaseWidget: Base class providing shared UI attributes and event handling
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, draw_location_x: int = 0, draw_location_y: int = 0):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.draw_location_x: int = draw_location_x
|
|
22
|
+
self.draw_location_y: int = draw_location_y
|
|
23
|
+
self.opacity: float = 0.2
|
|
24
|
+
# 鎖定=點擊穿透;解鎖後可用拖曳擺位(見 window_helpers.set_overlay_locked)
|
|
25
|
+
# Locked means click-through; unlocked overlays can be dragged into place.
|
|
26
|
+
self.overlay_locked: bool = True
|
|
27
|
+
self.overlay_lockable: bool = True
|
|
28
|
+
self.overlay_show_on_bottom: bool = False
|
|
29
|
+
# 綠幕背景色:設了就以不透明色填滿背景,方便 OBS 去背
|
|
30
|
+
# Chroma key colour: fills the background opaquely so OBS can key it out.
|
|
31
|
+
self.background_color: Optional[QColor] = None
|
|
32
|
+
self._drag_origin = None
|
|
33
|
+
self._geometry_restored = False
|
|
34
|
+
|
|
35
|
+
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
|
|
36
|
+
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
|
37
|
+
load_overlay_icon(self)
|
|
38
|
+
|
|
39
|
+
def set_ui_window_flag(self, show_on_bottom: bool = False) -> None:
|
|
40
|
+
front_engine_logger.info(f"{self.__class__.__name__} set_ui_window_flag | show_on_bottom: {show_on_bottom}")
|
|
41
|
+
apply_overlay_window_flags(self, show_on_bottom=show_on_bottom)
|
|
42
|
+
|
|
43
|
+
def set_ui_variable(self, opacity: float = 0.2) -> None:
|
|
44
|
+
front_engine_logger.info(f"{self.__class__.__name__} set_ui_variable | opacity: {opacity}")
|
|
45
|
+
self.opacity = opacity
|
|
46
|
+
|
|
47
|
+
def set_background_color(self, color) -> None:
|
|
48
|
+
"""
|
|
49
|
+
設定綠幕背景色(傳 None 取消)。背景以不透明色填滿,內容再依原本的
|
|
50
|
+
不透明度疊上去,這樣 OBS 的 Chroma Key 才能乾淨去背。
|
|
51
|
+
Set the chroma key background colour (None clears it). The background is
|
|
52
|
+
filled opaquely and the content drawn over it at the usual opacity, so
|
|
53
|
+
OBS's chroma key filter can remove it cleanly.
|
|
54
|
+
"""
|
|
55
|
+
if color is None:
|
|
56
|
+
self.background_color = None
|
|
57
|
+
else:
|
|
58
|
+
candidate = QColor(color)
|
|
59
|
+
self.background_color = candidate if candidate.isValid() else None
|
|
60
|
+
self.setAttribute(
|
|
61
|
+
Qt.WidgetAttribute.WA_TranslucentBackground, self.background_color is None)
|
|
62
|
+
self.update()
|
|
63
|
+
|
|
64
|
+
# --- drag to position (only while unlocked) --------------------------
|
|
65
|
+
def mousePressEvent(self, event) -> None:
|
|
66
|
+
if not self.overlay_locked and event.button() == Qt.MouseButton.LeftButton:
|
|
67
|
+
self._drag_origin = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
|
|
68
|
+
super().mousePressEvent(event)
|
|
69
|
+
|
|
70
|
+
def mouseMoveEvent(self, event) -> None:
|
|
71
|
+
if not self.overlay_locked and self._drag_origin is not None:
|
|
72
|
+
self.move(event.globalPosition().toPoint() - self._drag_origin)
|
|
73
|
+
super().mouseMoveEvent(event)
|
|
74
|
+
|
|
75
|
+
def mouseReleaseEvent(self, event) -> None:
|
|
76
|
+
if self._drag_origin is not None:
|
|
77
|
+
self._drag_origin = None
|
|
78
|
+
self.remember_geometry()
|
|
79
|
+
super().mouseReleaseEvent(event)
|
|
80
|
+
|
|
81
|
+
def remember_geometry(self) -> None:
|
|
82
|
+
"""記住目前位置與大小,之後同類覆蓋層會開在同一處。"""
|
|
83
|
+
geometry = self.geometry()
|
|
84
|
+
front_engine_logger.info(
|
|
85
|
+
f"{self.__class__.__name__} remember_geometry | {geometry.x()},{geometry.y()} "
|
|
86
|
+
f"{geometry.width()}x{geometry.height()}"
|
|
87
|
+
)
|
|
88
|
+
save_overlay_geometry(
|
|
89
|
+
self.__class__.__name__, geometry.x(), geometry.y(), geometry.width(), geometry.height())
|
|
90
|
+
|
|
91
|
+
def showEvent(self, event) -> None:
|
|
92
|
+
super().showEvent(event)
|
|
93
|
+
if not self._geometry_restored:
|
|
94
|
+
self._geometry_restored = True
|
|
95
|
+
# 顯示流程結束後再套用,避免和 showFullScreen 打架
|
|
96
|
+
# Applied after the show completes so it does not fight showFullScreen.
|
|
97
|
+
QTimer.singleShot(0, self.restore_saved_geometry)
|
|
98
|
+
|
|
99
|
+
def restore_saved_geometry(self) -> None:
|
|
100
|
+
"""若這類覆蓋層有記住的位置就套用(使用者拖曳過才會有)。"""
|
|
101
|
+
saved = get_overlay_geometry(self.__class__.__name__)
|
|
102
|
+
if not saved:
|
|
103
|
+
return
|
|
104
|
+
try:
|
|
105
|
+
if self.isFullScreen():
|
|
106
|
+
self.showNormal()
|
|
107
|
+
self.setGeometry(*saved)
|
|
108
|
+
except RuntimeError: # pragma: no cover - widget closed mid-callback
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
def paintEvent(self, event) -> None:
|
|
112
|
+
front_engine_logger.debug(f"{self.__class__.__name__} paintEvent | event: {event}")
|
|
113
|
+
painter = QPainter(self)
|
|
114
|
+
if self.background_color is not None:
|
|
115
|
+
painter.fillRect(self.rect(), self.background_color)
|
|
116
|
+
painter.save()
|
|
117
|
+
painter.setOpacity(self.opacity)
|
|
118
|
+
try:
|
|
119
|
+
self.draw_content(painter)
|
|
120
|
+
finally:
|
|
121
|
+
painter.restore()
|
|
122
|
+
|
|
123
|
+
@abstractmethod
|
|
124
|
+
def draw_content(self, painter: QPainter) -> None:
|
|
125
|
+
"""Subclass hook that renders the widget content onto the shared painter."""
|
|
@@ -1161,8 +1161,9 @@ class DesktopPetWidget(BaseWidget):
|
|
|
1161
1161
|
PLAY_CHANCE = 0.25
|
|
1162
1162
|
|
|
1163
1163
|
def set_pet_window_flag(self) -> None:
|
|
1164
|
-
"""寵物需接收滑鼠以便拖曳,故 allow_input=True
|
|
1164
|
+
"""寵物需接收滑鼠以便拖曳,故 allow_input=True 並置頂,也不參與全域鎖定。"""
|
|
1165
1165
|
apply_overlay_window_flags(self, show_on_bottom=False, allow_input=True)
|
|
1166
|
+
self.overlay_lockable = False
|
|
1166
1167
|
|
|
1167
1168
|
def center(self) -> Tuple[int, int]:
|
|
1168
1169
|
return (self.x() + self.width() // 2, self.y() + self.height() // 2)
|
|
@@ -41,6 +41,11 @@ class TextWidget(BaseWidget):
|
|
|
41
41
|
self._marquee_timer = QTimer(self)
|
|
42
42
|
self._marquee_timer.timeout.connect(self._tick_marquee)
|
|
43
43
|
|
|
44
|
+
# 動態文字來源(時鐘/倒數/系統資訊…)/ Dynamic text source
|
|
45
|
+
self.text_source = None
|
|
46
|
+
self._source_timer = QTimer(self)
|
|
47
|
+
self._source_timer.timeout.connect(self.refresh_text)
|
|
48
|
+
|
|
44
49
|
def set_font_variable(self, font_size: int = 100) -> None:
|
|
45
50
|
front_engine_logger.info(f"[TextWidget] set_font_variable | font_size={font_size}")
|
|
46
51
|
self.font_size = font_size
|
|
@@ -82,6 +87,35 @@ class TextWidget(BaseWidget):
|
|
|
82
87
|
else:
|
|
83
88
|
self._marquee_timer.stop()
|
|
84
89
|
|
|
90
|
+
def set_text_source(self, source) -> None:
|
|
91
|
+
"""
|
|
92
|
+
接上動態文字來源(TextSource);靜態來源只取一次文字,不啟動計時器。
|
|
93
|
+
Attach a dynamic text source; a static one is read once, with no timer.
|
|
94
|
+
"""
|
|
95
|
+
front_engine_logger.info(f"[TextWidget] set_text_source | kind={getattr(source, 'kind', None)}")
|
|
96
|
+
self._source_timer.stop()
|
|
97
|
+
self.text_source = source
|
|
98
|
+
if source is None:
|
|
99
|
+
return
|
|
100
|
+
source.start()
|
|
101
|
+
self.refresh_text()
|
|
102
|
+
interval = getattr(source, "refresh_interval_ms", 0)
|
|
103
|
+
if interval > 0:
|
|
104
|
+
self._source_timer.start(int(interval))
|
|
105
|
+
|
|
106
|
+
def refresh_text(self) -> None:
|
|
107
|
+
"""向來源取一次最新文字並重繪 / Pull the latest text and repaint."""
|
|
108
|
+
if self.text_source is None:
|
|
109
|
+
return
|
|
110
|
+
try:
|
|
111
|
+
text = self.text_source.text()
|
|
112
|
+
except Exception as error: # pragma: no cover - defensive around providers
|
|
113
|
+
front_engine_logger.warning(f"[TextWidget] text source failed: {error!r}")
|
|
114
|
+
return
|
|
115
|
+
if text != self.text:
|
|
116
|
+
self.text = text
|
|
117
|
+
self.update()
|
|
118
|
+
|
|
85
119
|
def _tick_marquee(self) -> None:
|
|
86
120
|
text_width = QFontMetrics(self.draw_font).horizontalAdvance(self.text)
|
|
87
121
|
self.marquee_offset -= self.marquee_speed
|
|
@@ -102,6 +136,12 @@ class TextWidget(BaseWidget):
|
|
|
102
136
|
self.text,
|
|
103
137
|
)
|
|
104
138
|
|
|
139
|
+
def closeEvent(self, event) -> None:
|
|
140
|
+
for timer in (self._marquee_timer, self._source_timer):
|
|
141
|
+
if timer.isActive():
|
|
142
|
+
timer.stop()
|
|
143
|
+
super().closeEvent(event)
|
|
144
|
+
|
|
105
145
|
def draw_content(self, painter: QPainter) -> None:
|
|
106
146
|
painter.setFont(self.draw_font)
|
|
107
147
|
if self.outline:
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from PySide6.QtCore import Qt
|
|
5
|
+
from PySide6.QtGui import QIcon
|
|
6
|
+
from PySide6.QtWidgets import QWidget
|
|
7
|
+
|
|
8
|
+
_OVERLAY_BASE_FLAGS = (
|
|
9
|
+
Qt.WindowType.WindowTransparentForInput
|
|
10
|
+
| Qt.WindowType.FramelessWindowHint
|
|
11
|
+
| Qt.WindowType.Tool
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def apply_overlay_window_flags(
|
|
16
|
+
widget: QWidget,
|
|
17
|
+
show_on_bottom: bool = False,
|
|
18
|
+
allow_input: bool = False,
|
|
19
|
+
) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Apply the shared "overlay" window flag combination to a widget.
|
|
22
|
+
|
|
23
|
+
allow_input=True omits WindowTransparentForInput so the widget can still
|
|
24
|
+
receive mouse/keyboard (used by the web overlay when the user opts in).
|
|
25
|
+
"""
|
|
26
|
+
flags = Qt.WindowType.FramelessWindowHint | Qt.WindowType.Tool
|
|
27
|
+
if not allow_input:
|
|
28
|
+
flags |= Qt.WindowType.WindowTransparentForInput
|
|
29
|
+
if show_on_bottom:
|
|
30
|
+
flags |= Qt.WindowType.WindowStaysOnBottomHint
|
|
31
|
+
else:
|
|
32
|
+
flags |= Qt.WindowType.WindowStaysOnTopHint
|
|
33
|
+
widget.setWindowFlags(flags)
|
|
34
|
+
# 記住這次的選擇,之後鎖定/解鎖時才能保持同樣的堆疊層級
|
|
35
|
+
# Remember the choice so lock/unlock keeps the same stacking level.
|
|
36
|
+
widget.overlay_show_on_bottom = bool(show_on_bottom)
|
|
37
|
+
widget.overlay_locked = not bool(allow_input)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def set_overlay_locked(widget: QWidget, locked: bool) -> bool:
|
|
41
|
+
"""
|
|
42
|
+
鎖定覆蓋層=點擊穿透(滑鼠事件直接落到底下的程式);解鎖=可以接收滑鼠、
|
|
43
|
+
用拖曳調整位置。回傳是否真的切換(不可鎖定的覆蓋層如寵物會回傳 False)。
|
|
44
|
+
|
|
45
|
+
Locking an overlay makes it click-through so clicks reach whatever is
|
|
46
|
+
behind it; unlocking lets it take the mouse so it can be dragged into
|
|
47
|
+
place. Returns False for overlays that opt out of locking (e.g. the pet).
|
|
48
|
+
"""
|
|
49
|
+
if not getattr(widget, "overlay_lockable", True):
|
|
50
|
+
return False
|
|
51
|
+
was_visible = widget.isVisible()
|
|
52
|
+
apply_overlay_window_flags(
|
|
53
|
+
widget,
|
|
54
|
+
show_on_bottom=getattr(widget, "overlay_show_on_bottom", False),
|
|
55
|
+
allow_input=not locked,
|
|
56
|
+
)
|
|
57
|
+
# 變更 window flags 會讓視窗被隱藏,原本看得見的要重新顯示
|
|
58
|
+
# Changing window flags hides the window, so show it again if it was visible.
|
|
59
|
+
if was_visible:
|
|
60
|
+
widget.show()
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def load_overlay_icon(widget: QWidget) -> None:
|
|
65
|
+
"""Set the shared frontengine.ico as the widget's window icon if present."""
|
|
66
|
+
icon_path = Path(os.getcwd()) / "frontengine.ico"
|
|
67
|
+
if icon_path.exists() and icon_path.is_file():
|
|
68
|
+
widget.setWindowIcon(QIcon(str(icon_path)))
|
|
@@ -30,6 +30,8 @@ _ACTION_LABELS: Dict[str, Tuple[str, str]] = {
|
|
|
30
30
|
"mute_all": ("control_center_mute_all", "Mute all"),
|
|
31
31
|
"opacity_up": ("hotkey_opacity_up", "Opacity up"),
|
|
32
32
|
"opacity_down": ("hotkey_opacity_down", "Opacity down"),
|
|
33
|
+
"dashboard_next": ("web_dashboard_next", "Next page"),
|
|
34
|
+
"toggle_lock": ("hotkey_toggle_lock", "Lock / unlock overlays"),
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
|
|
@@ -321,6 +321,10 @@ class FrontEngineMainUI(QMainWindow):
|
|
|
321
321
|
self.control_center_ui.step_opacity_all(0.1)
|
|
322
322
|
elif action == "opacity_down":
|
|
323
323
|
self.control_center_ui.step_opacity_all(-0.1)
|
|
324
|
+
elif action == "dashboard_next":
|
|
325
|
+
self.web_setting_ui.show_next_dashboard_page()
|
|
326
|
+
elif action == "toggle_lock":
|
|
327
|
+
self.control_center_ui.toggle_lock_all()
|
|
324
328
|
|
|
325
329
|
def _on_fullscreen_changed(self, fullscreen_active: bool) -> None:
|
|
326
330
|
"""
|
{frontengine-1.0.29 → frontengine-1.0.30}/frontengine/ui/page/control_center/control_center_ui.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from PySide6.QtCore import QTimer, Qt
|
|
2
2
|
from PySide6.QtWidgets import QGridLayout, QWidget, QPushButton, QTextEdit, QScrollArea
|
|
3
3
|
|
|
4
|
+
from frontengine.show.window_helpers import set_overlay_locked
|
|
4
5
|
from frontengine.ui.color.global_color import error_color, output_color
|
|
5
6
|
from frontengine.ui.page.gif.gif_setting_ui import GIFSettingUI
|
|
6
7
|
from frontengine.ui.page.image.image_setting_ui import ImageSettingUI
|
|
@@ -10,6 +11,7 @@ from frontengine.ui.page.sound_player.sound_player_setting_ui import SoundPlayer
|
|
|
10
11
|
from frontengine.ui.page.text.text_setting_ui import TextSettingUI
|
|
11
12
|
from frontengine.ui.page.video.video_setting_ui import VideoSettingUI
|
|
12
13
|
from frontengine.ui.page.web.web_setting_ui import WEBSettingUI
|
|
14
|
+
from frontengine.user_setting.user_setting_file import clear_overlay_geometry
|
|
13
15
|
from frontengine.utils.logging.loggin_instance import front_engine_logger
|
|
14
16
|
from frontengine.utils.multi_language.language_wrapper import language_wrapper
|
|
15
17
|
from frontengine.utils.redirect_manager.redirect_manager_class import redirect_manager_instance
|
|
@@ -74,6 +76,13 @@ class ControlCenterUI(QWidget):
|
|
|
74
76
|
# 全域不透明度(None 表示尚未使用過)/ Global opacity level (None until first used)
|
|
75
77
|
self._global_opacity = None
|
|
76
78
|
self.mute_all_button = self._create_button("control_center_mute_all", self.toggle_mute_all)
|
|
79
|
+
# 覆蓋層鎖定(點擊穿透)與綠幕背景 / Overlay lock (click-through) and chroma key
|
|
80
|
+
self._overlays_locked = True
|
|
81
|
+
self._chroma_key = False
|
|
82
|
+
self.lock_all_button = self._create_button("control_center_unlock_all", self.toggle_lock_all)
|
|
83
|
+
self.chroma_key_button = self._create_button("control_center_chroma_key_on", self.toggle_chroma_key)
|
|
84
|
+
self.reset_positions_button = self._create_button(
|
|
85
|
+
"control_center_reset_positions", self.reset_overlay_positions)
|
|
77
86
|
self.clear_all_button = self._create_button("control_center_close_all", self.clear_all)
|
|
78
87
|
|
|
79
88
|
# Log panel
|
|
@@ -96,8 +105,11 @@ class ControlCenterUI(QWidget):
|
|
|
96
105
|
self.grid_layout.addWidget(self.hide_all_button, 7, 0)
|
|
97
106
|
self.grid_layout.addWidget(self.show_all_button, 8, 0)
|
|
98
107
|
self.grid_layout.addWidget(self.mute_all_button, 9, 0)
|
|
99
|
-
self.grid_layout.addWidget(self.
|
|
100
|
-
self.grid_layout.addWidget(self.
|
|
108
|
+
self.grid_layout.addWidget(self.lock_all_button, 10, 0)
|
|
109
|
+
self.grid_layout.addWidget(self.chroma_key_button, 11, 0)
|
|
110
|
+
self.grid_layout.addWidget(self.reset_positions_button, 12, 0)
|
|
111
|
+
self.grid_layout.addWidget(self.clear_all_button, 13, 0)
|
|
112
|
+
self.grid_layout.addWidget(self.log_panel_scroll_area, 0, 1, 14, 1) # 明確指定 rowSpan, colSpan=1
|
|
101
113
|
self.setLayout(self.grid_layout)
|
|
102
114
|
|
|
103
115
|
# Redirect
|
|
@@ -225,6 +237,59 @@ class ControlCenterUI(QWidget):
|
|
|
225
237
|
"""切換全域靜音狀態 / Toggle the global mute state."""
|
|
226
238
|
self.set_mute_all(not getattr(self, "_muted", False))
|
|
227
239
|
|
|
240
|
+
# --- lock / chroma key / positions -----------------------------------
|
|
241
|
+
CHROMA_KEY_COLOR = "#00ff00"
|
|
242
|
+
|
|
243
|
+
def set_lock_all(self, locked: bool) -> None:
|
|
244
|
+
"""
|
|
245
|
+
鎖定=所有覆蓋層點擊穿透;解鎖=可以用滑鼠把覆蓋層拖到想要的位置,
|
|
246
|
+
放開時位置會被記住。寵物不受影響(它本來就要接收滑鼠)。
|
|
247
|
+
Locked overlays are click-through; unlocked ones can be dragged into
|
|
248
|
+
place and remember where they were dropped. The pet is left alone as it
|
|
249
|
+
needs the mouse either way.
|
|
250
|
+
"""
|
|
251
|
+
front_engine_logger.info(f"ControlCenterUI set_lock_all | locked={locked}")
|
|
252
|
+
self._overlays_locked = bool(locked)
|
|
253
|
+
self._for_each_overlay(lambda widget: set_overlay_locked(widget, self._overlays_locked))
|
|
254
|
+
self.lock_all_button.setText(
|
|
255
|
+
language_wrapper.language_word_dict.get(
|
|
256
|
+
"control_center_unlock_all" if self._overlays_locked else "control_center_lock_all",
|
|
257
|
+
"Unlock overlays" if self._overlays_locked else "Lock overlays",
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def toggle_lock_all(self) -> None:
|
|
262
|
+
"""切換覆蓋層鎖定狀態 / Toggle the overlay lock."""
|
|
263
|
+
self.set_lock_all(not getattr(self, "_overlays_locked", True))
|
|
264
|
+
|
|
265
|
+
def set_chroma_key(self, enabled: bool) -> None:
|
|
266
|
+
"""開關綠幕背景(給 OBS 之類的軟體去背用)。"""
|
|
267
|
+
front_engine_logger.info(f"ControlCenterUI set_chroma_key | enabled={enabled}")
|
|
268
|
+
self._chroma_key = bool(enabled)
|
|
269
|
+
color = self.CHROMA_KEY_COLOR if self._chroma_key else None
|
|
270
|
+
|
|
271
|
+
def apply(widget) -> None:
|
|
272
|
+
setter = getattr(widget, "set_background_color", None)
|
|
273
|
+
if setter is not None:
|
|
274
|
+
setter(color)
|
|
275
|
+
|
|
276
|
+
self._for_each_overlay(apply)
|
|
277
|
+
self.chroma_key_button.setText(
|
|
278
|
+
language_wrapper.language_word_dict.get(
|
|
279
|
+
"control_center_chroma_key_off" if self._chroma_key else "control_center_chroma_key_on",
|
|
280
|
+
"Chroma key off" if self._chroma_key else "Chroma key on",
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
def toggle_chroma_key(self) -> None:
|
|
285
|
+
"""切換綠幕背景 / Toggle the chroma key background."""
|
|
286
|
+
self.set_chroma_key(not getattr(self, "_chroma_key", False))
|
|
287
|
+
|
|
288
|
+
def reset_overlay_positions(self) -> None:
|
|
289
|
+
"""忘掉記住的覆蓋層位置,之後開的覆蓋層回到各自的預設顯示方式。"""
|
|
290
|
+
front_engine_logger.info("ControlCenterUI reset_overlay_positions")
|
|
291
|
+
clear_overlay_geometry()
|
|
292
|
+
|
|
228
293
|
def _sample_overlay_opacity(self) -> float:
|
|
229
294
|
"""取一個現有覆蓋層的不透明度作為起始值 / Seed from an existing overlay's opacity."""
|
|
230
295
|
for widget_list in self._all_overlay_widget_lists():
|
|
@@ -12,6 +12,12 @@ from frontengine.ui.page.utils import (
|
|
|
12
12
|
)
|
|
13
13
|
from frontengine.utils.logging.loggin_instance import front_engine_logger
|
|
14
14
|
from frontengine.utils.multi_language.language_wrapper import language_wrapper
|
|
15
|
+
from frontengine.utils.system_stats.system_stats import system_stats
|
|
16
|
+
from frontengine.utils.text_source.text_source import (
|
|
17
|
+
DEFAULT_TEMPLATES, SOURCE_CLOCK, SOURCE_COUNTDOWN, SOURCE_DATE, SOURCE_STATIC,
|
|
18
|
+
SOURCE_STOPWATCH, SOURCE_SYSTEM, SOURCE_WEATHER, TextSource,
|
|
19
|
+
)
|
|
20
|
+
from frontengine.utils.weather.weather_service import current_weather, lookup_city, weather_service
|
|
15
21
|
|
|
16
22
|
# 名稱 -> HEX 顏色 / Named text colors -> hex
|
|
17
23
|
_TEXT_COLORS = [
|
|
@@ -97,6 +103,29 @@ class TextSettingUI(QWidget):
|
|
|
97
103
|
)
|
|
98
104
|
self.target_monitor_combobox = build_target_monitor_combobox()
|
|
99
105
|
|
|
106
|
+
# 文字來源:靜態字串或時鐘/日期/倒數/碼表/系統資訊/天氣
|
|
107
|
+
# Text source: a fixed string, or a clock/date/countdown/stopwatch/stats/weather feed.
|
|
108
|
+
self.text_source_label = QLabel(
|
|
109
|
+
language_wrapper.language_word_dict.get("text_source_label", "Text source"))
|
|
110
|
+
self.text_source_combobox = QComboBox()
|
|
111
|
+
for kind, fallback in (
|
|
112
|
+
(SOURCE_STATIC, "Static text"), (SOURCE_CLOCK, "Clock"), (SOURCE_DATE, "Date"),
|
|
113
|
+
(SOURCE_COUNTDOWN, "Countdown"), (SOURCE_STOPWATCH, "Stopwatch"),
|
|
114
|
+
(SOURCE_SYSTEM, "System stats"), (SOURCE_WEATHER, "Weather"),
|
|
115
|
+
):
|
|
116
|
+
self.text_source_combobox.addItem(
|
|
117
|
+
language_wrapper.language_word_dict.get(f"text_source_{kind}", fallback), kind)
|
|
118
|
+
self.text_source_combobox.currentIndexChanged.connect(self._update_source_hint)
|
|
119
|
+
self.text_source_hint_label = QLabel("")
|
|
120
|
+
self.text_source_hint_label.setWordWrap(True)
|
|
121
|
+
|
|
122
|
+
# 天氣地點(只有天氣來源會用到)/ Weather location (only used by the weather source)
|
|
123
|
+
self.weather_location_label = QLabel(
|
|
124
|
+
language_wrapper.language_word_dict.get("weather_location_label", "Weather city"))
|
|
125
|
+
self.weather_location_edit = QLineEdit()
|
|
126
|
+
self.weather_location_edit.setPlaceholderText(
|
|
127
|
+
language_wrapper.language_word_dict.get("weather_location_hint", "e.g. Taipei"))
|
|
128
|
+
|
|
100
129
|
# Layout
|
|
101
130
|
self.grid_layout.addWidget(self.opacity_label, 0, 0)
|
|
102
131
|
self.grid_layout.addWidget(self.opacity_slider_value_label, 0, 1)
|
|
@@ -121,11 +150,49 @@ class TextSettingUI(QWidget):
|
|
|
121
150
|
self.grid_layout.addWidget(self.marquee_speed_combobox, 8, 2)
|
|
122
151
|
self.grid_layout.addWidget(self.outline_checkbox, 9, 0)
|
|
123
152
|
self.grid_layout.addWidget(self.outline_color_combobox, 9, 1)
|
|
153
|
+
self.grid_layout.addWidget(self.text_source_label, 10, 0)
|
|
154
|
+
self.grid_layout.addWidget(self.text_source_combobox, 10, 1)
|
|
155
|
+
self.grid_layout.addWidget(self.weather_location_label, 11, 0)
|
|
156
|
+
self.grid_layout.addWidget(self.weather_location_edit, 11, 1)
|
|
157
|
+
self.grid_layout.addWidget(self.text_source_hint_label, 12, 0, 1, 3)
|
|
158
|
+
self._update_source_hint()
|
|
124
159
|
|
|
125
160
|
def set_show_all_screen(self) -> None:
|
|
126
161
|
front_engine_logger.info("[TextSettingUI] set_show_all_screen")
|
|
127
162
|
self.show_all_screen = self.show_on_all_screen_checkbox.isChecked()
|
|
128
163
|
|
|
164
|
+
def _update_source_hint(self) -> None:
|
|
165
|
+
"""依所選來源說明文字欄位的意義(樣板、倒數分鐘數…)。"""
|
|
166
|
+
kind = self.text_source_combobox.currentData() or SOURCE_STATIC
|
|
167
|
+
default = DEFAULT_TEMPLATES.get(kind, "")
|
|
168
|
+
hint = language_wrapper.language_word_dict.get(f"text_source_hint_{kind}", "")
|
|
169
|
+
self.text_source_hint_label.setText(f"{hint} ({default})" if default else hint)
|
|
170
|
+
self.weather_location_label.setVisible(kind == SOURCE_WEATHER)
|
|
171
|
+
self.weather_location_edit.setVisible(kind == SOURCE_WEATHER)
|
|
172
|
+
|
|
173
|
+
def _build_text_source(self) -> TextSource:
|
|
174
|
+
"""依目前設定組出文字來源;天氣來源會先把地名解析成座標。"""
|
|
175
|
+
kind = self.text_source_combobox.currentData() or SOURCE_STATIC
|
|
176
|
+
if kind == SOURCE_WEATHER:
|
|
177
|
+
self._apply_weather_location()
|
|
178
|
+
return TextSource(
|
|
179
|
+
kind=kind,
|
|
180
|
+
template=self.line_edit.text(),
|
|
181
|
+
stats_provider=system_stats,
|
|
182
|
+
weather_provider=current_weather,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def _apply_weather_location(self) -> None:
|
|
186
|
+
"""把使用者填的地名換成座標;查不到就沿用預設地點。"""
|
|
187
|
+
city = self.weather_location_edit.text().strip()
|
|
188
|
+
if not city:
|
|
189
|
+
return
|
|
190
|
+
found = lookup_city(city)
|
|
191
|
+
if found is None:
|
|
192
|
+
front_engine_logger.warning(f"[TextSettingUI] weather city not found: {city}")
|
|
193
|
+
return
|
|
194
|
+
weather_service().set_location(found[0], found[1])
|
|
195
|
+
|
|
129
196
|
def _create_text_widget(self) -> TextWidget:
|
|
130
197
|
front_engine_logger.info("[TextSettingUI] _create_text_widget")
|
|
131
198
|
text_widget = TextWidget(
|
|
@@ -144,6 +211,7 @@ class TextSettingUI(QWidget):
|
|
|
144
211
|
self.marquee_checkbox.isChecked(),
|
|
145
212
|
int(self.marquee_speed_combobox.currentText()),
|
|
146
213
|
)
|
|
214
|
+
text_widget.set_text_source(self._build_text_source())
|
|
147
215
|
self.text_widget_list.append(text_widget)
|
|
148
216
|
return text_widget
|
|
149
217
|
|
|
@@ -170,6 +238,8 @@ class TextSettingUI(QWidget):
|
|
|
170
238
|
"marquee_speed": self.marquee_speed_combobox.currentText(),
|
|
171
239
|
"outline": self.outline_checkbox.isChecked(),
|
|
172
240
|
"outline_color": self.outline_color_combobox.currentText(),
|
|
241
|
+
"text_source": self.text_source_combobox.currentData(),
|
|
242
|
+
"weather_location": self.weather_location_edit.text(),
|
|
173
243
|
}
|
|
174
244
|
|
|
175
245
|
def set_state(self, state: dict) -> None:
|
|
@@ -212,11 +282,21 @@ class TextSettingUI(QWidget):
|
|
|
212
282
|
index = self.outline_color_combobox.findText(str(state["outline_color"]))
|
|
213
283
|
if index >= 0:
|
|
214
284
|
self.outline_color_combobox.setCurrentIndex(index)
|
|
285
|
+
if state.get("text_source") is not None:
|
|
286
|
+
index = self.text_source_combobox.findData(str(state["text_source"]))
|
|
287
|
+
if index >= 0:
|
|
288
|
+
self.text_source_combobox.setCurrentIndex(index)
|
|
289
|
+
if state.get("weather_location") is not None:
|
|
290
|
+
self.weather_location_edit.setText(str(state["weather_location"]))
|
|
215
291
|
|
|
216
292
|
def start_draw_text_on_screen(self) -> None:
|
|
217
293
|
front_engine_logger.info("[TextSettingUI] start_draw_text_on_screen")
|
|
218
294
|
|
|
219
|
-
|
|
295
|
+
# 動態來源不需要固定文字(樣板留白就用預設);只有靜態文字必須填。
|
|
296
|
+
# Dynamic sources fall back to their default template, so only static
|
|
297
|
+
# text has to be filled in.
|
|
298
|
+
is_static = (self.text_source_combobox.currentData() or SOURCE_STATIC) == SOURCE_STATIC
|
|
299
|
+
if is_static and not self.line_edit.text().strip():
|
|
220
300
|
QMessageBox.warning(self, "Warning", language_wrapper.language_word_dict.get("not_prepare"))
|
|
221
301
|
return
|
|
222
302
|
|