pyqtshadcn 0.1.0__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.
- pyqtshadcn-0.1.0/LICENSE +21 -0
- pyqtshadcn-0.1.0/PKG-INFO +105 -0
- pyqtshadcn-0.1.0/README.md +90 -0
- pyqtshadcn-0.1.0/pyproject.toml +31 -0
- pyqtshadcn-0.1.0/setup.cfg +4 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnAvatar.qml +100 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnBadge.qml +52 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnButton.qml +239 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnButtonGroup.qml +28 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnCard.qml +69 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnCardContent.qml +9 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnCardDescription.qml +13 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnCardFooter.qml +9 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnCardHeader.qml +14 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnCardTitle.qml +13 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnCheckbox.qml +61 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnDialog.qml +97 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnDialogContent.qml +151 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnDialogDescription.qml +13 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnDialogFooter.qml +10 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnDialogHeader.qml +9 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnDialogTitle.qml +14 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnInput.qml +54 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnInputGroup.qml +104 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnProgress.qml +55 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnRadio.qml +65 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnRadioGroup.qml +37 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnSelect.qml +180 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnSlider.qml +79 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnSpinner.qml +48 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnStatusDot.qml +50 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnSwitch.qml +77 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnTabsContent.qml +22 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnTabsList.qml +83 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnTabsTrigger.qml +163 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnTextarea.qml +119 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnToggle.qml +10 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Components/ShadcnToggleGroup.qml +33 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Icons/ShadcnIcon.qml +48 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Theme/QtShadcnTheme.qml +51 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qml/Theme/VariantTokens.qml +101 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/QtShadcn/qmldir +38 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/__init__.py +42 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/capability.py +247 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/alert-circle.svg +17 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/alert-triangle.svg +17 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/arrow-down.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/arrow-left.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/arrow-right.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/arrow-up.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/bell.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/book.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/bookmark.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/briefcase.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/calendar.svg +18 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/check-circle.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/check.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/chevron-down.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/chevron-left.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/chevron-right.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/chevron-up.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/clock.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/code.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/columns.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/copy.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/credit-card.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/download.svg +17 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/external-link.svg +17 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/eye-off.svg +18 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/eye.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/file.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/filter.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/flag.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/folder.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/github.svg +14 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/globe.svg +17 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/grid.svg +19 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/heart.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/home.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/image.svg +17 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/info.svg +17 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/key.svg +17 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/link.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/list.svg +20 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/loader-circle.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/lock.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/mail.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/map-pin.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/menu.svg +17 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/message-circle.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/minus.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/monitor.svg +17 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/moon.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/more-horizontal.svg +17 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/more-vertical.svg +17 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/palette.svg +19 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/pause.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/pencil.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/phone.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/play.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/plus.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/refresh-cw.svg +18 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/search.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/send.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/settings.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/shield.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/sparkles.svg +18 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/star.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/sun.svg +23 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/table.svg +18 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/tag.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/thumbs-up.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/trash-2.svg +19 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/unlock.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/upload.svg +17 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/user.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/users.svg +18 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/wrench.svg +15 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn/icons/x.svg +16 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn.egg-info/PKG-INFO +105 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn.egg-info/SOURCES.txt +122 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn.egg-info/dependency_links.txt +1 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn.egg-info/requires.txt +1 -0
- pyqtshadcn-0.1.0/src/pyqtshadcn.egg-info/top_level.txt +1 -0
pyqtshadcn-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 QtShadcn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyqtshadcn
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: PyQt6 pure-Python adapter for the QtShadcn QML component library (no C++ build).
|
|
5
|
+
Author: QtShadcn
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/QtShadcn/pyqtshadcn
|
|
8
|
+
Project-URL: Repository, https://github.com/QtShadcn/pyqtshadcn
|
|
9
|
+
Keywords: qt,qml,pyqt6,shadcn,ui,components
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: PyQt6>=6.10
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# pyqtshadcn
|
|
17
|
+
|
|
18
|
+
QtShadcn 组件库的 **PyQt6 纯 Python 适配包**——无需 C++ 编译,安装即用。
|
|
19
|
+
|
|
20
|
+
对应主仓库 [`qtshadcn`](https://github.com/QtShadcn/qtshadcn) 的 QML 组件,本包用纯 Python 重写其
|
|
21
|
+
C++ 能力层(`ThemeManager` / `IconRegistry` 两个 QML singleton),使 PyQt6 引擎可直接加载
|
|
22
|
+
原仓库的 30+ 个 QML 组件(零修改复用)。
|
|
23
|
+
|
|
24
|
+
## 为什么不用 C++ 插件
|
|
25
|
+
|
|
26
|
+
主仓库的 C++ 插件针对特定 Qt 小版本编译,而 pip 的 PyQt6 自带另一版本 Qt,ABI 不匹配时
|
|
27
|
+
QML 引擎会拒绝加载插件。本包的能力层用 Python 实现、与 PyQt6 自带 Qt 同 ABI,安装即跑。
|
|
28
|
+
|
|
29
|
+
## 安装
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install pyqtshadcn
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
需要 Python ≥ 3.10 与 PyQt6 ≥ 6.10(pip 会自动安装 PyQt6 依赖)。
|
|
36
|
+
|
|
37
|
+
## 用法
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import sys
|
|
41
|
+
from PyQt6.QtGui import QGuiApplication
|
|
42
|
+
from PyQt6.QtQml import QQmlApplicationEngine
|
|
43
|
+
from PyQt6.QtCore import QUrl
|
|
44
|
+
|
|
45
|
+
from pyqtshadcn import setup # 一行接入
|
|
46
|
+
|
|
47
|
+
app = QGuiApplication(sys.argv)
|
|
48
|
+
engine = QQmlApplicationEngine()
|
|
49
|
+
setup(engine) # 注册 singleton + 添加 QML import path
|
|
50
|
+
|
|
51
|
+
engine.loadData(b"""
|
|
52
|
+
import QtQuick
|
|
53
|
+
import QtQuick.Controls.Basic
|
|
54
|
+
import QtShadcn
|
|
55
|
+
Window {
|
|
56
|
+
width: 400; height: 300; visible: true
|
|
57
|
+
QtShadcnTheme { id: theme }
|
|
58
|
+
ShadcnButton { text: "Hello"; onClicked: theme.mode = "dark" }
|
|
59
|
+
}
|
|
60
|
+
""", QUrl("qrc:/main.qml"))
|
|
61
|
+
|
|
62
|
+
sys.exit(app.exec())
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`setup(engine)` 做两件事:
|
|
66
|
+
|
|
67
|
+
1. `engine.addImportPath(<包内 QtShadcn 目录>)` —— 使 QML 中 `import QtShadcn` 可解析
|
|
68
|
+
2. 注册 `ThemeManager` / `IconRegistry` 为 QML singleton
|
|
69
|
+
|
|
70
|
+
## 本地开发运行
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git clone https://github.com/QtShadcn/pyqtshadcn
|
|
74
|
+
cd pyqtshadcn
|
|
75
|
+
python -m venv .venv && source .venv/bin/activate
|
|
76
|
+
pip install -e . PyQt6
|
|
77
|
+
|
|
78
|
+
# 无界面验证(无需显示器)
|
|
79
|
+
QT_QPA_PLATFORM=offscreen python examples/prototype_main.py
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 与主仓库的同步
|
|
83
|
+
|
|
84
|
+
包内的 `qml/` 与 `icons/` 是主仓库 `src/qml`、`src/assets/icons` 的**副本**(发布时打包进 wheel)。
|
|
85
|
+
主仓库更新组件后,运行同步脚本拉取最新副本:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
python sync.py --src /path/to/qtshadcn # 写文件
|
|
89
|
+
python sync.py --src /path/to/qtshadcn --check # 仅检查是否过期(CI 用)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
同步会自动排除 `ShadcnTable`(见下)并校验 `qmldir` 与目录一致。
|
|
93
|
+
|
|
94
|
+
## 已知限制
|
|
95
|
+
|
|
96
|
+
1. **`ShadcnTable` 暂不支持**:该组件依赖主仓库 C++ 的 `ShadcnTableModel`(QAbstractTableModel 子类)。
|
|
97
|
+
纯 Python 路线下尚未用 `QAbstractTableModel` 移植,故包内未包含此组件。
|
|
98
|
+
2. `IconRegistry` 仅实现**本地内置图标**(随包附带的 75 个 svg),未实现主仓库 C++ 版的
|
|
99
|
+
远程 CDN 兜底(lucide 按需下载 + 磁盘缓存)。
|
|
100
|
+
3. 组件 QML 内已用 `import QtQuick.Controls.Basic` 强制 Basic 风格(token 自绘的前提),
|
|
101
|
+
不依赖 C++ 侧设置样式。
|
|
102
|
+
|
|
103
|
+
## 许可
|
|
104
|
+
|
|
105
|
+
与主仓库一致(MIT)。
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# pyqtshadcn
|
|
2
|
+
|
|
3
|
+
QtShadcn 组件库的 **PyQt6 纯 Python 适配包**——无需 C++ 编译,安装即用。
|
|
4
|
+
|
|
5
|
+
对应主仓库 [`qtshadcn`](https://github.com/QtShadcn/qtshadcn) 的 QML 组件,本包用纯 Python 重写其
|
|
6
|
+
C++ 能力层(`ThemeManager` / `IconRegistry` 两个 QML singleton),使 PyQt6 引擎可直接加载
|
|
7
|
+
原仓库的 30+ 个 QML 组件(零修改复用)。
|
|
8
|
+
|
|
9
|
+
## 为什么不用 C++ 插件
|
|
10
|
+
|
|
11
|
+
主仓库的 C++ 插件针对特定 Qt 小版本编译,而 pip 的 PyQt6 自带另一版本 Qt,ABI 不匹配时
|
|
12
|
+
QML 引擎会拒绝加载插件。本包的能力层用 Python 实现、与 PyQt6 自带 Qt 同 ABI,安装即跑。
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install pyqtshadcn
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
需要 Python ≥ 3.10 与 PyQt6 ≥ 6.10(pip 会自动安装 PyQt6 依赖)。
|
|
21
|
+
|
|
22
|
+
## 用法
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import sys
|
|
26
|
+
from PyQt6.QtGui import QGuiApplication
|
|
27
|
+
from PyQt6.QtQml import QQmlApplicationEngine
|
|
28
|
+
from PyQt6.QtCore import QUrl
|
|
29
|
+
|
|
30
|
+
from pyqtshadcn import setup # 一行接入
|
|
31
|
+
|
|
32
|
+
app = QGuiApplication(sys.argv)
|
|
33
|
+
engine = QQmlApplicationEngine()
|
|
34
|
+
setup(engine) # 注册 singleton + 添加 QML import path
|
|
35
|
+
|
|
36
|
+
engine.loadData(b"""
|
|
37
|
+
import QtQuick
|
|
38
|
+
import QtQuick.Controls.Basic
|
|
39
|
+
import QtShadcn
|
|
40
|
+
Window {
|
|
41
|
+
width: 400; height: 300; visible: true
|
|
42
|
+
QtShadcnTheme { id: theme }
|
|
43
|
+
ShadcnButton { text: "Hello"; onClicked: theme.mode = "dark" }
|
|
44
|
+
}
|
|
45
|
+
""", QUrl("qrc:/main.qml"))
|
|
46
|
+
|
|
47
|
+
sys.exit(app.exec())
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`setup(engine)` 做两件事:
|
|
51
|
+
|
|
52
|
+
1. `engine.addImportPath(<包内 QtShadcn 目录>)` —— 使 QML 中 `import QtShadcn` 可解析
|
|
53
|
+
2. 注册 `ThemeManager` / `IconRegistry` 为 QML singleton
|
|
54
|
+
|
|
55
|
+
## 本地开发运行
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
git clone https://github.com/QtShadcn/pyqtshadcn
|
|
59
|
+
cd pyqtshadcn
|
|
60
|
+
python -m venv .venv && source .venv/bin/activate
|
|
61
|
+
pip install -e . PyQt6
|
|
62
|
+
|
|
63
|
+
# 无界面验证(无需显示器)
|
|
64
|
+
QT_QPA_PLATFORM=offscreen python examples/prototype_main.py
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 与主仓库的同步
|
|
68
|
+
|
|
69
|
+
包内的 `qml/` 与 `icons/` 是主仓库 `src/qml`、`src/assets/icons` 的**副本**(发布时打包进 wheel)。
|
|
70
|
+
主仓库更新组件后,运行同步脚本拉取最新副本:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python sync.py --src /path/to/qtshadcn # 写文件
|
|
74
|
+
python sync.py --src /path/to/qtshadcn --check # 仅检查是否过期(CI 用)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
同步会自动排除 `ShadcnTable`(见下)并校验 `qmldir` 与目录一致。
|
|
78
|
+
|
|
79
|
+
## 已知限制
|
|
80
|
+
|
|
81
|
+
1. **`ShadcnTable` 暂不支持**:该组件依赖主仓库 C++ 的 `ShadcnTableModel`(QAbstractTableModel 子类)。
|
|
82
|
+
纯 Python 路线下尚未用 `QAbstractTableModel` 移植,故包内未包含此组件。
|
|
83
|
+
2. `IconRegistry` 仅实现**本地内置图标**(随包附带的 75 个 svg),未实现主仓库 C++ 版的
|
|
84
|
+
远程 CDN 兜底(lucide 按需下载 + 磁盘缓存)。
|
|
85
|
+
3. 组件 QML 内已用 `import QtQuick.Controls.Basic` 强制 Basic 风格(token 自绘的前提),
|
|
86
|
+
不依赖 C++ 侧设置样式。
|
|
87
|
+
|
|
88
|
+
## 许可
|
|
89
|
+
|
|
90
|
+
与主仓库一致(MIT)。
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyqtshadcn"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "PyQt6 pure-Python adapter for the QtShadcn QML component library (no C++ build)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "QtShadcn" }]
|
|
14
|
+
keywords = ["qt", "qml", "pyqt6", "shadcn", "ui", "components"]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"PyQt6>=6.10",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://github.com/QtShadcn/pyqtshadcn"
|
|
21
|
+
Repository = "https://github.com/QtShadcn/pyqtshadcn"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["src"]
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.package-data]
|
|
27
|
+
pyqtshadcn = [
|
|
28
|
+
"QtShadcn/qml/**/*.qml",
|
|
29
|
+
"icons/**/*.svg",
|
|
30
|
+
"QtShadcn/qmldir",
|
|
31
|
+
]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import QtShadcn
|
|
3
|
+
|
|
4
|
+
// 圆形头像组件:首字母 / 图标 / 图片 三选一;右下角可选状态小圆点
|
|
5
|
+
//
|
|
6
|
+
// 用法:
|
|
7
|
+
// ShadcnAvatar {
|
|
8
|
+
// size: ShadcnAvatar.Size.Medium
|
|
9
|
+
// text: "Ryan"
|
|
10
|
+
// status: ShadcnStatusDot.Status.Online
|
|
11
|
+
// }
|
|
12
|
+
//
|
|
13
|
+
// ShadcnAvatar { iconName: "user" }
|
|
14
|
+
// ShadcnAvatar { source: "file:/path/to/photo.png"; size: ShadcnAvatar.Size.Large }
|
|
15
|
+
Item {
|
|
16
|
+
id: root
|
|
17
|
+
|
|
18
|
+
enum Size { XSmall, Small, Medium, Large, XLarge }
|
|
19
|
+
|
|
20
|
+
property int size: ShadcnAvatar.Size.Medium
|
|
21
|
+
// 显示内容,按优先级:source > iconName > text(首字母)
|
|
22
|
+
property url source: ""
|
|
23
|
+
property string iconName: ""
|
|
24
|
+
property string text: ""
|
|
25
|
+
|
|
26
|
+
property color bgColor: theme.primary
|
|
27
|
+
property color textColor: theme.primaryForeground
|
|
28
|
+
property color iconColor: theme.primaryForeground
|
|
29
|
+
|
|
30
|
+
property int status: ShadcnStatusDot.Status.None
|
|
31
|
+
|
|
32
|
+
QtShadcnTheme { id: theme }
|
|
33
|
+
|
|
34
|
+
// Size 枚举 → 像素直径
|
|
35
|
+
readonly property int _diameter:
|
|
36
|
+
size === ShadcnAvatar.Size.XSmall ? 24 :
|
|
37
|
+
size === ShadcnAvatar.Size.Small ? 32 :
|
|
38
|
+
size === ShadcnAvatar.Size.Large ? 56 :
|
|
39
|
+
size === ShadcnAvatar.Size.XLarge ? 72 : 44 // Medium
|
|
40
|
+
|
|
41
|
+
implicitWidth: _diameter
|
|
42
|
+
implicitHeight: _diameter
|
|
43
|
+
|
|
44
|
+
// 是否使用某种内容模式(source / icon / text 三选一)
|
|
45
|
+
readonly property bool _useImage: root.source.toString().length > 0
|
|
46
|
+
readonly property bool _useIcon: !_useImage && root.iconName.length > 0
|
|
47
|
+
|
|
48
|
+
// 视觉底:圆形填充(根是 Item 不 clip,方便 status 点越界)
|
|
49
|
+
Rectangle {
|
|
50
|
+
id: bg
|
|
51
|
+
anchors.fill: parent
|
|
52
|
+
radius: width / 2
|
|
53
|
+
color: root.bgColor
|
|
54
|
+
clip: _useImage // 只有图片模式需要 clip 保证圆形裁切
|
|
55
|
+
|
|
56
|
+
// 模式一:图片
|
|
57
|
+
Image {
|
|
58
|
+
anchors.fill: parent
|
|
59
|
+
visible: root._useImage
|
|
60
|
+
source: root.source
|
|
61
|
+
fillMode: Image.PreserveAspectCrop
|
|
62
|
+
asynchronous: true
|
|
63
|
+
cache: true
|
|
64
|
+
sourceSize.width: parent.width * 2 // Retina 清晰
|
|
65
|
+
sourceSize.height: parent.height * 2
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 模式二:图标(无图片时)
|
|
69
|
+
ShadcnIcon {
|
|
70
|
+
visible: root._useIcon
|
|
71
|
+
anchors.centerIn: parent
|
|
72
|
+
name: root.iconName
|
|
73
|
+
size: Math.max(12, Math.round(root._diameter * 0.5))
|
|
74
|
+
color: root.iconColor
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 模式三:首字母(无图片也无图标时)
|
|
78
|
+
Text {
|
|
79
|
+
visible: !root._useImage && !root._useIcon && root.text.length > 0
|
|
80
|
+
anchors.centerIn: parent
|
|
81
|
+
text: root.text.charAt(0).toUpperCase()
|
|
82
|
+
color: root.textColor
|
|
83
|
+
font.pixelSize: Math.max(12, Math.round(root._diameter * 0.5))
|
|
84
|
+
font.weight: Font.Bold
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 状态覆盖层:右下角,大小约为头像直径 ÷4,带 1px 描边贴合头像底
|
|
89
|
+
ShadcnStatusDot {
|
|
90
|
+
id: statusDot
|
|
91
|
+
visible: root.status !== ShadcnStatusDot.Status.None
|
|
92
|
+
anchors.right: parent.right
|
|
93
|
+
anchors.bottom: parent.bottom
|
|
94
|
+
anchors.rightMargin: Math.max(0, -1)
|
|
95
|
+
anchors.bottomMargin: Math.max(0, -1)
|
|
96
|
+
status: root.status
|
|
97
|
+
size: Math.max(6, Math.round(root._diameter / 4))
|
|
98
|
+
border: true
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import QtShadcn
|
|
3
|
+
|
|
4
|
+
// shadcn/ui 风格徽标
|
|
5
|
+
// 6 variant(v4):default / secondary / destructive(透明底红字)/ outline / ghost / link
|
|
6
|
+
// 高度 20px(h-5)、胶囊圆角、文字 12px Medium
|
|
7
|
+
//
|
|
8
|
+
// 用法:
|
|
9
|
+
// ShadcnBadge { text: "New"; variant: ShadcnBadge.Variant.Default }
|
|
10
|
+
Item {
|
|
11
|
+
id: root
|
|
12
|
+
|
|
13
|
+
enum Variant { Default, Secondary, Destructive, Outline, Ghost, Link }
|
|
14
|
+
|
|
15
|
+
property int variant: ShadcnBadge.Variant.Default
|
|
16
|
+
property string text: ""
|
|
17
|
+
|
|
18
|
+
QtShadcnTheme { id: theme }
|
|
19
|
+
VariantTokens { id: vt }
|
|
20
|
+
|
|
21
|
+
readonly property var _v: vt.badge[root.variant] ?? vt.badge[0]
|
|
22
|
+
// 中转 color 类型:theme.tokens[..] 返回 QVariant(QString),直接 .r/.g/.b 取不到
|
|
23
|
+
// (undefined → Qt.rgba 变黑,曾致 Secondary badge 黑底深字看不见)
|
|
24
|
+
readonly property color _bgColor: _v.bg === "" ? "transparent" : theme.tokens[_v.bg]
|
|
25
|
+
readonly property color _bg: _v.bg === ""
|
|
26
|
+
? "transparent"
|
|
27
|
+
: Qt.rgba(_bgColor.r, _bgColor.g, _bgColor.b,
|
|
28
|
+
_v.bgAlpha !== undefined ? _v.bgAlpha : 1.0)
|
|
29
|
+
readonly property color _fg: theme.tokens[_v.fg]
|
|
30
|
+
readonly property color _border: theme.tokens[_v.borderToken !== undefined ? _v.borderToken : "border"]
|
|
31
|
+
|
|
32
|
+
implicitWidth: contentText.implicitWidth + 16 // px-2(8) * 2
|
|
33
|
+
implicitHeight: 20
|
|
34
|
+
|
|
35
|
+
Rectangle {
|
|
36
|
+
anchors.fill: parent
|
|
37
|
+
radius: 999 // 胶囊
|
|
38
|
+
color: root._bg
|
|
39
|
+
border.width: root._v.border ? 1 : 0
|
|
40
|
+
border.color: root._border
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
Text {
|
|
44
|
+
id: contentText
|
|
45
|
+
|
|
46
|
+
anchors.centerIn: parent
|
|
47
|
+
text: root.text
|
|
48
|
+
color: root._fg
|
|
49
|
+
font.pixelSize: 12
|
|
50
|
+
font.weight: Font.Medium
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import QtQuick.Controls.Basic
|
|
3
|
+
import QtQuick.Layouts
|
|
4
|
+
import QtShadcn
|
|
5
|
+
|
|
6
|
+
// shadcn/ui 风格按钮
|
|
7
|
+
// 基于 QQC Button(Basic style):键盘导航 / Focus / 无障碍继承,只替换视觉与 API
|
|
8
|
+
// 注意:自定义 contentItem/background 的文件使用 style-specific import
|
|
9
|
+
// (QtQuick.Controls.Basic),锁定 Basic 风格是 token 自绘的前提
|
|
10
|
+
//
|
|
11
|
+
// 用法:
|
|
12
|
+
// ShadcnButton {
|
|
13
|
+
// text: "Deploy"
|
|
14
|
+
// variant: ShadcnButton.Variant.Primary // Primary/Secondary/Outline/Ghost/Destructive
|
|
15
|
+
// size: ShadcnButton.Size.Medium // Small/Medium/Large/Icon
|
|
16
|
+
// loading: false
|
|
17
|
+
// onClicked: ...
|
|
18
|
+
// }
|
|
19
|
+
Button {
|
|
20
|
+
id: root
|
|
21
|
+
|
|
22
|
+
// 选中时 z 提升:组内 spacing:-1 相邻按钮重叠 1px(后声明者盖前者右缘),
|
|
23
|
+
// 选中按钮盖住相邻按钮左缘,保证自身右缘描边完整(否则右缘 1px 被盖,
|
|
24
|
+
// 曾表现为"选中项右侧边框不存在")
|
|
25
|
+
z: root._checked ? 1 : 0
|
|
26
|
+
|
|
27
|
+
enum Variant { Primary, Secondary, Outline, Ghost, Destructive, Link }
|
|
28
|
+
enum Size { ExtraSmall, Small, Medium, Large, Icon }
|
|
29
|
+
|
|
30
|
+
// ── 公开 API ──
|
|
31
|
+
property int variant: ShadcnButton.Variant.Primary
|
|
32
|
+
property int size: ShadcnButton.Size.Medium
|
|
33
|
+
property bool loading: false
|
|
34
|
+
// 图标(lucide 名,见 IconRegistry.names):显示在文本左侧(shadcn 带图标按钮)
|
|
35
|
+
property string iconName: ""
|
|
36
|
+
|
|
37
|
+
// ShadcnButtonGroup / ShadcnToggleGroup 内部使用:'only' | 'first' | 'middle' | 'last'
|
|
38
|
+
property string _groupPosition: "only"
|
|
39
|
+
|
|
40
|
+
QtShadcnTheme { id: theme }
|
|
41
|
+
VariantTokens { id: vt }
|
|
42
|
+
|
|
43
|
+
// ── 尺寸表(对齐 shadcn/ui:xs=32 / sm=36 / default=40 / lg=44 / icon=40)──
|
|
44
|
+
readonly property int _ctrlHeight: size === ShadcnButton.Size.ExtraSmall ? 32
|
|
45
|
+
: size === ShadcnButton.Size.Small ? 36
|
|
46
|
+
: size === ShadcnButton.Size.Large ? 44 : 40 // Medium / Icon
|
|
47
|
+
readonly property int _hPad: size === ShadcnButton.Size.ExtraSmall ? 10
|
|
48
|
+
: size === ShadcnButton.Size.Small ? 12
|
|
49
|
+
: size === ShadcnButton.Size.Large ? 32 : 16 // shadcn px-8=32 / px-4=16
|
|
50
|
+
readonly property int _fontSize: size === ShadcnButton.Size.ExtraSmall ? 12
|
|
51
|
+
: size === ShadcnButton.Size.Small ? 12 : 14 // text-sm
|
|
52
|
+
readonly property bool _isIcon: size === ShadcnButton.Size.Icon
|
|
53
|
+
|
|
54
|
+
implicitWidth: _isIcon ? _ctrlHeight : contentItem.implicitWidth + _hPad * 2
|
|
55
|
+
implicitHeight: _ctrlHeight
|
|
56
|
+
|
|
57
|
+
// ── 配色:variant → token 查表(映射集中在 VariantTokens,新增 variant
|
|
58
|
+
// 只需枚举 + 映射表各加一项;token 名经 theme.tokens[] 查询保持绑定)──
|
|
59
|
+
readonly property bool _checked: checkable && checked
|
|
60
|
+
readonly property var _variantMap: vt.button[root.variant] ?? vt.button[0]
|
|
61
|
+
// 背景 token 不随 checked 切换(选中态视觉由 background 内的覆盖层负责——
|
|
62
|
+
// 教训:bg 在 accent↔transparent 间 color 动画会 RGBA 插值出中间灰,点击时闪烁)
|
|
63
|
+
readonly property string _bgToken: _variantMap.bg
|
|
64
|
+
// 前景色随选中切换(文字色,无动画,瞬间切换不闪)
|
|
65
|
+
readonly property string _fgToken: _checked ? vt.checkedOverride.fg : _variantMap.fg
|
|
66
|
+
readonly property color _baseBg: _bgToken === "" ? "transparent" : theme.tokens[_bgToken]
|
|
67
|
+
// hover 文字色:outline/ghost hover 时用 accentForeground(shadcn: hover:text-accent-foreground)
|
|
68
|
+
readonly property color _baseFg: (root.hovered && (variant === ShadcnButton.Variant.Outline
|
|
69
|
+
|| variant === ShadcnButton.Variant.Ghost))
|
|
70
|
+
? theme.accentForeground : theme.tokens[_fgToken]
|
|
71
|
+
readonly property bool _showBorder: _variantMap.border
|
|
72
|
+
// 边框宽(outline 等 border variant 才有;组内 spacing:-1 合并)
|
|
73
|
+
readonly property int _borderW: root._showBorder ? 1 : 0
|
|
74
|
+
|
|
75
|
+
// hover/pressed 混合底色:选中时用覆盖层色(accent),否则用 variant 底色。
|
|
76
|
+
// 教训:_bgToken 不随 checked 后,选中按钮的实际背景是 accent 覆盖层,
|
|
77
|
+
// 若用 primary 等深色 variant 底色混合会叠出深灰("选中项 hover 变黑")
|
|
78
|
+
readonly property color _hoverBase: _checked
|
|
79
|
+
? theme.tokens[vt.checkedOverride.bg] : _baseBg
|
|
80
|
+
|
|
81
|
+
// hover 背景:outline/ghost → accent 色(shadcn: hover:bg-accent);
|
|
82
|
+
// 其余 → 按钮色与页面背景按 hoverMix 混合(等效 shadcn hover:bg-*/90|80:
|
|
83
|
+
// 深色按钮变浅、浅色变暗,随主题背景色自动适配)。
|
|
84
|
+
// 教训:固定黑 8% 在 light 模式 primary(#18181b 近黑)上黑上加黑,肉眼不可见。
|
|
85
|
+
readonly property color _hoverOverlay: {
|
|
86
|
+
if (variant === ShadcnButton.Variant.Outline || variant === ShadcnButton.Variant.Ghost)
|
|
87
|
+
return theme.accent
|
|
88
|
+
return _mix(_hoverBase, theme.background, _variantMap.hoverMix)
|
|
89
|
+
}
|
|
90
|
+
// pressed:混合比例加重(按压反馈更深);accent 上叠黑 15% 压暗
|
|
91
|
+
readonly property color _pressedOverlay: {
|
|
92
|
+
if (variant === ShadcnButton.Variant.Outline || variant === ShadcnButton.Variant.Ghost)
|
|
93
|
+
return Qt.rgba(0, 0, 0, 0.15)
|
|
94
|
+
return _mix(_hoverBase, theme.background, Math.min(0.25, _variantMap.hoverMix * 2))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// a 向 b 按 t 混合(t=0.1 等效 shadcn /90:0.9×a + 0.1×b)
|
|
98
|
+
function _mix(a, b, t) {
|
|
99
|
+
return Qt.rgba(a.r * (1 - t) + b.r * t, a.g * (1 - t) + b.g * t, a.b * (1 - t) + b.b * t, 1)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 圆角:shadcn button 用 rounded-md = 6px(theme.radius 8 用于卡片/容器)
|
|
103
|
+
readonly property int _r: 6
|
|
104
|
+
readonly property int _tl: _groupPosition === "only" || _groupPosition === "first" ? _r : 0
|
|
105
|
+
readonly property int _tr: _groupPosition === "only" || _groupPosition === "last" ? _r : 0
|
|
106
|
+
readonly property int _bl: _groupPosition === "only" || _groupPosition === "first" ? _r : 0
|
|
107
|
+
readonly property int _br: _groupPosition === "only" || _groupPosition === "last" ? _r : 0
|
|
108
|
+
|
|
109
|
+
// ── 背景 ────────────────────────────────────────
|
|
110
|
+
background: Rectangle {
|
|
111
|
+
topLeftRadius: root._tl
|
|
112
|
+
topRightRadius: root._tr
|
|
113
|
+
bottomLeftRadius: root._bl
|
|
114
|
+
bottomRightRadius: root._br
|
|
115
|
+
color: root._baseBg
|
|
116
|
+
border.width: root._borderW
|
|
117
|
+
// 选中态描边用 accentForeground(light 下深色描边圈住选中项,弥补 accent 背景对比度弱;
|
|
118
|
+
// dark 下自动变白色描边。保持 border token 语义不变)
|
|
119
|
+
border.color: root.activeFocus ? theme.ring
|
|
120
|
+
: root._checked ? theme.accentForeground : theme.border
|
|
121
|
+
|
|
122
|
+
Behavior on color { ColorAnimation { duration: 120 } }
|
|
123
|
+
|
|
124
|
+
// 组内分隔线:middle/last 左侧 1px(仅无 border 的 variant,如 Primary)
|
|
125
|
+
// 用按钮自身前景色(primary 组 = 白色 primaryForeground,深底上可见;
|
|
126
|
+
// 教训:theme.foreground 在 light 模式是深色,叠深色按钮上不可见)
|
|
127
|
+
Rectangle {
|
|
128
|
+
visible: (root._groupPosition === "middle" || root._groupPosition === "last")
|
|
129
|
+
&& !root._showBorder
|
|
130
|
+
width: 1
|
|
131
|
+
anchors.top: parent.top
|
|
132
|
+
anchors.bottom: parent.bottom
|
|
133
|
+
anchors.left: parent.left
|
|
134
|
+
color: Qt.rgba(root._baseFg.r, root._baseFg.g, root._baseFg.b, 0.15)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 选中态覆盖层(checkable 用):accent 背景 + opacity 动画。
|
|
138
|
+
// 教训:background.color 在 accent↔transparent 间 ColorAnimation 会 RGBA 插值
|
|
139
|
+
// 出中间灰(半透明灰叠白底,点击切换时闪烁);opacity 动画 RGB 恒定不闪灰。
|
|
140
|
+
// 内缩边框宽:outline 选中时保留 1px 边框
|
|
141
|
+
Rectangle {
|
|
142
|
+
anchors.fill: parent
|
|
143
|
+
anchors.margins: root._borderW
|
|
144
|
+
topLeftRadius: Math.max(0, parent.topLeftRadius - root._borderW)
|
|
145
|
+
topRightRadius: Math.max(0, parent.topRightRadius - root._borderW)
|
|
146
|
+
bottomLeftRadius: Math.max(0, parent.bottomLeftRadius - root._borderW)
|
|
147
|
+
bottomRightRadius: Math.max(0, parent.bottomRightRadius - root._borderW)
|
|
148
|
+
visible: root.checkable
|
|
149
|
+
color: theme.tokens[vt.checkedOverride.bg]
|
|
150
|
+
opacity: root._checked ? 1 : 0
|
|
151
|
+
Behavior on opacity { NumberAnimation { duration: 120 } }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// hover / pressed:背景色叠加(outline/ghost → accent;其余按亮度黑/白;link 无)
|
|
155
|
+
// anchors.margins = 边框宽:不盖住 outline 的 1px 边框(否则 hover 时边框视觉消失)
|
|
156
|
+
Rectangle {
|
|
157
|
+
anchors.fill: parent
|
|
158
|
+
anchors.margins: root._borderW
|
|
159
|
+
topLeftRadius: Math.max(0, parent.topLeftRadius - root._borderW)
|
|
160
|
+
topRightRadius: Math.max(0, parent.topRightRadius - root._borderW)
|
|
161
|
+
bottomLeftRadius: Math.max(0, parent.bottomLeftRadius - root._borderW)
|
|
162
|
+
bottomRightRadius: Math.max(0, parent.bottomRightRadius - root._borderW)
|
|
163
|
+
visible: root.hovered && root.variant !== ShadcnButton.Variant.Link
|
|
164
|
+
color: root._hoverOverlay
|
|
165
|
+
}
|
|
166
|
+
Rectangle {
|
|
167
|
+
anchors.fill: parent
|
|
168
|
+
anchors.margins: root._borderW
|
|
169
|
+
topLeftRadius: Math.max(0, parent.topLeftRadius - root._borderW)
|
|
170
|
+
topRightRadius: Math.max(0, parent.topRightRadius - root._borderW)
|
|
171
|
+
bottomLeftRadius: Math.max(0, parent.bottomLeftRadius - root._borderW)
|
|
172
|
+
bottomRightRadius: Math.max(0, parent.bottomRightRadius - root._borderW)
|
|
173
|
+
visible: root.pressed && root.variant !== ShadcnButton.Variant.Link
|
|
174
|
+
color: root._pressedOverlay
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 键盘焦点环(activeFocus,Tab 可达)
|
|
178
|
+
Rectangle {
|
|
179
|
+
anchors.fill: parent
|
|
180
|
+
anchors.margins: -3
|
|
181
|
+
radius: root._r + 3
|
|
182
|
+
visible: root.activeFocus
|
|
183
|
+
color: "transparent"
|
|
184
|
+
border.width: 2
|
|
185
|
+
border.color: theme.ring
|
|
186
|
+
opacity: 0.7
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── 内容 ────────────────────────────────────────
|
|
191
|
+
// Item 包装:contentItem 会被 QQC 拉伸到内容区全宽,RowLayout 必须居中于
|
|
192
|
+
// Item(anchors.centerIn)才能保证文字水平+垂直都居中(按钮被拉伸时也成立);
|
|
193
|
+
// RowLayout 内部子项默认垂直居中
|
|
194
|
+
contentItem: Item {
|
|
195
|
+
implicitWidth: row.implicitWidth
|
|
196
|
+
implicitHeight: row.implicitHeight
|
|
197
|
+
|
|
198
|
+
RowLayout {
|
|
199
|
+
id: row
|
|
200
|
+
anchors.centerIn: parent
|
|
201
|
+
spacing: theme.spacingXs
|
|
202
|
+
|
|
203
|
+
ShadcnSpinner {
|
|
204
|
+
Layout.preferredWidth: 14
|
|
205
|
+
Layout.preferredHeight: 14
|
|
206
|
+
visible: root.loading
|
|
207
|
+
color: root._baseFg
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 图标(iconName,shadcn 带图标按钮:svg size-4=16px,随文字色)
|
|
211
|
+
ShadcnIcon {
|
|
212
|
+
Layout.preferredWidth: 16
|
|
213
|
+
Layout.preferredHeight: 16
|
|
214
|
+
visible: root.iconName.length > 0
|
|
215
|
+
name: root.iconName
|
|
216
|
+
size: 16
|
|
217
|
+
color: root._baseFg
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
Text {
|
|
221
|
+
visible: root.text.length > 0
|
|
222
|
+
text: root.text
|
|
223
|
+
// 组属性限制:font 整体赋值后不能局部覆盖,故逐项复制
|
|
224
|
+
font.family: root.font.family
|
|
225
|
+
font.pixelSize: root._fontSize
|
|
226
|
+
font.weight: Font.Medium // shadcn: font-medium
|
|
227
|
+
// link variant:hover/聚焦时显示下划线(shadcn: hover:underline)
|
|
228
|
+
font.underline: root.variant === ShadcnButton.Variant.Link
|
|
229
|
+
&& (root.hovered || root.activeFocus)
|
|
230
|
+
color: root._baseFg
|
|
231
|
+
opacity: root.loading ? 0.6 : 1
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// loading:禁用交互;disabled/loading 半透明(shadcn: disabled:opacity-50)
|
|
237
|
+
enabled: !root.loading
|
|
238
|
+
opacity: root.loading || !root.enabled ? 0.5 : 1
|
|
239
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import QtShadcn
|
|
3
|
+
|
|
4
|
+
// 按钮组:相邻按钮边框合并(spacing: -1),圆角只保留两端
|
|
5
|
+
// 子项直接放 ShadcnButton / ShadcnToggle 即可,圆角位置自动分配
|
|
6
|
+
Row {
|
|
7
|
+
id: root
|
|
8
|
+
|
|
9
|
+
spacing: -1
|
|
10
|
+
|
|
11
|
+
onChildrenChanged: updateGroup()
|
|
12
|
+
Component.onCompleted: updateGroup()
|
|
13
|
+
|
|
14
|
+
function updateGroup() {
|
|
15
|
+
var btns = []
|
|
16
|
+
for (var i = 0; i < root.children.length; ++i) {
|
|
17
|
+
var c = root.children[i]
|
|
18
|
+
if (c.hasOwnProperty("_groupPosition"))
|
|
19
|
+
btns.push(c)
|
|
20
|
+
}
|
|
21
|
+
var n = btns.length
|
|
22
|
+
for (var j = 0; j < n; ++j) {
|
|
23
|
+
btns[j]._groupPosition = n === 1 ? "only"
|
|
24
|
+
: j === 0 ? "first"
|
|
25
|
+
: j === n - 1 ? "last" : "middle"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|