RinUI 0.0.9__py3-none-any.whl
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.
- RinUI/__init__.py +4 -0
- RinUI/__pycache__/__init__.cpython-38.pyc +0 -0
- RinUI/assets/fonts/FluentSystemIcons-Index.js +5256 -0
- RinUI/assets/fonts/FluentSystemIcons-Resizable.ttf +0 -0
- RinUI/assets/img/default_app_icon.png +0 -0
- RinUI/components/Base.qml +79 -0
- RinUI/components/BasicInput/Button.qml +148 -0
- RinUI/components/BasicInput/CheckBox.qml +99 -0
- RinUI/components/BasicInput/ComboBox.qml +160 -0
- RinUI/components/BasicInput/DropDownButton.qml +21 -0
- RinUI/components/BasicInput/Hyperlink.qml +18 -0
- RinUI/components/BasicInput/RadioButton.qml +95 -0
- RinUI/components/BasicInput/Slider.qml +212 -0
- RinUI/components/BasicInput/Switch.qml +102 -0
- RinUI/components/BasicInput/ToggleButton.qml +11 -0
- RinUI/components/BasicInput/ToolButton.qml +31 -0
- RinUI/components/ContextMenu.qml +184 -0
- RinUI/components/DateAndTime/PickerView.qml +217 -0
- RinUI/components/DateAndTime/TimePicker.qml +115 -0
- RinUI/components/DialogsAndFlyouts/Dialog.qml +106 -0
- RinUI/components/DialogsAndFlyouts/DialogButtonBox.qml +47 -0
- RinUI/components/DialogsAndFlyouts/Flyout.qml +144 -0
- RinUI/components/DialogsAndFlyouts/Popup.qml +106 -0
- RinUI/components/FocusIndicator.qml +33 -0
- RinUI/components/IconWidget.qml +52 -0
- RinUI/components/Indicator.qml +90 -0
- RinUI/components/Layout/Expander.qml +160 -0
- RinUI/components/Layout/SettingExpander.qml +67 -0
- RinUI/components/Layout/SettingItem.qml +71 -0
- RinUI/components/ListAndCollections/Clip.qml +22 -0
- RinUI/components/ListAndCollections/Frame.qml +44 -0
- RinUI/components/ListAndCollections/ListView.qml +105 -0
- RinUI/components/ListAndCollections/ListViewDelegate.qml +83 -0
- RinUI/components/ListAndCollections/SettingCard.qml +73 -0
- RinUI/components/ListAndCollections/TableView.qml +82 -0
- RinUI/components/ListAndCollections/TableViewDelegate.qml +89 -0
- RinUI/components/MenusAndToolbars/Menu.qml +149 -0
- RinUI/components/MenusAndToolbars/MenuBar.qml +43 -0
- RinUI/components/MenusAndToolbars/MenuItem.qml +119 -0
- RinUI/components/MenusAndToolbars/MenuItemGroup.qml +43 -0
- RinUI/components/MenusAndToolbars/MenuSeparator.qml +14 -0
- RinUI/components/MenusAndToolbars/ToolSeparator.qml +17 -0
- RinUI/components/Navigation/ErrorPage.qml +48 -0
- RinUI/components/Navigation/NavigationBar.qml +179 -0
- RinUI/components/Navigation/NavigationItem.qml +193 -0
- RinUI/components/Navigation/NavigationSubItem.qml +103 -0
- RinUI/components/Navigation/NavigationView.qml +210 -0
- RinUI/components/Navigation/SelectorBar.qml +58 -0
- RinUI/components/ScrollBar.qml +163 -0
- RinUI/components/ScrollView.qml +13 -0
- RinUI/components/Shadow.qml +48 -0
- RinUI/components/StatusAndInfo/InfoBadge.qml +78 -0
- RinUI/components/StatusAndInfo/InfoBar.qml +246 -0
- RinUI/components/StatusAndInfo/ProgressBar.qml +127 -0
- RinUI/components/StatusAndInfo/Toast.qml +237 -0
- RinUI/components/StatusAndInfo/ToolTip.qml +93 -0
- RinUI/components/Text/SpinBox.qml +134 -0
- RinUI/components/Text/Text.qml +44 -0
- RinUI/components/Text/TextField.qml +94 -0
- RinUI/components/Text/TextInput.qml +29 -0
- RinUI/components/Utils/Blur.qml +42 -0
- RinUI/components/qmldir +76 -0
- RinUI/config/rin_ui.json +8 -0
- RinUI/core/__init__.py +3 -0
- RinUI/core/__pycache__/__init__.cpython-38.pyc +0 -0
- RinUI/core/__pycache__/config.cpython-38.pyc +0 -0
- RinUI/core/__pycache__/launcher.cpython-38.pyc +0 -0
- RinUI/core/__pycache__/theme.cpython-38.pyc +0 -0
- RinUI/core/config.py +109 -0
- RinUI/core/launcher.py +144 -0
- RinUI/core/theme.py +342 -0
- RinUI/hooks/__init__.py +3 -0
- RinUI/hooks/hook-RinUI.py +3 -0
- RinUI/qmldir +92 -0
- RinUI/themes/dark.qml +137 -0
- RinUI/themes/light.qml +137 -0
- RinUI/themes/qmldir +7 -0
- RinUI/themes/theme.qml +126 -0
- RinUI/themes/utils.qml +28 -0
- RinUI/utils/Animation.qml +12 -0
- RinUI/utils/FloatLayer.qml +123 -0
- RinUI/utils/FontIconLoader.qml +14 -0
- RinUI/utils/Position.qml +19 -0
- RinUI/utils/Severity.qml +13 -0
- RinUI/utils/Typography.qml +17 -0
- RinUI/utils/qmldir +5 -0
- RinUI/windows/CtrlBtn.qml +119 -0
- RinUI/windows/FluentPage.qml +92 -0
- RinUI/windows/FluentWindow.qml +32 -0
- RinUI/windows/FluentWindowBase.qml +157 -0
- RinUI/windows/TitleBar.qml +132 -0
- RinUI/windows/qmldir +8 -0
- RinUI/windows/window/ApplicationWindow.qml +9 -0
- RinUI/windows/window/Window.qml +112 -0
- rinui-0.0.9.data/data/LICENSE +21 -0
- rinui-0.0.9.data/data/README.md +90 -0
- rinui-0.0.9.dist-info/LICENSE +21 -0
- rinui-0.0.9.dist-info/METADATA +105 -0
- rinui-0.0.9.dist-info/RECORD +102 -0
- rinui-0.0.9.dist-info/WHEEL +5 -0
- rinui-0.0.9.dist-info/entry_points.txt +2 -0
- rinui-0.0.9.dist-info/top_level.txt +1 -0
RinUI/core/launcher.py
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
|
4
|
+
from PySide6.QtCore import QCoreApplication
|
5
|
+
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout
|
6
|
+
from PySide6.QtQml import QQmlApplicationEngine
|
7
|
+
from .theme import ThemeManager
|
8
|
+
from .config import BackdropEffect, is_windows, Theme
|
9
|
+
|
10
|
+
|
11
|
+
def resource_path(relative_path):
|
12
|
+
"""兼容 PyInstaller 打包和开发环境的路径"""
|
13
|
+
if hasattr(sys, '_MEIPASS'):
|
14
|
+
return os.path.join(sys._MEIPASS, relative_path)
|
15
|
+
return os.path.abspath(relative_path)
|
16
|
+
|
17
|
+
|
18
|
+
class TestWindow(QWidget):
|
19
|
+
def __init__(self, theme_manager):
|
20
|
+
super().__init__()
|
21
|
+
layout = QVBoxLayout()
|
22
|
+
self.setLayout(layout)
|
23
|
+
self.setWindowTitle("Test Window")
|
24
|
+
btn = QPushButton(theme_manager.current_theme)
|
25
|
+
btn.clicked.connect(
|
26
|
+
lambda: theme_manager.toggle_theme("Light" if theme_manager.current_theme == "Dark" else "Dark")
|
27
|
+
)
|
28
|
+
self.layout().addWidget(btn)
|
29
|
+
self.resize(400, 300)
|
30
|
+
|
31
|
+
|
32
|
+
class RinUIWindow:
|
33
|
+
# _instance = None
|
34
|
+
#
|
35
|
+
# def __new__(cls, *args, **kwargs): # 单例模式管理
|
36
|
+
# if cls._instance is None:
|
37
|
+
# cls._instance = super().__new__(cls)
|
38
|
+
# return cls._instance
|
39
|
+
|
40
|
+
def __init__(self, qml_path: str):
|
41
|
+
"""
|
42
|
+
创建基于 RinUI 的 QML 应用程序。
|
43
|
+
:param qml_path: str, QML 文件路径
|
44
|
+
"""
|
45
|
+
if hasattr(self, "_initialized") and self._initialized:
|
46
|
+
return
|
47
|
+
self._initialized = True
|
48
|
+
print("✨ RinUIWindow Initializing")
|
49
|
+
|
50
|
+
self.engine = QQmlApplicationEngine()
|
51
|
+
self.theme_manager = ThemeManager()
|
52
|
+
self.qml_path = qml_path
|
53
|
+
self.autoSetWindowsEffect = True
|
54
|
+
|
55
|
+
self._setup_application()
|
56
|
+
self.print_startup_info()
|
57
|
+
|
58
|
+
# 退出清理
|
59
|
+
app_instance = QCoreApplication.instance()
|
60
|
+
if app_instance:
|
61
|
+
app_instance.aboutToQuit.connect(self.theme_manager.clean_up)
|
62
|
+
|
63
|
+
def _setup_application(self):
|
64
|
+
"""Setup"""
|
65
|
+
# RInUI 模块
|
66
|
+
rinui_path = os.path.abspath(os.path.dirname(__file__)) # RinUI/core 目录
|
67
|
+
rinui_import_path = resource_path(os.path.join(rinui_path, "../../")) # 使用 resource_path 处理路径
|
68
|
+
print(f"UI Module Path: {rinui_import_path}")
|
69
|
+
|
70
|
+
if os.path.exists(rinui_import_path):
|
71
|
+
self.engine.addImportPath(rinui_import_path)
|
72
|
+
else:
|
73
|
+
raise FileNotFoundError(f"Cannot find RinUI module: {rinui_import_path}")
|
74
|
+
|
75
|
+
# 主题管理器
|
76
|
+
|
77
|
+
self.engine.rootContext().setContextProperty("ThemeManager", self.theme_manager)
|
78
|
+
try:
|
79
|
+
self.engine.load(self.qml_path)
|
80
|
+
except Exception as e:
|
81
|
+
print(f"Cannot Load QML file: {e}")
|
82
|
+
|
83
|
+
if not self.engine.rootObjects():
|
84
|
+
raise RuntimeError(f"Error loading QML file: {self.qml_path}")
|
85
|
+
|
86
|
+
# 窗口设置
|
87
|
+
|
88
|
+
self.root_window = self.engine.rootObjects()[0]
|
89
|
+
|
90
|
+
self.theme_manager.set_window(self.root_window)
|
91
|
+
self._apply_windows_effects() if self.autoSetWindowsEffect else None
|
92
|
+
|
93
|
+
def _apply_windows_effects(self):
|
94
|
+
"""
|
95
|
+
Apply Windows effects to the window.
|
96
|
+
:return:
|
97
|
+
"""
|
98
|
+
if sys.platform == "win32":
|
99
|
+
self.theme_manager.apply_backdrop_effect(self.theme_manager.get_backdrop_effect())
|
100
|
+
self.theme_manager.apply_window_effects()
|
101
|
+
|
102
|
+
# func名称遵循 Qt 命名规范
|
103
|
+
def setBackdropEffect(self, effect: BackdropEffect):
|
104
|
+
"""
|
105
|
+
Sets the backdrop effect for the window. (Only available on Windows)
|
106
|
+
:param effect: BackdropEffect, type of backdrop effect(Acrylic, Mica, Tabbed, None_)
|
107
|
+
:return:
|
108
|
+
"""
|
109
|
+
if not is_windows() and effect != BackdropEffect.None_:
|
110
|
+
raise OSError("Only can set backdrop effect on Windows platform.")
|
111
|
+
self.theme_manager.apply_backdrop_effect(effect.value)
|
112
|
+
|
113
|
+
def setTheme(self, theme: Theme):
|
114
|
+
"""
|
115
|
+
Sets the theme for the window.
|
116
|
+
:param theme: Theme, type of theme(Auto, Dark, Light)
|
117
|
+
:return:
|
118
|
+
"""
|
119
|
+
self.theme_manager.toggle_theme(theme.value)
|
120
|
+
|
121
|
+
def __getattr__(self, name):
|
122
|
+
"""获取 QML 窗口属性"""
|
123
|
+
try:
|
124
|
+
root = object.__getattribute__(self, "root_window")
|
125
|
+
return getattr(root, name)
|
126
|
+
except AttributeError:
|
127
|
+
raise AttributeError(f"\"RinUIWindow\" object has no attribute '{name}'")
|
128
|
+
|
129
|
+
def print_startup_info(self):
|
130
|
+
border = "=" * 40
|
131
|
+
print(f"\n{border}")
|
132
|
+
print("✨ RinUIWindow Loaded Successfully!")
|
133
|
+
print(f"QML File Path: {self.qml_path}")
|
134
|
+
print(f"Current Theme: {self.theme_manager.current_theme}")
|
135
|
+
print(f"Backdrop Effect: {self.theme_manager.get_backdrop_effect()}")
|
136
|
+
print(f"OS: {sys.platform}")
|
137
|
+
print(border + "\n")
|
138
|
+
|
139
|
+
|
140
|
+
if __name__ == "__main__":
|
141
|
+
# 新用法,应该更规范了捏
|
142
|
+
app = QApplication(sys.argv)
|
143
|
+
example = RinUIWindow("../../examples/gallery.qml")
|
144
|
+
sys.exit(app.exec())
|
RinUI/core/theme.py
ADDED
@@ -0,0 +1,342 @@
|
|
1
|
+
import ctypes
|
2
|
+
import platform
|
3
|
+
import time
|
4
|
+
|
5
|
+
from PySide6.QtCore import QObject, Signal, Slot, QThread
|
6
|
+
|
7
|
+
from .config import DEFAULT_CONFIG, ConfigCenter, PATH, is_win10, is_windows, is_win11, BackdropEffect
|
8
|
+
import sys
|
9
|
+
import darkdetect
|
10
|
+
|
11
|
+
|
12
|
+
def check_darkdetect_support():
|
13
|
+
system = platform.system()
|
14
|
+
if system == "Darwin":
|
15
|
+
mac_ver = platform.mac_ver()[0]
|
16
|
+
major, minor, *_ = map(int, mac_ver.split("."))
|
17
|
+
return (major == 10 and minor >= 14) or major > 10
|
18
|
+
|
19
|
+
elif system == "Windows":
|
20
|
+
return platform.release() >= "10"
|
21
|
+
else:
|
22
|
+
return False
|
23
|
+
|
24
|
+
|
25
|
+
ACCENT_STATES = {
|
26
|
+
"acrylic": 3,
|
27
|
+
"mica": 2,
|
28
|
+
"tabbed": 4,
|
29
|
+
"none": 0
|
30
|
+
}
|
31
|
+
|
32
|
+
ACCENT_SUPPORT = {
|
33
|
+
"acrylic": is_win10(),
|
34
|
+
"mica": is_win11(),
|
35
|
+
"tabbed": is_win10(),
|
36
|
+
"none": True
|
37
|
+
}
|
38
|
+
|
39
|
+
|
40
|
+
class ACCENT_POLICY(ctypes.Structure):
|
41
|
+
_fields_ = [
|
42
|
+
("AccentState", ctypes.c_int),
|
43
|
+
("AccentFlags", ctypes.c_int),
|
44
|
+
("GradientColor", ctypes.c_int),
|
45
|
+
("AnimationId", ctypes.c_int),
|
46
|
+
]
|
47
|
+
|
48
|
+
|
49
|
+
class WINDOWCOMPOSITIONATTRIBDATA(ctypes.Structure):
|
50
|
+
_fields_ = [
|
51
|
+
("Attrib", ctypes.c_int),
|
52
|
+
("pvData", ctypes.c_void_p),
|
53
|
+
("cbData", ctypes.c_size_t),
|
54
|
+
]
|
55
|
+
|
56
|
+
|
57
|
+
class ThemeListener(QThread):
|
58
|
+
"""
|
59
|
+
监听系统颜色模式
|
60
|
+
"""
|
61
|
+
themeChanged = Signal(str)
|
62
|
+
|
63
|
+
def run(self):
|
64
|
+
last_theme = darkdetect.theme()
|
65
|
+
while True:
|
66
|
+
current_theme = darkdetect.theme()
|
67
|
+
if current_theme != last_theme:
|
68
|
+
last_theme = current_theme
|
69
|
+
self.themeChanged.emit(current_theme)
|
70
|
+
print(f"Theme changed: {current_theme}")
|
71
|
+
time.sleep(1)
|
72
|
+
|
73
|
+
def stop(self):
|
74
|
+
self.terminate()
|
75
|
+
|
76
|
+
|
77
|
+
class ThemeManager(QObject):
|
78
|
+
themeChanged = Signal(str)
|
79
|
+
backdropChanged = Signal(str)
|
80
|
+
windows = [] # 窗口句柄们(
|
81
|
+
_instance = None
|
82
|
+
|
83
|
+
# DWM 常量保持不变
|
84
|
+
DWMWA_USE_IMMERSIVE_DARK_MODE = 20
|
85
|
+
DWMWA_WINDOW_CORNER_PREFERENCE = 33
|
86
|
+
DWMWA_NCRENDERING_POLICY = 2
|
87
|
+
DWMNCRENDERINGPOLICY_ENABLED = 2
|
88
|
+
DWMWA_SYSTEMBACKDROP_TYPE = 38
|
89
|
+
WCA_ACCENT_POLICY = 19
|
90
|
+
|
91
|
+
# 圆角
|
92
|
+
DWMWCP_DEFAULT = 0
|
93
|
+
DWMWCP_DONOTROUND = 1
|
94
|
+
DWMWCP_ROUND = 2
|
95
|
+
DWMWCP_ROUNDSMALL = 3
|
96
|
+
|
97
|
+
def clean_up(self):
|
98
|
+
"""
|
99
|
+
清理资源并停止主题监听。
|
100
|
+
"""
|
101
|
+
if self.listener:
|
102
|
+
self.config.save_config()
|
103
|
+
print("Save config.")
|
104
|
+
self.listener.stop()
|
105
|
+
self.listener.wait() # 等待线程结束
|
106
|
+
print("Theme listener stopped.")
|
107
|
+
|
108
|
+
def __new__(cls, *args, **kwargs):
|
109
|
+
"""
|
110
|
+
单例管理,共享主题状态
|
111
|
+
:param args:
|
112
|
+
:param kwargs:
|
113
|
+
"""
|
114
|
+
if cls._instance is None:
|
115
|
+
cls._instance = super().__new__(cls)
|
116
|
+
return cls._instance
|
117
|
+
|
118
|
+
def __init__(self):
|
119
|
+
if hasattr(self, "_initialized") and self._initialized:
|
120
|
+
return
|
121
|
+
self._initialized = True
|
122
|
+
super().__init__()
|
123
|
+
self.theme_dict = {
|
124
|
+
"Light": 0,
|
125
|
+
"Dark": 1
|
126
|
+
}
|
127
|
+
|
128
|
+
self.listener = None # 监听线程
|
129
|
+
self.current_theme = DEFAULT_CONFIG["theme"]["current_theme"] # 当前主题
|
130
|
+
self.is_darkdetect_supported = check_darkdetect_support()
|
131
|
+
|
132
|
+
self.config = ConfigCenter(PATH, "rin_ui.json") # 配置中心
|
133
|
+
self.config.load_config(DEFAULT_CONFIG) # 加载配置
|
134
|
+
|
135
|
+
try:
|
136
|
+
self.current_theme = self.config["theme"]["current_theme"]
|
137
|
+
except Exception as e:
|
138
|
+
print(f"Failed to load config because of {e}, using default config")
|
139
|
+
|
140
|
+
# self.hwnd = None # 窗口句柄
|
141
|
+
|
142
|
+
self.start_listener()
|
143
|
+
|
144
|
+
def start_listener(self):
|
145
|
+
if not self.is_darkdetect_supported:
|
146
|
+
print("darkdetect not supported on this platform")
|
147
|
+
return
|
148
|
+
self.listener = ThemeListener()
|
149
|
+
self.listener.themeChanged.connect(self._handle_system_theme)
|
150
|
+
self.listener.start()
|
151
|
+
|
152
|
+
def set_window(self, window): # 绑定窗口句柄
|
153
|
+
hwnd = int(window.winId())
|
154
|
+
self.windows.append(hwnd)
|
155
|
+
print(f"Window handle set: {hwnd}")
|
156
|
+
|
157
|
+
def _handle_system_theme(self, system_theme):
|
158
|
+
if self.current_theme == "Auto":
|
159
|
+
self._update_window_theme()
|
160
|
+
self.themeChanged.emit(self._actual_theme())
|
161
|
+
else:
|
162
|
+
# 保持当前背景效果不变
|
163
|
+
self._update_window_theme()
|
164
|
+
|
165
|
+
@Slot(str)
|
166
|
+
def apply_backdrop_effect(self, effect_type: str):
|
167
|
+
"""
|
168
|
+
应用背景效果
|
169
|
+
:param effect_type: str, 背景效果类型(acrylic, mica, tabbed, none)
|
170
|
+
"""
|
171
|
+
self._update_window_theme()
|
172
|
+
if not is_windows() or not self.windows:
|
173
|
+
print(f"Cannot apply effect \"{effect_type}\" on this platform")
|
174
|
+
return -2 # 非 windows或未绑定窗口
|
175
|
+
self.backdropChanged.emit(effect_type)
|
176
|
+
|
177
|
+
accent_state = ACCENT_STATES.get(effect_type, 0)
|
178
|
+
if not ACCENT_SUPPORT.get(effect_type, False):
|
179
|
+
print(f"Effect \"{effect_type}\" not supported on this platform")
|
180
|
+
return -1 # 效果不支持
|
181
|
+
|
182
|
+
for hwnd in self.windows:
|
183
|
+
if is_win11():
|
184
|
+
ctypes.windll.dwmapi.DwmSetWindowAttribute(
|
185
|
+
hwnd,
|
186
|
+
self.DWMWA_SYSTEMBACKDROP_TYPE,
|
187
|
+
ctypes.byref(ctypes.c_int(accent_state)),
|
188
|
+
ctypes.sizeof(ctypes.c_int)
|
189
|
+
)
|
190
|
+
elif is_win10() and effect_type == BackdropEffect.Acrylic.value:
|
191
|
+
self._apply_win10_effect(effect_type, hwnd)
|
192
|
+
|
193
|
+
self.config["backdrop_effect"] = effect_type
|
194
|
+
print(
|
195
|
+
f"Applied \"{effect_type.strip().capitalize()}\" effect with "
|
196
|
+
f"{platform.system() + '11' if is_win11() else '10'}"
|
197
|
+
)
|
198
|
+
return 0 # 成功
|
199
|
+
|
200
|
+
def _apply_win10_effect(self, effect_type, hwnd):
|
201
|
+
"""
|
202
|
+
应用 Windows 10 背景效果
|
203
|
+
:param effect_type: str, 背景效果类型(acrylic, tabbed(actually blur)
|
204
|
+
"""
|
205
|
+
backdrop_color = self.config["win10_feat"]["backdrop_dark" if self.is_dark_theme() else "backdrop_light"]
|
206
|
+
|
207
|
+
accent = ACCENT_POLICY()
|
208
|
+
accent.AccentState = ACCENT_STATES[effect_type]
|
209
|
+
accent.AccentFlags = 2
|
210
|
+
accent.GradientColor = backdrop_color
|
211
|
+
data = WINDOWCOMPOSITIONATTRIBDATA()
|
212
|
+
data.Attrib = self.WCA_ACCENT_POLICY
|
213
|
+
data.pvData = ctypes.cast(ctypes.pointer(accent), ctypes.c_void_p)
|
214
|
+
data.cbData = ctypes.sizeof(accent)
|
215
|
+
|
216
|
+
try:
|
217
|
+
set_window_composition = ctypes.windll.user32.SetWindowCompositionAttribute
|
218
|
+
set_window_composition(hwnd, ctypes.byref(data))
|
219
|
+
except Exception as e:
|
220
|
+
print(f"Failed to apply acrylic on Win10: {e}")
|
221
|
+
|
222
|
+
def apply_window_effects(self): # 启用圆角阴影
|
223
|
+
if sys.platform != "win32" or not self.windows:
|
224
|
+
return
|
225
|
+
|
226
|
+
dwm = ctypes.windll.dwmapi
|
227
|
+
|
228
|
+
# 启用非客户端渲染策略(让窗口边框具备阴影)
|
229
|
+
ncrp = ctypes.c_int(self.DWMNCRENDERINGPOLICY_ENABLED)
|
230
|
+
for hwnd in self.windows:
|
231
|
+
dwm.DwmSetWindowAttribute(
|
232
|
+
hwnd,
|
233
|
+
self.DWMWA_NCRENDERING_POLICY,
|
234
|
+
ctypes.byref(ncrp),
|
235
|
+
ctypes.sizeof(ncrp)
|
236
|
+
)
|
237
|
+
|
238
|
+
# 启用圆角效果
|
239
|
+
corner_preference = ctypes.c_int(self.DWMWCP_ROUND)
|
240
|
+
dwm.DwmSetWindowAttribute(
|
241
|
+
hwnd,
|
242
|
+
self.DWMWA_WINDOW_CORNER_PREFERENCE,
|
243
|
+
ctypes.byref(corner_preference),
|
244
|
+
ctypes.sizeof(corner_preference)
|
245
|
+
)
|
246
|
+
print("Enabled Rounded and Shadows")
|
247
|
+
|
248
|
+
def _update_window_theme(self): # 更新窗口的颜色模式
|
249
|
+
if sys.platform != "win32" or not self.windows:
|
250
|
+
return
|
251
|
+
actual_theme = self._actual_theme()
|
252
|
+
for hwnd in self.windows:
|
253
|
+
if is_win11():
|
254
|
+
ctypes.windll.dwmapi.DwmSetWindowAttribute(
|
255
|
+
hwnd,
|
256
|
+
self.DWMWA_USE_IMMERSIVE_DARK_MODE,
|
257
|
+
ctypes.byref(ctypes.c_int(self.theme_dict[actual_theme])),
|
258
|
+
ctypes.sizeof(ctypes.c_int)
|
259
|
+
)
|
260
|
+
elif is_win10() and self.config["backdrop_effect"] == BackdropEffect.Acrylic.value:
|
261
|
+
self._apply_win10_effect(self.config["backdrop_effect"], hwnd)
|
262
|
+
else:
|
263
|
+
print(f"Cannot apply backdrop on {platform.system()}")
|
264
|
+
|
265
|
+
print(f"Window theme updated to {actual_theme}")
|
266
|
+
|
267
|
+
def is_dark_theme(self):
|
268
|
+
"""是否为暗黑主题"""
|
269
|
+
return self._actual_theme() == "Dark"
|
270
|
+
|
271
|
+
def _actual_theme(self):
|
272
|
+
"""实际应用的主题"""
|
273
|
+
if self.current_theme == "Auto":
|
274
|
+
return darkdetect.theme() if self.is_darkdetect_supported else "Light"
|
275
|
+
return self.current_theme
|
276
|
+
|
277
|
+
@Slot(str)
|
278
|
+
def toggle_theme(self, theme: str): # 切换主题
|
279
|
+
if theme not in ["Auto", "Light", "Dark"]: # 三状态
|
280
|
+
return
|
281
|
+
if self.current_theme != theme:
|
282
|
+
print(f"Switching to '{theme}' theme")
|
283
|
+
self.current_theme = theme
|
284
|
+
self.config["theme"]["current_theme"] = theme
|
285
|
+
self._update_window_theme()
|
286
|
+
self.themeChanged.emit(self._actual_theme())
|
287
|
+
|
288
|
+
@Slot(result=str)
|
289
|
+
def get_theme(self):
|
290
|
+
return self._actual_theme()
|
291
|
+
|
292
|
+
@Slot(result=str)
|
293
|
+
def get_theme_name(self):
|
294
|
+
"""获取当前主题名称"""
|
295
|
+
return self.current_theme
|
296
|
+
|
297
|
+
@Slot(str)
|
298
|
+
def receive(self, message):
|
299
|
+
print(message)
|
300
|
+
|
301
|
+
@Slot(result=str)
|
302
|
+
def get_backdrop_effect(self):
|
303
|
+
"""获取当前背景效果"""
|
304
|
+
return self.config["backdrop_effect"]
|
305
|
+
|
306
|
+
@Slot(result=str)
|
307
|
+
def get_theme_color(self):
|
308
|
+
"""获取当前主题颜色"""
|
309
|
+
return self.config["theme_color"]
|
310
|
+
|
311
|
+
@Slot(result=str)
|
312
|
+
def set_theme_color(self, color):
|
313
|
+
"""设置当前主题颜色"""
|
314
|
+
self.config["theme_color"] = color
|
315
|
+
self.config.save_config()
|
316
|
+
|
317
|
+
@Slot(QObject, result=int)
|
318
|
+
def getWindowId(self, window):
|
319
|
+
"""获取窗口的句柄"""
|
320
|
+
print(f"GetWindowId: {window.winId()}")
|
321
|
+
return int(window.winId())
|
322
|
+
|
323
|
+
@Slot(int)
|
324
|
+
def dragWindowEvent(self, hwnd):
|
325
|
+
""" 在Windows 用原生方法拖动"""
|
326
|
+
if not is_windows() or hwnd not in self.windows:
|
327
|
+
print(
|
328
|
+
f"Use Qt method to drag window on: {platform.system()}"
|
329
|
+
if not is_windows() else f"Invalid window handle: {hwnd}"
|
330
|
+
)
|
331
|
+
return
|
332
|
+
|
333
|
+
import win32con
|
334
|
+
from win32gui import ReleaseCapture
|
335
|
+
from win32api import SendMessage
|
336
|
+
|
337
|
+
ReleaseCapture()
|
338
|
+
SendMessage(
|
339
|
+
hwnd,
|
340
|
+
win32con.WM_SYSCOMMAND,
|
341
|
+
win32con.SC_MOVE | win32con.HTCAPTION, 0
|
342
|
+
)
|
RinUI/hooks/__init__.py
ADDED
RinUI/qmldir
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
module RinUI
|
2
|
+
|
3
|
+
singleton Theme 1.0 themes/theme.qml
|
4
|
+
singleton Utils 1.0 themes/utils.qml
|
5
|
+
|
6
|
+
Shadow 1.0 components/Shadow.qml
|
7
|
+
FocusIndicator 1.0 components/FocusIndicator.qml
|
8
|
+
|
9
|
+
# Basic Input
|
10
|
+
Button 1.0 components/BasicInput/Button.qml
|
11
|
+
DropDownButton 1.0 components/BasicInput/DropDownButton.qml
|
12
|
+
ToggleButton 1.0 components/BasicInput/ToggleButton.qml
|
13
|
+
ToolButton 1.0 components/BasicInput/ToolButton.qml
|
14
|
+
ComboBox 1.0 components/BasicInput/ComboBox.qml
|
15
|
+
Switch 1.0 components/BasicInput/Switch.qml
|
16
|
+
Hyperlink 1.0 components/BasicInput/Hyperlink.qml
|
17
|
+
RadioButton 1.0 components/BasicInput/RadioButton.qml
|
18
|
+
CheckBox 1.0 components/BasicInput/CheckBox.qml
|
19
|
+
Slider 1.0 components/BasicInput/Slider.qml
|
20
|
+
|
21
|
+
# Date & Time
|
22
|
+
TimePicker 1.0 components/DateAndTime/TimePicker.qml
|
23
|
+
PickerView 1.0 components/DateAndTime/PickerView.qml
|
24
|
+
|
25
|
+
# Dialogs & Flyouts
|
26
|
+
Dialog 1.0 components/DialogsAndFlyouts/Dialog.qml
|
27
|
+
Flyout 1.0 components/DialogsAndFlyouts/Flyout.qml
|
28
|
+
|
29
|
+
# Layout
|
30
|
+
Expander 1.0 components/Layout/Expander.qml
|
31
|
+
SettingExpander 1.0 components/Layout/SettingExpander.qml
|
32
|
+
SettingItem 1.0 components/Layout/SettingItem.qml
|
33
|
+
|
34
|
+
|
35
|
+
# Text
|
36
|
+
Text 1.0 components/Text/Text.qml
|
37
|
+
TextField 1.0 components/Text/TextField.qml
|
38
|
+
SpinBox 1.0 components/Text/SpinBox.qml
|
39
|
+
TextInput 1.0 components/Text/TextInput.qml
|
40
|
+
|
41
|
+
|
42
|
+
# Status & Info
|
43
|
+
InfoBadge 1.0 components/StatusAndInfo/InfoBadge.qml
|
44
|
+
InfoBar 1.0 components/StatusAndInfo/InfoBar.qml
|
45
|
+
ProgressBar 1.0 components/StatusAndInfo/ProgressBar.qml
|
46
|
+
ToolTip 1.0 components/StatusAndInfo/ToolTip.qml
|
47
|
+
|
48
|
+
# ListAndCollections
|
49
|
+
Clip 1.0 components/ListAndCollections/Clip.qml
|
50
|
+
Frame 1.0 components/ListAndCollections/Frame.qml
|
51
|
+
SettingCard 1.0 components/ListAndCollections/SettingCard.qml
|
52
|
+
ListView 1.0 components/ListAndCollections/ListView.qml
|
53
|
+
ListViewDelegate 1.0 components/ListAndCollections/ListViewDelegate.qml
|
54
|
+
TableView 1.0 components/ListAndCollections/TableView.qml
|
55
|
+
TableViewDelegate 1.0 components/ListAndCollections/TableViewDelegate.qml
|
56
|
+
|
57
|
+
# Menus & Toolbars
|
58
|
+
Menu 1.0 components/MenusAndToolbars/Menu.qml
|
59
|
+
MenuItem 1.0 components/MenusAndToolbars/MenuItem.qml
|
60
|
+
MenuItemGroup 1.0 components/MenusAndToolbars/MenuItemGroup.qml
|
61
|
+
MenuSeparator 1.0 components/MenusAndToolbars/MenuSeparator.qml
|
62
|
+
ToolSeparator 1.0 components/MenusAndToolbars/ToolSeparator.qml
|
63
|
+
MenuBar 1.0 components/MenusAndToolbars/MenuBar.qml
|
64
|
+
|
65
|
+
# Navigation
|
66
|
+
NavigationView 1.0 components/Navigation/NavigationView.qml
|
67
|
+
NavigationBar 1.0 components/Navigation/NavigationBar.qml
|
68
|
+
SelectorBar 1.0 components/Navigation/SelectorBar.qml
|
69
|
+
|
70
|
+
# Window
|
71
|
+
ApplicationWindow 1.0 windows/window/ApplicationWindow.qml
|
72
|
+
Window 1.0 windows/window/Window.qml
|
73
|
+
|
74
|
+
|
75
|
+
ScrollViewer 1.0 components/ScrollViewer.qml
|
76
|
+
ScrollBar 1.0 components/ScrollBar.qml
|
77
|
+
|
78
|
+
IconWidget 1.0 components/IconWidget.qml
|
79
|
+
|
80
|
+
|
81
|
+
ContextMenu 1.0 components/ContextMenu.qml
|
82
|
+
|
83
|
+
FluentWindow 1.0 windows/FluentWindow.qml
|
84
|
+
FluentWindowBase 1.0 windows/FluentWindowBase.qml
|
85
|
+
CtrlBtn 1.0 windows/CtrlBtn.qml
|
86
|
+
FluentPage 1.0 windows/FluentPage.qml
|
87
|
+
|
88
|
+
singleton Typography 1.0 utils/Typography.qml
|
89
|
+
singleton Severity 1.0 utils/Severity.qml
|
90
|
+
singleton Position 1.0 utils/Position.qml
|
91
|
+
|
92
|
+
FloatLayer 1.0 utils/FloatLayer.qml
|