提交 9b11ac09 编写于 作者: 之一Yo's avatar 之一Yo

添加设置界面示例

上级 ca1c5299
......@@ -15,3 +15,14 @@ log/
# 忽略测试文件
test.py
# 忽略配置文件
config.json
# 忽略下载文件夹
download/
# 忽略构建文件夹
build/
dist/
PyQt_Fluent_Widgets.egg-info/
\ No newline at end of file
# PyQt-Fluent-Widgets
A library of fluent design widgets.
<p align="center">
<img width="18%" align="center" src="docs/source/_static/logo.png" alt="logo">
</p>
<h1 align="center">
PyQt-Fluent-Widgets
</h1>
<p align="center">
A fluent design widgets library based on PyQt5
</p>
<p align="center">
<a style="text-decoration:none">
<img src="https://img.shields.io/badge/Python-3.6+-blue.svg?color=00B16A" alt="Python 3.6+"/>
</a>
<a style="text-decoration:none">
<img src="https://img.shields.io/badge/PyQt-5.15.6-blue?color=00B16A" alt="PyQt 5.15.6"/>
</a>
<a style="text-decoration:none">
<img src="https://img.shields.io/badge/Platform-Win32%20|%20Linux%20|%20macOS-blue?color=00B16A" alt="Platform Win32 | Linux | macOS"/>
</a>
</p>
![Interface](docs/source/_static/Interface.png)
## Install
To install use pip:
......@@ -12,168 +37,13 @@ git clone https://github.com/zhiyiYo/PyQt-Fluent-Widgets.git
python setup.py install
```
## Detailed list
<table>
<tbody>
<tr>
<td colspan="2" align="center"></td>
</tr>
<tr>
<td>
Switch Button
</td>
<td>
<code>SwitchButton</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/switch_button.gif" />
</td>
</tr>
<tr>
<td>
Hollow Handle Slider
</td>
<td>
<code>HollowHandleStyle</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/hollow_handle_slider.gif" />
</td>
</tr>
<tr>
<td>
Line Edit
</td>
<td>
<code>LineEdit</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/line_edit.gif" />
</td>
</tr>
<tr>
<td>
Dialog
</td>
<td>
<code>Dialog</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/dialog.gif" />
</td>
</tr>
<tr>
<td>
Message Dialog
</td>
<td>
<code>MessageDialog</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/dialog_with_mask.gif" />
</td>
</tr>
<tr>
<td>
Folder List Dialog
</td>
<td>
<code>FolderListDialog</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/folder_list_dialog.gif" />
</td>
</tr>
<tr>
<td>
State Tooltip
</td>
<td>
<code>StateTooltip</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/state_tooltip.gif" />
</td>
</tr>
<tr>
<td>
Tooltip
</td>
<td>
<code>Tooltip</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/ToolTip.gif" />
</td>
</tr>
<tr>
<td>
Flow Layout
</td>
<td>
<code>FlowLayout</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/flow_layout.gif" />
</td>
</tr>
<tr>
<td>
Acrylic Label
</td>
<td>
<code>AcrylicLabel</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/acrylic_label.png" style="width:70%"/>
</td>
</tr>
<tr>
<td>
Smooth Scroll Area
</td>
<td>
<code>ScrollArea</code>
</td>
</tr>
<tr>
<td>
Opacity Animation Stacked Widget
</td>
<td>
<code>OpacityAniStackedWidget</code>
</td>
</tr>
<tr>
<td>
Pop Up Animation Stacked Widget
</td>
<td>
<code>PopUpAniStackedWidget</code>
</td>
</tr>
</tbody>
</table>
## Run example
After installing PyQt-Fluent-Widgets package using pip, you can run any demo in the examples directory, for example:
```python
cd examples/settings
python demo.py
```
## See Also
Here are some projects that use PyQt-Fluent-Widgets:
......
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'PyQt-Fluent-Widgets'
copyright = '2023, zhiyiYo'
author = 'zhiyiYo'
release = 'v0.1.0'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
source_parsers = {
'.md': 'recommonmark.parser.CommonMarkParser',
}
source_suffix = ['.rst', '.md']
extensions = ['recommonmark', 'sphinx_markdown_tables']
templates_path = ['_templates']
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
.. PyQt-Fluent-Widgets documentation master file, created by
sphinx-quickstart on Tue Jan 17 15:31:54 2023.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
.. raw:: html
<p align="center">
<img width="18%" align="center" src="./_static/logo.png" alt="logo">
</p>
<h1 align="center">
PyQt-Fluent-Widgets
</h1>
<hr/>
<p align="center">
A fluent design widgets library based on PyQt5
</p>
<p align="center">
<a style="text-decoration:none">
<img src="https://img.shields.io/badge/Python-3.6+-blue.svg?color=00B16A" alt="Python 3.6+"/>
</a>
<a style="text-decoration:none">
<img src="https://img.shields.io/badge/PyQt-5.15.6-blue?color=00B16A" alt="PyQt 5.15.6"/>
</a>
<a style="text-decoration:none">
<img src="https://img.shields.io/badge/Platform-Win32%20|%20Linux%20|%20macOS-blue?color=00B16A" alt="Platform Win32 | Linux | macOS"/>
</a>
</p>
<p align="center">
<img src="./_static/Interface.png" alt="Interface">
</p>
Welcome to PyQt-Fluent-Widgets's document!
===============================================
This document will show you all the features of PyQt-Fluent-Widgets and the best practice of it.
.. toctree::
:maxdepth: 2
:caption: Contents
quick-start
## Quick start
### Install
To install use pip:
```shell
pip install PyQt-Fluent-Widgets
```
Or clone the repo:
```shell
git clone https://github.com/zhiyiYo/PyQt-Fluent-Widgets.git
python setup.py install
```
### Run example
After installing PyQt-Fluent-Widgets package using pip, you can run any demo in the examples directory, for example:
```python
cd examples/settings
python demo.py
```
\ No newline at end of file
# coding:utf-8
from enum import Enum
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QGuiApplication, QFont
from qfluentwidgets import (config, QConfig, ConfigItem, OptionsConfigItem, BoolValidator,
ColorConfigItem, OptionsValidator, RangeConfigItem, RangeValidator,
FolderListValidator, EnumSerializer, FolderValidator)
class SongQuality(Enum):
""" Online song quality enumeration class """
STANDARD = "Standard quality"
HIGH = "High quality"
SUPER = "Super quality"
LOSSLESS = "Lossless quality"
@staticmethod
def values():
return [q.value for q in SongQuality]
class MvQuality(Enum):
""" MV quality enumeration class """
FULL_HD = "Full HD"
HD = "HD"
SD = "SD"
LD = "LD"
@staticmethod
def values():
return [q.value for q in MvQuality]
class Config(QConfig):
""" Config of application """
# folders
musicFolders = ConfigItem(
"Folders", "LocalMusic", [], FolderListValidator())
downloadFolder = ConfigItem(
"Folders", "Download", "download", FolderValidator())
# online
onlineSongQuality = OptionsConfigItem(
"Online", "SongQuality", SongQuality.STANDARD, OptionsValidator(SongQuality), EnumSerializer(SongQuality))
onlinePageSize = RangeConfigItem(
"Online", "PageSize", 30, RangeValidator(0, 50))
onlineMvQuality = OptionsConfigItem(
"Online", "MvQuality", MvQuality.FULL_HD, OptionsValidator(MvQuality), EnumSerializer(MvQuality))
# main window
enableAcrylicBackground = ConfigItem(
"MainWindow", "EnableAcrylicBackground", False, BoolValidator())
minimizeToTray = ConfigItem(
"MainWindow", "MinimizeToTray", True, BoolValidator())
playBarColor = ColorConfigItem("MainWindow", "PlayBarColor", "#225C7F")
themeMode = OptionsConfigItem(
"MainWindow", "ThemeMode", "Light", OptionsValidator(["Light", "Dark", "Auto"]), restart=True)
recentPlaysNumber = RangeConfigItem(
"MainWindow", "RecentPlayNumbers", 300, RangeValidator(10, 300))
dpiScale = OptionsConfigItem(
"MainWindow", "DpiScale", "Auto", OptionsValidator([1, 1.25, 1.5, 1.75, 2, "Auto"]), restart=True)
# desktop lyric
deskLyricHighlightColor = ColorConfigItem(
"DesktopLyric", "HighlightColor", "#0099BC")
deskLyricFontSize = RangeConfigItem(
"DesktopLyric", "FontSize", 50, RangeValidator(15, 50))
deskLyricStrokeSize = RangeConfigItem(
"DesktopLyric", "StrokeSize", 5, RangeValidator(0, 20))
deskLyricStrokeColor = ColorConfigItem(
"DesktopLyric", "StrokeColor", Qt.black)
deskLyricFontFamily = ConfigItem(
"DesktopLyric", "FontFamily", "Microsoft YaHei")
deskLyricAlignment = OptionsConfigItem(
"DesktopLyric", "Alignment", "Center", OptionsValidator(["Center", "Left", "Right"]))
# software update
checkUpdateAtStartUp = ConfigItem(
"Update", "CheckUpdateAtStartUp", True, BoolValidator())
@property
def desktopLyricFont(self):
""" get the desktop lyric font """
font = QFont(self.deskLyricFontFamily.value)
font.setPixelSize(self.deskLyricFontSize.value)
return font
@desktopLyricFont.setter
def desktopLyricFont(self, font: QFont):
dpi = QGuiApplication.primaryScreen().logicalDotsPerInch()
self.deskLyricFontFamily.value = font.family()
self.deskLyricFontSize.value = max(15, int(font.pointSize()*dpi/72))
self.save()
YEAR = 2023
AUTHOR = "zhiyiYo"
VERSION = "v0.0.1"
HELP_URL = "https://pyqt-fluent-widgets.readthedocs.io"
FEEDBACK_URL = "https://github.com/zhiyiYo/PyQt-Fluent-Widgets/issues"
RELEASE_URL = "https://github.com/zhiyiYo/PyQt-Fluent-Widgets/releases/latest"
cfg = Config()
config.load('config/config.json', cfg)
\ No newline at end of file
# coding:utf-8
import os
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QColor
from PyQt5.QtWidgets import QApplication, QLabel, QHBoxLayout
from qfluentwidgets import dpi_manager
from qframelesswindow import FramelessWindow, TitleBar
from qframelesswindow.titlebar import TitleBarButton
from setting_interface import SettingInterface, cfg
class CustomTitleBar(TitleBar):
""" Custom title bar """
def __init__(self, parent):
super().__init__(parent)
# add window icon
self.iconLabel = QLabel(self)
self.iconLabel.setFixedSize(22, 22)
self.hBoxLayout.insertSpacing(0, 10)
self.hBoxLayout.insertWidget(1, self.iconLabel, 0, Qt.AlignLeft)
self.window().windowIconChanged.connect(self.setIcon)
# add title label
self.titleLabel = QLabel(self)
self.hBoxLayout.insertWidget(2, self.titleLabel, 0, Qt.AlignLeft)
self.titleLabel.setStyleSheet(f"""
QLabel{{
background: transparent;
font: 15px 'Segoe UI';
padding: 0 3px;
color: {'white' if cfg.theme == 'dark' else 'black'}
}}
""")
self.window().windowTitleChanged.connect(self.setTitle)
# customize title bar button
if cfg.theme == 'dark':
for button in (self.findChildren(TitleBarButton)):
button.setNormalColor(Qt.white)
button.setHoverColor(Qt.white)
button.setPressedColor(Qt.white)
if button is not self.closeBtn:
button.setHoverBackgroundColor(QColor(255, 255, 255, 26))
button.setPressedBackgroundColor(QColor(255, 255, 255, 51))
def setTitle(self, title):
self.titleLabel.setText(title)
self.titleLabel.adjustSize()
def setIcon(self, icon):
self.iconLabel.setPixmap(icon.pixmap(22, 22))
class Window(FramelessWindow):
def __init__(self, parent=None):
super().__init__(parent=parent)
# change the default title bar if you like
self.setTitleBar(CustomTitleBar(self))
self.hBoxLayout = QHBoxLayout(self)
self.settingInterface = SettingInterface(self)
self.hBoxLayout.setContentsMargins(0, 0, 0, 0)
self.hBoxLayout.addWidget(self.settingInterface)
self.setWindowIcon(QIcon("resource/logo.png"))
self.setWindowTitle("PyQt-Fluent-Widgets")
self.resize(1350, 980)
desktop = QApplication.desktop().availableGeometry()
w, h = desktop.width(), desktop.height()
self.move(w//2 - self.width()//2, h//2 - self.height()//2)
self.titleBar.raise_()
if __name__ == '__main__':
# enable high dpi scale
os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "0"
if cfg.get(cfg.dpiScale) == "Auto":
os.environ["QT_SCALE_FACTOR"] = str(max(1, dpi_manager.scale-0.25))
else:
os.environ["QT_SCALE_FACTOR"] = str(cfg.get(cfg.dpiScale))
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec_()
# coding:utf-8
from config import cfg, HELP_URL, FEEDBACK_URL, AUTHOR, VERSION, YEAR
from qfluentwidgets import (SettingCardGroup, SwitchSettingCard, FolderListSettingCard,
OptionsSettingCard, RangeSettingCard, PushSettingCard,
ColorSettingCard, HyperlinkCard, PrimaryPushSettingCard, ScrollArea,
ExpandLayout, setStyleSheet, ToastToolTip)
from qfluentwidgets import SettingIconFactory as SIF
from PyQt5.QtCore import Qt, pyqtSignal, QUrl
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import QWidget, QLabel, QFontDialog, QFileDialog
class SettingInterface(ScrollArea):
""" Setting interface """
checkUpdateSig = pyqtSignal()
musicFoldersChanged = pyqtSignal(list)
acrylicEnableChanged = pyqtSignal(bool)
downloadFolderChanged = pyqtSignal(str)
minimizeToTrayChanged = pyqtSignal(bool)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.scrollWidget = QWidget()
self.expandLayout = ExpandLayout(self.scrollWidget)
# setting label
self.settingLabel = QLabel("Settings", self)
# music folders
self.musicInThisPCGroup = SettingCardGroup(
"Music on this PC", self.scrollWidget)
self.musicFolderCard = FolderListSettingCard(
cfg.musicFolders,
"Local music library",
parent=self.musicInThisPCGroup
)
self.downloadFolderCard = PushSettingCard(
'Choose folder',
SIF.create(SIF.DOWNLOAD),
"Download Directory",
cfg.get(cfg.downloadFolder),
self.musicInThisPCGroup
)
# personalization
self.personalGroup = SettingCardGroup('Personalization', self.scrollWidget)
self.enableAcrylicCard = SwitchSettingCard(
SIF.create(SIF.TRANSPARENT),
"Use Acrylic effect",
"Acrylic effect has better visual experience, but it may cause the window to become stuck",
configItem=cfg.enableAcrylicBackground,
parent=self.personalGroup
)
self.themeCard = OptionsSettingCard(
cfg.themeMode,
SIF.create(SIF.BRUSH),
'Application theme',
"Change the appearance of your application",
texts=[
'Light', 'Dark',
'Use system setting'
],
parent=self.personalGroup
)
self.zoomCard = OptionsSettingCard(
cfg.dpiScale,
SIF.create(SIF.ZOOM),
"Interface zoom",
"Change the size of widgets and fonts",
texts=[
"100%", "125%", "150%", "175%", "200%",
"Use system setting"
],
parent=self.personalGroup
)
# online music
self.onlineMusicGroup = SettingCardGroup('Online Music', self.scrollWidget)
self.onlinePageSizeCard = RangeSettingCard(
cfg.onlinePageSize,
SIF.create(SIF.SEARCH),
"Number of online music displayed on each page",
parent=self.onlineMusicGroup
)
self.onlineMusicQualityCard = OptionsSettingCard(
cfg.onlineSongQuality,
SIF.create(SIF.MUSIC),
'Online music quality',
texts=[
'Standard quality', 'High quality',
'Super quality', 'Lossless quality'
],
parent=self.onlineMusicGroup
)
self.onlineMvQualityCard = OptionsSettingCard(
cfg.onlineMvQuality,
SIF.create(SIF.VIDEO),
'Online MV quality',
texts=[
'Full HD', 'HD',
'SD', 'LD'
],
parent=self.onlineMusicGroup
)
# desktop lyric
self.deskLyricGroup = SettingCardGroup('Desktop Lyric', self.scrollWidget)
self.deskLyricFontCard = PushSettingCard(
'Choose font',
SIF.create(SIF.FONT),
'Font',
parent=self.deskLyricGroup
)
self.deskLyricHighlightColorCard = ColorSettingCard(
cfg.deskLyricHighlightColor,
SIF.create(SIF.PALETTE),
'Foreground color',
parent=self.deskLyricGroup
)
self.deskLyricStrokeColorCard = ColorSettingCard(
cfg.deskLyricStrokeColor,
SIF.create(SIF.PENCIL_INK),
'Stroke color',
parent=self.deskLyricGroup
)
self.deskLyricStrokeSizeCard = RangeSettingCard(
cfg.deskLyricStrokeSize,
SIF.create(SIF.FLUORESCENT_PEN),
'Stroke size',
parent=self.deskLyricGroup
)
self.deskLyricAlignmentCard = OptionsSettingCard(
cfg.deskLyricAlignment,
SIF.create(SIF.ALIGNMENT),
'Alignment',
texts=[
'Center aligned', 'Left aligned',
'Right aligned'
],
parent=self.deskLyricGroup
)
# main panel
self.mainPanelGroup = SettingCardGroup('Main Panel', self.scrollWidget)
self.minimizeToTrayCard = SwitchSettingCard(
SIF.create(SIF.MINIMIZE),
'Minimize to tray after closing',
'PyQt Fluent Widgets will continue to run in the background',
configItem=cfg.minimizeToTray,
parent=self.mainPanelGroup
)
# update software
self.updateSoftwareGroup = SettingCardGroup("Software update", self.scrollWidget)
self.updateOnStartUpCard = SwitchSettingCard(
SIF.create(SIF.UPDATE),
'Check for updates when the application starts',
'The new version will be more stable and have more features',
configItem=cfg.checkUpdateAtStartUp,
parent=self.updateSoftwareGroup
)
# application
self.aboutGroup = SettingCardGroup('About', self.scrollWidget)
self.helpCard = HyperlinkCard(
HELP_URL,
'Open help page',
SIF.create(SIF.HELP),
'Help',
'Discover new features and learn useful tips about PyQt-Fluent-Widgets',
self.aboutGroup
)
self.feedbackCard = PrimaryPushSettingCard(
'Provide feedback',
SIF.create(SIF.FEEDBACK),
'Provide feedback',
'Help us improve PyQt Fluent Widgets by providing feedback',
self.aboutGroup
)
self.aboutCard = PrimaryPushSettingCard(
'Check update',
SIF.create(SIF.INFO),
'About',
'© ' + 'Copyright' + f" {YEAR}, {AUTHOR}. " +
'Version' + f" {VERSION[1:]}",
self.aboutGroup
)
self.__initWidget()
def __initWidget(self):
self.resize(1000, 800)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setViewportMargins(0, 120, 0, 0)
self.setWidget(self.scrollWidget)
self.scrollWidget.resize(self.width(), 1940)
# initialize style sheet
self.scrollWidget.setObjectName('scrollWidget')
self.settingLabel.setObjectName('settingLabel')
setStyleSheet(self, 'setting_interface')
# initialize layout
self.__initLayout()
self.__connectSignalToSlot()
def __initLayout(self):
self.settingLabel.move(60, 63)
# add cards to group
self.musicInThisPCGroup.addSettingCard(self.musicFolderCard)
self.musicInThisPCGroup.addSettingCard(self.downloadFolderCard)
self.personalGroup.addSettingCard(self.enableAcrylicCard)
self.personalGroup.addSettingCard(self.themeCard)
self.personalGroup.addSettingCard(self.zoomCard)
self.onlineMusicGroup.addSettingCard(self.onlinePageSizeCard)
self.onlineMusicGroup.addSettingCard(self.onlineMusicQualityCard)
self.onlineMusicGroup.addSettingCard(self.onlineMvQualityCard)
self.deskLyricGroup.addSettingCard(self.deskLyricFontCard)
self.deskLyricGroup.addSettingCard(self.deskLyricHighlightColorCard)
self.deskLyricGroup.addSettingCard(self.deskLyricStrokeColorCard)
self.deskLyricGroup.addSettingCard(self.deskLyricStrokeSizeCard)
self.deskLyricGroup.addSettingCard(self.deskLyricAlignmentCard)
self.updateSoftwareGroup.addSettingCard(self.updateOnStartUpCard)
self.mainPanelGroup.addSettingCard(self.minimizeToTrayCard)
self.aboutGroup.addSettingCard(self.helpCard)
self.aboutGroup.addSettingCard(self.feedbackCard)
self.aboutGroup.addSettingCard(self.aboutCard)
# add setting card group to layout
self.expandLayout.setSpacing(28)
self.expandLayout.setContentsMargins(60, 10, 60, 0)
self.expandLayout.addWidget(self.musicInThisPCGroup)
self.expandLayout.addWidget(self.personalGroup)
self.expandLayout.addWidget(self.onlineMusicGroup)
self.expandLayout.addWidget(self.deskLyricGroup)
self.expandLayout.addWidget(self.mainPanelGroup)
self.expandLayout.addWidget(self.updateSoftwareGroup)
self.expandLayout.addWidget(self.aboutGroup)
def resizeEvent(self, e):
self.scrollWidget.resize(self.width(), self.scrollWidget.height())
super().resizeEvent(e)
def __showRestartTooltip(self):
""" show restart tooltip """
w = ToastToolTip(
self.tr('Configuration updated successfully'),
self.tr('Configuration takes effect after restart'),
'info',
self.window()
)
w.show()
def __onDeskLyricFontCardClicked(self):
""" desktop lyric font button clicked slot """
font, isOk = QFontDialog.getFont(
cfg.desktopLyricFont, self.window(), "Choose Font")
if isOk:
cfg.desktopLyricFont = font
def __onDownloadFolderCardClicked(self):
""" download folder card clicked slot """
folder = QFileDialog.getExistingDirectory(
self, "Choose folder", "./")
if not folder or cfg.get(cfg.downloadFolder) == folder:
return
cfg.set(cfg.downloadFolder, folder)
self.downloadFolderCard.setContent(folder)
def __connectSignalToSlot(self):
""" connect signal to slot """
cfg.appRestartSig.connect(self.__showRestartTooltip)
# music in the pc
self.musicFolderCard.folderChanged.connect(
self.musicFoldersChanged)
self.downloadFolderCard.clicked.connect(
self.__onDownloadFolderCardClicked)
# personalization
self.enableAcrylicCard.checkedChanged.connect(
self.acrylicEnableChanged)
# playing interface
self.deskLyricFontCard.clicked.connect(self.__onDeskLyricFontCardClicked)
# main panel
self.minimizeToTrayCard.checkedChanged.connect(
self.minimizeToTrayChanged)
# about
self.aboutCard.clicked.connect(self.checkUpdateSig)
self.feedbackCard.clicked.connect(
lambda: QDesktopServices.openUrl(QUrl(FEEDBACK_URL)))
......@@ -16,7 +16,7 @@ class Demo(QWidget):
self._toolTip = ToolTip(parent=self)
# self._toolTip.setDarkTheme(True)
self.button1.setToolTip('aiko - キラキラ ✨\n'*8)
self.button1.setToolTip('aiko - キラキラ ✨')
self.button2.setToolTip('aiko - 食べた愛 🥰')
self.button1.setToolTipDuration(1000)
self.button2.setToolTipDuration(5000)
......
......@@ -3,15 +3,15 @@ PyQt-Fluent-Widgets
===================
A library of fluent design widgets.
Documentation is available in the docstrings and
online at https://pyqt-fluent-widgets.readthedocs.io.
:copyright: (c) 2023 by zhiyiYo.
:license: MIT, see LICENSE for more details.
"""
__version__ = "0.0.1"
from .components.dialog_box import *
from .components.layout import *
from .components.settings import *
from .components.widgets import *
from .components import *
from .common import *
from ._rc import resource
\ No newline at end of file
......@@ -118,23 +118,40 @@ QRadioButton::indicator:hover {
}
QRadioButton::indicator:pressed {
background-color: rgba(255, 255, 255, 20);
border: 2px solid #434343;
image: url(:/images/setting_card/RadioButton_black.png);
background-color: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5,
stop:0 rgb(0, 0, 0),
stop:0.5 rgb(0, 0, 0),
stop:0.55 rgb(43, 42, 42),
stop:1 rgb(43, 42, 42));
}
QRadioButton::indicator:checked {
background-color: rgb(41, 247, 255);
border: 2px solid rgb(41, 247, 255);
image: url(:/images/setting_card/RadioButton_black.png);
height: 26px;
width: 26px;
border: none;
border-radius: 13px;
background-color: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5,
stop:0 rgb(0, 0, 0),
stop:0.5 rgb(0, 0, 0),
stop:0.55 rgb(72, 210, 242),
stop:1 rgb(72, 210, 242));
}
QRadioButton::indicator:checked:hover {
image: url(:/images/setting_card/RadioButtonHover_black.png);
background-color: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5,
stop:0 rgb(0, 0, 0),
stop:0.6 rgb(0, 0, 0),
stop:0.65 rgb(72, 210, 242),
stop:1 rgb(72, 210, 242));
}
QRadioButton::indicator:checked:pressed {
image: url(:/images/setting_card/RadioButtonPressed_black.png);
background-color: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5,
stop:0 rgb(0, 0, 0),
stop:0.5 rgb(0, 0, 0),
stop:0.55 rgb(72, 210, 242),
stop:1 rgb(72, 210, 242));
}
QRadioButton:disabled {
......
/*设置菜单样式*/
QMenu {
background-color: rgb(43, 43, 43);
font-size: 18px;
font: 18px 'Segoe UI', 'Microsoft YaHei';
padding: 5px 0px 16px 0px;
padding: 5px 0px 5px 0px;
border: 1px solid rgb(34, 34, 34);
color: white;
/* border-radius: 5px; */
}
QMenu::right-arrow {
width: 16px;
height: 16px;
right: 16px;
border-image: url(:/images/menu/ChevronRight_white.png);
border-image: url(:/qfluentwidgets/images/menu/ChevronRight_white.png);
}
QMenu::separator {
......@@ -36,87 +34,7 @@ QMenu::item:selected {
color: white;
}
/*添加到菜单样式*/
QMenu#addToMenu,
QMenu#addFromMenu {
padding: 5px 0px 8px 0px;
}
QMenu#addToMenu::item,
QMenu#addFromMenu::item,
QMenu#blackAddToMenu::item,
QMenu#blackDownloadMenu::item,
QMenu#deskLyricMenu::item {
height: 38px;
/*设置文字边距*/
padding-left: 20px;
padding-top: 0px;
padding-bottom: 0px;
}
QMenu#addToMenu::item:selected,
QMenu#addFromMenu::item:selected {
background-color: rgb(64, 64, 64);
}
QMenu#addToMenu::separator {
height: 1px;
margin-right: 14px;
margin-left: 14px;
}
/*调整图标位置*/
QMenu#addToMenu::icon,
QMenu#addFromMenu::icon,
QMenu#blackAddToMenu::icon,
QMenu#blackDownloadMenu::icon,
QMenu#deskLyricMenu::icon {
position: absolute;
left: 14px;
}
/*排序模式菜单样式*/
QMenu#sortModeMenu {
font-size: 16px;
height: 114px;
border: 1px solid rgb(210, 210, 210);
border-bottom: none;
padding-top: 1px;
border-radius: 0px;
background-color: rgb(43, 43, 43);
}
QMenu#sortModeMenu[modeNumber='4'] {
height: 152px;
}
QMenu#sortModeMenu[modeNumber='2'] {
height: 76px;
}
QMenu#sortModeMenu::item {
height: 14px;
color: white;
padding: 11px 85px 11px 12px;
margin: 0px 1px 1px 1px;
}
QMenu#sortModeMenu::item:default {
background-color: rgb(0, 153, 188);
}
QMenu#sortModeMenu::item:selected {
background: rgb(64, 64, 64);
}
QMenu#sortModeMenu::item:default:selected {
background: rgb(0, 107, 131);
}
/*单行输入框右击菜单样式*/
QMenu#lineEditMenu {
width: 1px;
height: 1px;
......@@ -125,10 +43,6 @@ QMenu#lineEditMenu {
padding: 5px 0px 5px 0px;
}
QMenu#lineEditMenu[hasCancelAct='true'] {
width: 1px;
}
QMenu#lineEditMenu::icon {
position: absolute;
left: 16px;
......@@ -138,204 +52,6 @@ QMenu#lineEditMenu::item {
padding-left: 26px;
}
/*主界面更多操作菜单样式*/
QMenu#moreActionsMenu {
border-radius: 0px;
width: 1px;
}
QMenu#lyricMenu,
QMenu#playSpeedMenu {
border-radius: 0px;
}
QMenu#moreActionsMenu::icon,
QMenu#lyricMenu::icon,
QMenu#playSpeedMenu::icon {
position: absolute;
left: 14px;
}
QMenu#moreActionsMenu::item,
QMenu#lyricMenu::item,
QMenu#playSpeedMenu::item {
/*设置文字边距*/
padding-left: 20px;
padding-top: 5px;
padding-bottom: 5px;
}
/*正在播放界面菜单样式*/
QMenu#playingInterfaceMenu::right-arrow {
border-image: url(:/images/playing_interface/ChevronRight.png);
}
QMenu#playingInterfaceMenu,
QMenu#blackAddToMenu,
QMenu#blackDownloadMenu {
color: white;
background-color: rgb(31, 31, 31);
border: 1px solid rgb(20, 20, 20);
border-radius: 5px;
font-size: 18px;
font: 18px 'Segoe UI', 'Microsoft YaHei';
}
QMenu#playingInterfaceMenu::separator,
QMenu#blackAddToMenu::separator,
QMenu#blackDownloadMenu::separator {
background-color: rgb(120, 120, 120);
}
QMenu#playingInterfaceMenu::item {
padding-right: 42px;
}
QMenu#playingInterfaceMenu::item,
QMenu#blackAddToMenu::item,
QMenu#blackDownloadMenu::item {
color: white;
}
QMenu#playingInterfaceMenu::item:disabled {
color: rgb(120, 120, 120);
background: transparent;
}
QMenu#playingInterfaceMenu::item:disabled:selected {
color: rgb(120, 120, 120);
background: transparent;
}
QMenu#playingInterfaceMenu::item:disabled:pressed {
color: rgb(120, 120, 120);
background: transparent;
}
QMenu#playingInterfaceMenu::item:selected,
QMenu#blackAddToMenu::item:selected,
QMenu#blackDownloadMenu::item:selected {
background: rgb(54, 54, 54);
color: white;
}
QMenu#playingInterfaceMenu::item:pressed,
QMenu#blackAddToMenu::item:pressed,
QMenu#blackDownloadMenu::item:pressed {
background: rgb(77, 77, 77);
color: white;
}
QMenu#blackAddToMenu,
QMenu#blackDownloadMenu {
padding: 5px 0px 8px 0px;
}
/*亚克力菜单样式*/
QMenu[effect='acrylic'] {
background: transparent;
border: 0px;
}
QMenu#acrylicMenu::item {
/* padding-right: 20px;
padding-left: 13px; */
padding: 7px 20px 7px 13px;
background-color: transparent;
}
QMenu#acrylicMenu::item:selected {
border-width: 1px;
border-color: rgb(212, 212, 212);
background-color: rgba(0, 0, 0, 25);
color: black;
}
QMenu#albumCardContextMenu::item,
QMenu#playlistCardContextMenu::item {
padding: 7px 20px 7px 14px;
}
QMenu#albumCardContextMenu::item:selected,
QMenu#playlistCardContextMenu::item:selected {
background-color: rgba(0, 0, 0, 25);
}
QMenu#albumCardContextMenu::separator,
QMenu#playlistCardContextMenu::separator {
height: 1px;
background: rgba(255, 255, 255, 104);
margin-right: 15px;
margin-top: 5px;
margin-bottom: 5px;
margin-left: 15;
}
/*专辑界面更多操作菜单*/
QMenu#appBarMoreActionsMenu,
QMenu#albumInterfaceAddToMenu {
width: 1px;
border-radius: 0px;
}
QMenu#appBarMoreActionsMenu::item {
min-width: 120px;
}
QMenu#albumInterfaceAddToMenu::item:selected {
height: 38px;
}
QMenu#albumInterfaceAddToMenu::separator {
height: 1px;
background: rgba(255, 255, 255, 104);
margin-right: 15px;
margin-top: 6px;
margin-bottom: 6px;
margin-left: 15;
}
QMenu#albumInterfaceAddToMenu::icon {
position: absolute;
left: 14px;
height: 20px;
width: 20px;
}
QMenu#albumInterfaceAddToMenu::item {
/*设置文字边距*/
padding-left: 20px;
margin-bottom: 2px;
}
QMenu#onlineSongListContextMenu::item {
padding-right: 35px;
}
/* 系统托盘菜单 */
QMenu#systemTrayMenu {
width: 300px;
}
QMenu#systemTrayMenu::separator {
height: 1px;
margin-top: 5px;
margin-bottom: 5px;
margin-left: 0;
margin-right: 0;
background: rgba(255, 255, 255, 80);
}
QMenu#systemTrayMenu::icon {
padding: 0px 0px 0px 27px;
}
QMenu#systemTrayMenu::item {
padding: 7px 20px 7px 25px;
QMenu#lineEditMenu[selectAll=true]::item {
width: 118px;
}
\ No newline at end of file
SettingInterface, #scrollWidget {
background-color: rgb(39, 39, 39);
}
QScrollArea {
border: none;
background-color: rgb(39, 39, 39);
}
/* 标签 */
QLabel#settingLabel {
font: 41px 'Microsoft YaHei Light';
background-color: transparent;
color: white;
}
/* 滚动条 */
QScrollBar {
background: transparent;
width: 5px;
margin-top: 40px;
margin-bottom: 0;
padding-right: 2px;
}
/*隐藏上箭头*/
QScrollBar::sub-line {
background: transparent;
}
/*隐藏下箭头*/
QScrollBar::add-line {
background: transparent;
}
QScrollBar::handle {
background: rgb(122, 122, 122);
border: 2px solid rgb(128, 128, 128);
border-radius: 1px;
min-height: 40px;
}
QScrollBar::add-page:vertical,
QScrollBar::sub-page:vertical {
background: none;
}
\ No newline at end of file
......@@ -17,13 +17,13 @@ QToolButton {
margin: 0px;
width: 14px;
height: 14px;
border-image: url(:/images/state_tooltip/close_normal.png) top center no-repeat;
border-image: url(:/qfluentwidgets/images/state_tool_tip/close_normal.png) top center no-repeat;
}
QToolButton:hover {
border-image: url(:/images/state_tooltip/close_hover.png) top center no-repeat;
border-image: url(:/qfluentwidgets/images/state_tool_tip/close_hover.png) top center no-repeat;
}
QToolButton:pressed {
border-image: url(:/images/state_tooltip/close_hover.png) top center no-repeat;
border-image: url(:/qfluentwidgets/images/state_tool_tip/close_hover.png) top center no-repeat;
}
\ No newline at end of file
ColorDialog, QScrollArea, QWidget {
background-color: transparent;
}
QScrollArea{
border: 1px solid transparent;
border-radius: 10px;
background-color: transparent;
}
#centerWidget{
border: 1px solid rgb(144, 144, 142);
border-radius: 10px;
background-color: white;
}
#buttonGroup {
background-color: rgb(243, 243, 243);
border-top: 1px solid rgb(229, 229, 229);
border-left: none;
border-right: none;
border-bottom: none;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
QLabel {
font: 18px 'Segoe UI', 'Microsoft YaHei';
color: black;
background-color: transparent;
}
#titleLabel {
font-size: 24px;
}
#editLabel{
font-size: 20px;
}
#prefixLabel {
padding: 0;
font-size: 18px;
}
QPushButton {
background: rgb(251, 251, 251);
border: 1px solid rgb(229, 229, 229);
border-bottom: 1px solid rgb(204, 204, 204);
border-radius: 5px;
font: 17px 'Segoe UI', 'Microsoft YaHei';
padding: 7px 0;
}
QPushButton:hover {
background: rgb(246, 246, 246);
}
QPushButton:pressed {
background: rgb(245, 245, 245);
border-bottom: 1px solid rgb(229, 229, 229);
}
QPushButton#yesButton {
color: white;
background-color: rgb(0, 107, 131);
border: 1px solid rgb(20, 119, 141);
border-bottom: 1px solid rgb(0, 64, 79);
}
QPushButton#yesButton:hover {
background-color: rgb(25, 121, 142);
border: 1px solid rgb(43, 132, 151);
border-bottom: 1px solid rgb(15, 73, 85);
}
QPushButton#yesButton:pressed {
background-color: rgb(49, 134, 153);
border: 1px solid rgb(49, 134, 153);
}
/* 滑动条 */
QSlider:horizontal {
min-width: 415px;
min-height: 30px;
}
QSlider::groove:horizontal {
height: 16px;
border-radius: 8px;
background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, x3:2, y3:0,
stop:0 hsv(--slider-hue, --slider-saturation, 0),
stop:1 hsv(--slider-hue, --slider-saturation, 255));
}
QSlider::handle:horizontal {
border: 1px solid rgb(222, 222, 222);
width: 26px;
min-height: 13px;
margin: -6px 0;
border-radius: 14px;
background-color: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5,
stop:0 rgb(0, 0, 0),
stop:0.55 rgb(0, 0, 0),
stop:0.6 rgb(255, 255, 255),
stop:1 rgb(255, 255, 255));
}
QSlider::groove:horizontal:disabled {
background-color: rgba(0, 0, 0, 75);
}
QSlider::handle:horizontal:disabled {
background-color: #808080;
border: 6px solid #cccccc;
}
/* Line Edit */
QLineEdit {
background-color: white;
border: 1px solid rgba(0, 0, 0, 13);
border-bottom: 1px solid rgba(0, 0, 0, 100);
border-radius: 7px;
font: 18px "Segoe UI";
padding: 0px 13px;
selection-background-color: rgb(0, 183, 195);
}
QLineEdit:hover {
background-color: rgb(252, 252, 252);
border: 1px solid rgba(0, 0, 0, 13);
border-bottom: 1px solid rgba(0, 0, 0, 100);
}
QLineEdit:focus {
border-bottom: 1px solid rgb(0, 159, 170);
background-color: white;
border-top: 1px solid rgba(0, 0, 0, 13);
border-left: 1px solid rgba(0, 0, 0, 13);
border-right: 1px solid rgba(0, 0, 0, 13);
}
QLineEdit:disabled {
color: rgba(0, 0, 0, 150);
background-color: rgba(0, 0, 0, 13);
border: 1px solid rgba(0, 0, 0, 5);
}
#clearButton {
background-color: transparent;
border-radius: 4px;
margin: 0;
}
#clearButton:hover {
background-color: rgba(0, 0, 0, 9);
}
#clearButton:pressed {
background-color: rgba(0, 0, 0, 6);
}
/* 滚动条 */
QScrollBar {
background: transparent;
width: 5px;
margin-top: 40px;
margin-bottom: 133px;
padding-right: 2px;
}
/*隐藏上箭头*/
QScrollBar::sub-line {
background: transparent;
}
/*隐藏下箭头*/
QScrollBar::add-line {
background: transparent;
}
QScrollBar::handle {
background: rgb(122, 122, 122);
border: 2px solid rgb(128, 128, 128);
border-radius: 1px;
min-height: 40px;
}
QScrollBar::add-page:vertical,
QScrollBar::sub-page:vertical {
background: none;
}
\ No newline at end of file
......@@ -117,24 +117,40 @@ QRadioButton::indicator:hover {
}
QRadioButton::indicator:pressed {
background-color: rgba(0, 0, 0, 5);
border: 2px solid #bbbbbb;
image: url(:/images/setting_card/RadioButton_white.png);
background-color: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5,
stop:0 rgb(255, 255, 255),
stop:0.5 rgb(255, 255, 255),
stop:0.55 rgb(225, 224, 223),
stop:1 rgb(225, 224, 223));
}
QRadioButton::indicator:checked {
background-color: rgb(72, 210, 242);
border: 2px solid rgb(72, 210, 242);
image: url(:/images/setting_card/RadioButton_white.png);
color: rgb(255, 255, 255);
height: 26px;
width: 26px;
border: none;
border-radius: 13px;
background-color: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5,
stop:0 rgb(255, 255, 255),
stop:0.5 rgb(255, 255, 255),
stop:0.55 rgb(72, 210, 242),
stop:1 rgb(72, 210, 242));
}
QRadioButton::indicator:checked:hover {
image: url(:/images/setting_card/RadioButtonHover_white.png);
background-color: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5,
stop:0 rgb(255, 255, 255),
stop:0.6 rgb(255, 255, 255),
stop:0.65 rgb(72, 210, 242),
stop:1 rgb(72, 210, 242));
}
QRadioButton::indicator:checked:pressed {
image: url(:/images/setting_card/RadioButtonPressed_white.png);
background-color: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5,
stop:0 rgb(255, 255, 255),
stop:0.5 rgb(255, 255, 255),
stop:0.55 rgb(72, 210, 242),
stop:1 rgb(72, 210, 242));
}
QRadioButton:disabled {
......
QMenu {
background-color: rgb(232, 232, 232);
font-size: 18px;
font: 18px 'Segoe UI', 'Microsoft YaHei';
padding: 5px 0px 16px 0px;
padding: 5px 0px 5px 0px;
border: 1px solid rgb(200, 200, 200);
/* border-radius: 5px; */
}
QMenu::right-arrow {
width: 16px;
height: 16px;
right: 16px;
border-image: url(:/images/menu/ChevronRight_black.png);
border-image: url(:/qfluentwidgets/images/menu/ChevronRight_black.png);
}
QMenu::separator {
......@@ -25,7 +22,7 @@ QMenu::separator {
}
QMenu::item {
padding: 7px 20px 7px 13px;
padding: 7px 13px 7px 13px;
}
QMenu::item:selected {
......@@ -40,12 +37,6 @@ QMenu#lineEditMenu {
width: 1px;
height: 1px;
border: 1px solid rgb(200, 200, 200);
border-radius: 0px;
padding: 5px 0px 5px 0px;
}
QMenu#lineEditMenu[hasCancelAct='true'] {
width: 1px;
}
QMenu#lineEditMenu::icon {
......@@ -56,3 +47,7 @@ QMenu#lineEditMenu::icon {
QMenu#lineEditMenu::item {
padding-left: 26px;
}
QMenu#lineEditMenu[selectAll=true]::item {
width: 118px;
}
......@@ -166,11 +166,11 @@ QPushButton:pressed {
}
#hyperlinkButton:hover {
background-color: rgba(0, 0, 0, 20);
background-color: rgba(0, 0, 0, 15);
}
#hyperlinkButton:pressed {
background-color: rgba(0, 0, 0, 10);
background-color: rgba(0, 0, 0, 7);
}
#primaryButton {
......
SettingInterface, #scrollWidget {
background-color: rgb(255, 255, 255);
}
QScrollArea {
background-color: rgb(255, 255, 255);
border: none;
}
/* 标签 */
QLabel#settingLabel {
font: 41px 'Microsoft YaHei Light';
background-color: transparent;
}
/* 滚动条 */
QScrollBar {
background: transparent;
width: 5px;
margin-top: 40px;
margin-bottom: 0;
padding-right: 2px;
}
/*隐藏上箭头*/
QScrollBar::sub-line {
background: transparent;
}
/*隐藏下箭头*/
QScrollBar::add-line {
background: transparent;
}
QScrollBar::handle {
background: rgb(122, 122, 122);
border: 2px solid rgb(128, 128, 128);
border-radius: 1px;
min-height: 40px;
}
QScrollBar::add-page:vertical,
QScrollBar::sub-page:vertical {
background: none;
}
\ No newline at end of file
......@@ -17,13 +17,13 @@ QToolButton {
margin: 0px;
width: 14px;
height: 14px;
border-image: url(:/images/state_tooltip/close_normal.png) top center no-repeat;
border-image: url(:/qfluentwidgets/images/state_tool_tip/close_normal.png) top center no-repeat;
}
QToolButton:hover {
border-image: url(:/images/state_tooltip/close_hover.png) top center no-repeat;
border-image: url(:/qfluentwidgets/images/state_tool_tip/close_hover.png) top center no-repeat;
}
QToolButton:pressed {
border-image: url(:/images/state_tooltip/close_hover.png) top center no-repeat;
border-image: url(:/qfluentwidgets/images/state_tool_tip/close_hover.png) top center no-repeat;
}
\ No newline at end of file
此差异已折叠。
......@@ -113,12 +113,6 @@
<file>images/setting_card/PencilInk_white.png</file>
<file>images/setting_card/Question_black.png</file>
<file>images/setting_card/Question_white.png</file>
<file>images/setting_card/RadioButtonHover_black.png</file>
<file>images/setting_card/RadioButtonHover_white.png</file>
<file>images/setting_card/RadioButtonPressed_black.png</file>
<file>images/setting_card/RadioButtonPressed_white.png</file>
<file>images/setting_card/RadioButton_black.png</file>
<file>images/setting_card/RadioButton_white.png</file>
<file>images/setting_card/Search_black.png</file>
<file>images/setting_card/Search_white.png</file>
<file>images/setting_card/Transparent_black.png</file>
......@@ -153,7 +147,9 @@
<file>qss/dark/switch_button.qss</file>
<file>qss/dark/tool_tip.qss</file>
<file>qss/dark/folder_list_dialog.qss</file>
<file>qss/dark/setting_interface.qss</file>
<file>qss/light/color_dialog.qss</file>
<file>qss/light/dialog.qss</file>
<file>qss/light/expand_setting_card.qss</file>
<file>qss/light/menu.qss</file>
......@@ -164,5 +160,6 @@
<file>qss/light/switch_button.qss</file>
<file>qss/light/tool_tip.qss</file>
<file>qss/light/folder_list_dialog.qss</file>
<file>qss/light/setting_interface.qss</file>
</qresource>
</RCC>
\ No newline at end of file
from .config import *
from .auto_wrap import TextWrap
from .style_sheet import setStyleSheet, getStyleSheet
from .dpi_manager import dpi_manager
from .icon import Icon, getIconColor
from .image_utils import DominantColor, gaussianBlur
\ No newline at end of file
from .image_utils import DominantColor, gaussianBlur
from .style_sheet import setStyleSheet, getStyleSheet
\ No newline at end of file
......@@ -5,10 +5,10 @@ from pathlib import Path
from typing import Iterable, List, Union
import darkdetect
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QColor
from .exception_handler import exceptionHandler
from .singleton import Singleton
class ConfigValidator:
......@@ -150,7 +150,7 @@ class ConfigItem:
""" Config item """
def __init__(self, group: str, name: str, default, validator: ConfigValidator = None,
serializer: ConfigSerializer = None):
serializer: ConfigSerializer = None, restart=False):
"""
Parameters
----------
......@@ -168,6 +168,9 @@ class ConfigItem:
serializer: ConfigSerializer
config serializer
restart: bool
whether to restart the application after updating value
"""
self.group = group
self.name = name
......@@ -175,6 +178,7 @@ class ConfigItem:
self.serializer = serializer or ConfigSerializer()
self.__value = default
self.value = default
self.restart = restart
@property
def value(self):
......@@ -190,6 +194,9 @@ class ConfigItem:
""" get the config key separated by `.` """
return self.group+"."+self.name if self.name else self.group
def __str__(self) -> str:
return f'{self.__class__.__name__}[value={self.value}]'
def serialize(self):
return self.serializer.serialize(self.value)
......@@ -205,6 +212,9 @@ class RangeConfigItem(ConfigItem):
""" get the available range of config """
return self.validator.range
def __str__(self) -> str:
return f'{self.__class__.__name__}[range={self.range}, value={self.value}]'
class OptionsConfigItem(ConfigItem):
""" Config item with options """
......@@ -213,44 +223,53 @@ class OptionsConfigItem(ConfigItem):
def options(self):
return self.validator.options
def __str__(self) -> str:
return f'{self.__class__.__name__}[options={self.options}, value={self.value}]'
class ColorConfigItem(ConfigItem):
""" Color config item """
def __init__(self, group: str, name: str, default):
super().__init__(group, name, QColor(default), ColorValidator(default), ColorSerializer())
def __init__(self, group: str, name: str, default, restart=False):
super().__init__(group, name, QColor(default), ColorValidator(default),
ColorSerializer(), restart)
def __str__(self) -> str:
return f'{self.__class__.__name__}[value={self.value.name()}]'
class Config(Singleton):
class QConfig(QObject):
""" Config of app """
folder = Path('config')
file = folder/"config.json"
appRestartSig = pyqtSignal()
themeMode = OptionsConfigItem(
"MainWindow", "Theme", "Light", OptionsValidator(["Light", "Dark", "Auto"]))
def __init__(self):
super().__init__()
self.file = Path("config/config.json")
self._theme = "Light"
self._cfg = self
@classmethod
def get(cls, item: ConfigItem):
def get(self, item: ConfigItem):
return item.value
@classmethod
def set(cls, item: ConfigItem, value):
def set(self, item: ConfigItem, value):
if item.value == value:
return
item.value = value
cls.save()
self.save()
if item.restart:
self._cfg.appRestartSig.emit()
@classmethod
def toDict(cls, serialize=True):
def toDict(self, serialize=True):
""" convert config items to `dict` """
items = {}
for name in dir(cls):
item = getattr(cls, name)
for name in dir(self._cfg.__class__):
item = getattr(self._cfg.__class__, name)
if not isinstance(item, ConfigItem):
continue
......@@ -266,30 +285,39 @@ class Config(Singleton):
return items
@classmethod
def save(cls):
cls.folder.mkdir(parents=True, exist_ok=True)
with open(cls.file, "w", encoding="utf-8") as f:
json.dump(cls.toDict(), f, ensure_ascii=False, indent=4)
def save(self):
self._cfg.file.parent.mkdir(parents=True, exist_ok=True)
with open(self._cfg.file, "w", encoding="utf-8") as f:
json.dump(self._cfg.toDict(), f, ensure_ascii=False, indent=4)
@exceptionHandler()
def load(self, file=None, config=None):
""" load config
Parameters
----------
file: str or Path
the path of json config file
config: Config
config object to be initialized
"""
if isinstance(config, QConfig):
self._cfg = config
@exceptionHandler
def load(self, file: str = None):
""" load config """
if isinstance(file, str) and Path(file).exists():
path = Path(file)
Config.folder = path.parent
Config.file = path.name
if isinstance(file, (str, Path)):
self._cfg.file = Path(file)
try:
with open(self.file, encoding="utf-8") as f:
with open(self._cfg.file, encoding="utf-8") as f:
cfg = json.load(f)
except:
cfg = {}
# map config items'key to item
items = {}
for name in dir(self.__class__):
item = getattr(Config, name)
for name in dir(self._cfg.__class__):
item = getattr(self._cfg.__class__, name)
if isinstance(item, ConfigItem):
items[item.key] = item
......@@ -303,15 +331,15 @@ class Config(Singleton):
if items.get(key) is not None:
items[key].deserializeFrom(value)
if self.get(self.themeMode) == "Auto":
self._theme = darkdetect.theme() or "Light"
if self.get(self._cfg.themeMode) == "Auto":
self._cfg._theme = darkdetect.theme() or "Light"
else:
self._theme = self.get(self.themeMode)
self._cfg._theme = self.get(self._cfg.themeMode)
@property
def theme(self):
""" get theme mode, can be `light` or `dark` """
return self._theme.lower()
return self._cfg._theme.lower()
config = Config()
config = QConfig()
# coding:utf-8
import sys
if sys.platform == "win32":
from win32con import DESKTOPHORZRES, HORZRES
from win32gui import GetDC, ReleaseDC
from win32print import GetDeviceCaps
elif sys.platform == "darwin":
from Cocoa import NSScreen, NSDeviceSize, NSDeviceResolution
from Quartz import CGDisplayScreenSize
else:
import xcffib
import xcffib.xproto
import xcffib.randr
from PyQt5.QtX11Extras import QX11Info
from PyQt5 import sip
class DPIManager:
""" DPI Manager """
def __new__(cls, *args, **kwargs):
if sys.platform == "win32":
cls = WindowsDPIManager
elif sys.platform == "darwin":
cls = MacDPIManager
else:
cls = LinuxDPIManager
return super().__new__(cls, *args, **kwargs)
def __init__(self):
self.scale = self._get_scale()
self.dpi = round(self.scale*96)
def _get_scale(self) -> float:
return 1
class WindowsDPIManager(DPIManager):
""" Windows DPI Manager """
def _get_scale(self) -> float:
hdc = GetDC(None)
t = GetDeviceCaps(hdc, DESKTOPHORZRES)
d = GetDeviceCaps(hdc, HORZRES)
ReleaseDC(None, hdc)
return t / d
class LinuxDPIManager(DPIManager):
""" Linux DPI Manager """
def _get_scale(self) -> float:
x = xcffib.connect()
x.randr = x(xcffib.randr.key)
res = x.randr.GetScreenResources(x.setup.roots[0].root).reply()
dpi = 96
px = dict(w=0, h=0)
mm = dict(w=0, h=0)
for crtc in res.crtcs:
info = x.randr.GetCrtcInfo(crtc, xcffib.CurrentTime).reply()
px['w'] += info.width
px['h'] += info.height
for out in res.outputs:
info = x.randr.GetOutputInfo(out, xcffib.CurrentTime).reply()
mm['w'] += info.mm_width
mm['h'] += info.mm_height
if mm['w'] > 0:
w_dpi = px['w'] * 25.4 / mm['w']
h_dpi = px['h'] * 25.4 / mm['h']
dpi = (w_dpi + h_dpi) / 2
return dpi/96
class MacDPIManager(DPIManager):
def _get_scale(self) -> float:
# for screen in NSScreen.screens():
# return screen.backingScaleFactor()
return 1
dpi_manager = DPIManager()
# coding:utf-8
class Singleton:
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
from .dialog_box import *
from .layout import *
from .settings import *
from .widgets import *
......@@ -6,6 +6,7 @@ from PyQt5.QtWidgets import QFrame, QHBoxLayout, QLabel, QToolButton, QVBoxLayou
from ..dialog_box.color_dialog import ColorDialog
from ..widgets.switch_button import SwitchButton, IndicatorPosition
from ..widgets.slider import Slider
from ..widgets.label import PixmapLabel
from ...common.style_sheet import setStyleSheet, getStyleSheet
from ...common.config import ConfigItem, config, RangeConfigItem
from ...common.icon import getIconColor
......@@ -31,7 +32,7 @@ class SettingCard(QFrame):
parent widget
"""
super().__init__(parent=parent)
self.iconLabel = QLabel(self)
self.iconLabel = PixmapLabel(self)
self.titleLabel = QLabel(title, self)
self.contentLabel = QLabel(content or '', self)
self.hBoxLayout = QHBoxLayout(self)
......
from .acrylic_label import AcrylicLabel
from .label import AcrylicLabel
from .menu import DWMMenu, LineEditMenu
from .slider import Slider, HollowHandleStyle
from .stacked_widget import PopUpAniStackedWidget, OpacityAniStackedWidget
from .switch_button import SwitchButton, IndicatorPosition
from .state_tool_tip import StateToolTip
from .state_tool_tip import StateToolTip, ToastToolTip
from .scroll_area import ScrollArea
from .tool_tip import ToolTip
\ No newline at end of file
......@@ -138,3 +138,29 @@ class AcrylicLabel(QLabel):
if not self.blurPixmap.isNull():
self.setPixmap(self.blurPixmap.scaled(
self.size(), Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation))
class PixmapLabel(QLabel):
""" Label for high dpi screen """
def __init__(self, parent=None):
super().__init__(parent)
self.__pixmap = QPixmap()
def setPixmap(self, pixmap: QPixmap):
self.__pixmap = pixmap
self.setFixedSize(pixmap.size())
self.update()
def pixmap(self):
return self.__pixmap
def paintEvent(self, e):
if self.__pixmap.isNull():
return
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing |
QPainter.SmoothPixmapTransform)
painter.setPen(Qt.NoPen)
painter.drawPixmap(self.rect(), self.__pixmap)
\ No newline at end of file
......@@ -4,7 +4,7 @@ from PyQt5.QtCore import QEasingCurve, QEvent, QPropertyAnimation, QPoint, QRect
from PyQt5.QtWidgets import QAction, QApplication, QMenu, QWidget
from ...common.icon import Icon, getIconColor
from ...common.style_sheet import setStyleSheet
from ...common.style_sheet import setStyleSheet, getStyleSheet
class MenuIconFactory:
......@@ -45,7 +45,7 @@ class MenuIconFactory:
@classmethod
def create(cls, iconType: str):
""" create icon """
path = f":/images/menu/{iconType}_{getIconColor()}.png"
path = f":/qfluentwidgets/images/menu/{iconType}_{getIconColor()}.png"
return Icon(path)
......@@ -57,7 +57,7 @@ class DWMMenu(QMenu):
def __init__(self, title="", parent=None):
super().__init__(title, parent)
self.windowEffect = WindowEffect()
self.windowEffect = WindowEffect(self)
self.setWindowFlags(
Qt.FramelessWindowHint | Qt.Popup | Qt.NoDropShadowWindowHint)
self.setAttribute(Qt.WA_StyledBackground)
......@@ -92,7 +92,7 @@ class LineEditMenu(DWMMenu):
self.animation = QPropertyAnimation(self, b"geometry")
self.animation.setDuration(300)
self.animation.setEasingCurve(QEasingCurve.OutQuad)
setStyleSheet(self, 'menu')
self.setProperty("selectAll", bool(self.parent().text()))
def createActions(self):
self.cutAct = QAction(
......@@ -135,11 +135,9 @@ class LineEditMenu(DWMMenu):
def exec_(self, pos):
self.clear()
self.createActions()
self.setProperty("hasCancelAct", "false")
if QApplication.clipboard().mimeData().hasText():
if self.parent().text():
self.setProperty("hasCancelAct", "true")
if self.parent().selectedText():
self.addActions(self.action_list)
else:
......@@ -148,7 +146,6 @@ class LineEditMenu(DWMMenu):
self.addAction(self.pasteAct)
else:
if self.parent().text():
self.setProperty("hasCancelAct", "true")
if self.parent().selectedText():
self.addActions(
self.action_list[:2] + self.action_list[3:])
......@@ -157,7 +154,7 @@ class LineEditMenu(DWMMenu):
else:
return
w = 130+max(self.fontMetrics().width(i.text()) for i in self.actions())
w = 128+max(self.fontMetrics().width(i.text()) for i in self.actions())
h = len(self.actions()) * 40 + 10
self.animation.setStartValue(QRect(pos.x(), pos.y(), 1, 1))
......
......@@ -22,7 +22,7 @@ class ScrollArea(QScrollArea):
self.scrollStamps = deque()
self.stepsLeftQueue = deque()
self.smoothMoveTimer = QTimer(self)
self.smoothMode = SmoothMode(SmoothMode.COSINE)
self.smoothMode = SmoothMode(SmoothMode.LINEAR)
self.smoothMoveTimer.timeout.connect(self.__smoothMove)
def setSmoothMode(self, smoothMode):
......
# coding:utf-8
from PyQt5.QtCore import QEasingCurve, QPropertyAnimation, Qt, QTimer, pyqtSignal
from PyQt5.QtCore import QEasingCurve, QPropertyAnimation, Qt, QTimer, pyqtSignal, QSize, QPoint
from PyQt5.QtGui import QPainter, QPixmap
from PyQt5.QtWidgets import QLabel, QWidget, QToolButton
from PyQt5.QtWidgets import QLabel, QWidget, QToolButton, QGraphicsOpacityEffect
from .label import PixmapLabel
from ...common import setStyleSheet
from ..._rc import resource
class StateToolTip(QWidget):
......@@ -137,3 +137,108 @@ class StateToolTip(QWidget):
else:
painter.drawPixmap(14, 13, self.doneImage.width(),
self.doneImage.height(), self.doneImage)
class ToastToolTip(QWidget):
""" Toast tooltip """
def __init__(self, title: str, content: str, icon: str, parent=None):
"""
Parameters
----------
title: str
title of tooltip
content: str
content of tooltip
icon: str
icon of toast, can be `completed` or `info`
parant:
parent window
"""
super().__init__(parent)
self.title = title
self.content = content
self.icon = f":/qfluentwidgets/images/state_tool_tip/{icon}.png"
self.titleLabel = QLabel(self.title, self)
self.contentLabel = QLabel(self.content, self)
self.iconLabel = PixmapLabel(self)
self.closeButton = QToolButton(self)
self.closeTimer = QTimer(self)
self.opacityEffect = QGraphicsOpacityEffect(self)
self.opacityAni = QPropertyAnimation(self.opacityEffect, b"opacity")
self.slideAni = QPropertyAnimation(self, b'pos')
self.__initWidget()
def __initWidget(self):
""" initialize widgets """
self.setAttribute(Qt.WA_StyledBackground)
self.closeButton.setFixedSize(QSize(14, 14))
self.closeButton.setIconSize(QSize(14, 14))
self.closeTimer.setInterval(2000)
self.contentLabel.setMinimumWidth(250)
self.iconLabel.setPixmap(QPixmap(self.icon))
self.iconLabel.adjustSize()
self.iconLabel.move(15, 13)
self.setGraphicsEffect(self.opacityEffect)
self.opacityEffect.setOpacity(1)
# connect signal to slot
self.closeButton.clicked.connect(self.hide)
self.closeTimer.timeout.connect(self.__fadeOut)
self.__setQss()
self.__initLayout()
def __initLayout(self):
""" initialize layout """
self.setFixedSize(max(self.titleLabel.width(),
self.contentLabel.width()) + 90, 64)
self.titleLabel.move(40, 11)
self.contentLabel.move(15, 34)
self.closeButton.move(self.width() - 30, 23)
def __setQss(self):
""" set style sheet """
self.titleLabel.setObjectName("titleLabel")
self.contentLabel.setObjectName("contentLabel")
setStyleSheet(self, 'state_tool_tip')
self.titleLabel.adjustSize()
self.contentLabel.adjustSize()
def __fadeOut(self):
""" fade out """
self.opacityAni.setDuration(300)
self.opacityAni.setStartValue(1)
self.opacityAni.setEndValue(0)
self.opacityAni.finished.connect(self.deleteLater)
self.opacityAni.start()
def getSuitablePos(self):
""" get suitable position in main window """
for i in range(10):
dy = i*(self.height() + 20)
pos = QPoint(self.window().width() - self.width() - 30, 63+dy)
widget = self.window().childAt(pos + QPoint(2, 2))
if isinstance(widget, (StateToolTip, ToastToolTip)):
pos += QPoint(0, self.height() + 20)
else:
break
return pos
def showEvent(self, e):
pos = self.getSuitablePos()
self.slideAni.setDuration(200)
self.slideAni.setEasingCurve(QEasingCurve.OutQuad)
self.slideAni.setStartValue(QPoint(self.window().width(), pos.y()))
self.slideAni.setEndValue(pos)
self.slideAni.start()
super().showEvent(e)
self.closeTimer.start()
......@@ -39,6 +39,7 @@ class ToolTip(QFrame):
self.timer.timeout.connect(self.hide)
# set style
self.setAttribute(Qt.WA_TransparentForMouseEvents)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setWindowFlags(Qt.Tool | Qt.FramelessWindowHint)
self.setDarkTheme(False)
......
......@@ -2,4 +2,6 @@ PyQt5-Frameless-Window
darkdetect
colorthief
scipy
pillow
\ No newline at end of file
pillow
sphinx-markdown-tables==0.0.17
sphinx-rtd-theme==1.1.1
\ No newline at end of file
......@@ -6,7 +6,7 @@ with open('README.md', encoding='utf-8') as f:
setuptools.setup(
name="PyQt-Fluent-Widgets",
version="0.0.1",
version="0.1.0",
keywords="pyqt fluent widgets",
author="zhiyiYo",
author_email="shokokawaii@outlook.com",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册