- added qdarktheme package into the code

This commit is contained in:
Marius Stanciu
2023-10-19 13:49:14 +03:00
parent 656bec2e7c
commit 28dbb70126
42 changed files with 3548 additions and 22 deletions

View File

@@ -11,6 +11,7 @@ CHANGELOG for FlatCAM Evo beta
- fixed preprocessors issue where the start GCode was not added due of trying to access an object that did not exist (because its name was not changed to reflect the changes in other parts on the app)
- updated the requirements.txt file
- added qdarktheme package into the code
27.09.2023

View File

@@ -47,9 +47,9 @@ import socket
import tkinter as tk
import qdarktheme
import qdarktheme.themes.dark.stylesheet as qdarksheet
import qdarktheme.themes.light.stylesheet as qlightsheet
import libs.qdarktheme
import libs.qdarktheme.themes.dark.stylesheet as qdarksheet
import libs.qdarktheme.themes.light.stylesheet as qlightsheet
from typing import Union
@@ -628,11 +628,11 @@ class App(QtCore.QObject):
elif self.options["global_theme"] == 'light':
self.resource_location = 'assets/resources'
qlightsheet.STYLE_SHEET = light_style_sheet.L_STYLE_SHEET
self.qapp.setStyleSheet(qdarktheme.load_stylesheet('light'))
self.qapp.setStyleSheet(libs.qdarktheme.load_stylesheet('light'))
else:
self.resource_location = 'assets/resources/dark_resources'
qdarksheet.STYLE_SHEET = dark_style_sheet.D_STYLE_SHEET
self.qapp.setStyleSheet(qdarktheme.load_stylesheet())
self.qapp.setStyleSheet(libs.qdarktheme.load_stylesheet())
# ############################################################################################################
# ################################### Set LOG verbosity ######################################################
@@ -801,7 +801,6 @@ class App(QtCore.QObject):
self.ui = MainGUI(self)
# ########################
# decide if to show or hide the Notebook side of the screen at startup
if self.options["global_project_at_startup"] is True:
self.ui.splitter.setSizes([1, 1])
@@ -956,7 +955,7 @@ class App(QtCore.QObject):
self.hover_shapes = ShapeCollectionLegacy(obj=self, app=self, name='hover')
# Storage for Selection shapes
self.sel_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name="selection")
self.sel_shapes = ShapeCollectionLegacy(obj=self, app=self, name="selection")
# #############################################################################################################
end_plot_time = time.time()
@@ -4142,9 +4141,9 @@ class App(QtCore.QObject):
"""
Called for converting a Geometry object from single-geo to multi-geo.
Single-geo Geometry objects store their geometry data into self.solid_geometry.
Multi-geo Geometry objects store their geometry data into the self.tools dictionary, each key (a tool actually)
having as a value another dictionary. This value dictionary has one of its keys 'solid_geometry' which holds
the solid-geometry of that tool.
Multi-geo Geometry objects store their geometry data into the `self.tools` dictionary, each key
(a tool actually) having as a value another dictionary. This value dictionary has
one of its keys 'solid_geometry' which holds the solid-geometry of that tool.
:return: None
"""
@@ -4833,7 +4832,7 @@ class App(QtCore.QObject):
except Exception:
pass
if type(dia_box_location) == tuple:
if isinstance(dia_box_location, tuple):
dia_box_location = str(dia_box_location)
else:
dia_box_location = None
@@ -5296,7 +5295,7 @@ class App(QtCore.QObject):
else:
default_data[opt_key] = self.options[opt_key]
if type(self.options["tools_mill_tooldia"]) == float:
if isinstance(self.options["tools_mill_tooldia"], float):
tools_diameters = [self.options["tools_mill_tooldia"]]
else:
try:
@@ -7240,7 +7239,7 @@ class App(QtCore.QObject):
outline = self.options['global_sel_line'][:-2] + str(hex(int(1.0 * 255)))[2:]
self.sel_objects_list.append(
self.sel_shapes.add(b_sel_rect,color=outline, face_color=face, update=True, layer=0, tolerance=None)
self.sel_shapes.add(b_sel_rect, color=outline, face_color=face, update=True, layer=0, tolerance=None)
)
if self.use_3d_engine is False:
self.sel_shapes.redraw()
@@ -7350,17 +7349,17 @@ class App(QtCore.QObject):
return
obj = self.collection.get_active()
if type(obj) == GeometryObject:
if isinstance(obj, GeometryObject):
self.f_handlers.on_file_exportdxf()
elif type(obj) == ExcellonObject:
elif isinstance(obj, ExcellonObject):
self.f_handlers.on_file_saveexcellon()
elif type(obj) == CNCJobObject:
elif isinstance(obj, CNCJobObject):
obj.on_exportgcode_button_click()
elif type(obj) == GerberObject:
elif isinstance(obj, GerberObject):
self.f_handlers.on_file_savegerber()
elif type(obj) == ScriptObject:
elif isinstance(obj, ScriptObject):
self.f_handlers.on_file_savescript()
elif type(obj) == DocumentObject:
elif isinstance(obj, DocumentObject):
self.f_handlers.on_file_savedocument()
def obj_move(self):

View File

@@ -12,8 +12,6 @@ from appGUI import VisPyPatches
from appGUI.GUIElements import FCMessageBox
from multiprocessing import freeze_support
# import copyreg
# import types
MIN_VERSION_MAJOR = 3
MIN_VERSION_MINOR = 6

View File

@@ -0,0 +1,6 @@
"""PyQtDarkTheme - A flat dark theme for PySide and PyQt.
- Repository: https://github.com/5yutan5/PyQtDarkTheme
- Documentation: https://pyqtdarktheme.readthedocs.io
"""
from libs.qdarktheme.main import __version__, clear_cache, get_themes, load_palette, load_stylesheet

251
libs/qdarktheme/main.py Normal file
View File

@@ -0,0 +1,251 @@
"""Main file of qdarktheme."""
from __future__ import annotations
import json
import platform
import re
import shutil
from pathlib import Path
from libs.qdarktheme.qtpy import __version__ as qt_version
from libs.qdarktheme.qtpy.qt_compat import QT_API
from libs.qdarktheme.util import OPERATORS, compare_v, get_logger, multi_replace
# Version of PyQtDarkTheme
__version__ = "1.1.1"
_logger = get_logger(__name__)
if qt_version is None:
_logger.warning("Failed to detect Qt version. Load Qt version as the latest version.")
_qt_version = "10.0.0" # Fairly future version for always setting latest version.
else:
_qt_version = qt_version
if QT_API is None:
_qt_api = "PySide6"
_logger.warning(f"Failed to detect Qt binding. Load Qt API as '{_qt_api}'.")
else:
_qt_api = QT_API
if None in [qt_version, QT_API]:
_logger.warning(
"Maybe you need to install qt-binding. Available Qt-binding packages: PySide6, PyQt6, PyQt5, PySide2."
)
_RESOURCE_HOME_DIR = Path.home() / ".qdarktheme"
_RESOURCES_BASE_DIR = _RESOURCE_HOME_DIR / f"v{__version__}"
# Pattern
_PATTERN_RADIUS = re.compile(r"\$radius\{[\s\S]*?\}")
_PATTERN_ENV_PATCH = re.compile(r"\$env_patch\{[\s\S]*?\}")
def _build_svg_files(theme: str, theme_resources_dir: Path) -> None:
svg_resources_dir = theme_resources_dir / "svg"
if not svg_resources_dir.exists():
svg_resources_dir.mkdir()
else:
return
if theme == "dark":
from libs.qdarktheme.themes.dark.svg import SVG_RESOURCES
else:
from libs.qdarktheme.themes.light.svg import SVG_RESOURCES
for file_name, code in json.loads(SVG_RESOURCES).items():
(svg_resources_dir / f"{file_name}.svg").write_text(code)
def get_themes() -> tuple[str, ...]:
"""Return available theme list.
Returns:
Available themes.
"""
from libs.qdarktheme.themes import THEMES
return THEMES
def _replace_rounded(match: re.Match) -> str:
return match.group().replace("$radius{", "").replace("}", "")
def _replace_sharp(match: re.Match) -> str:
return _PATTERN_RADIUS.sub("0", match.group())
def _parse_radius(stylesheet: str, border: str = "rounded") -> dict[str, str]:
"""Parse `$radius{...}` placeholder in template stylesheet."""
matches = _PATTERN_RADIUS.finditer(stylesheet)
replace = _replace_rounded if border == "rounded" else _replace_sharp
return {match.group(): replace(match) for match in matches}
def _parse_env_patch(stylesheet: str) -> dict[str, str]:
"""Parse `$env_patch{...}` placeholder in template stylesheet.
Template stylesheet has `$env_patch{...}` symbol.
This symbol has json string and resolve the differences of the style between qt versions.
The json keys:
* version - the qt version and qualifier. Available qualifiers: [==, !=, >=, <=, >, <].
* qt - the name of qt binding.
* value - the qt stylesheet string
Args:
stylesheet: The qt stylesheet string.
Raises:
SyntaxError: If the version operator in version key of `$env_patch{...}` is wrong.
Returns:
The dictionary. Key is the text of $env_patch{...} symbol.
Value is the value of the `value` key in $env_patch.
"""
replacements: dict[str, str] = {}
for match in re.finditer(_PATTERN_ENV_PATCH, stylesheet):
match_text = match.group()
json_text = match_text.replace("$env_patch", "")
env_property: dict[str, str] = json.loads(json_text)
patch_version = env_property.get("version")
patch_qt = env_property.get("qt")
patch_os = env_property.get("os")
patch_value = env_property["value"]
results: list[bool] = []
# Parse version
if patch_version is not None:
for operator in OPERATORS:
if operator not in patch_version:
continue
version = patch_version.replace(operator, "")
results.append(compare_v(_qt_version, operator, version))
break
else:
raise SyntaxError(
f"invalid character in qualifier. Available qualifiers {list(OPERATORS.keys())}"
) from None
# Parse qt binding
if patch_qt is not None:
if QT_API is None:
results.append(False)
results.append(patch_qt.lower() == _qt_api.lower())
# Parse os
if patch_os is not None:
results.append(platform.system().lower() in patch_os.lower())
replacements[match_text] = patch_value if all(results) else ""
return replacements
def load_stylesheet(theme: str = "dark", border: str = "rounded") -> str:
"""Load the style sheet which looks like flat design. There are two themes, dark theme and light theme.
Args:
theme: The name of the theme. Available themes are "dark" and "light".
border: The border style. Available styles are "rounded" and "sharp".
Raises:
TypeError: If the arg of theme name is wrong.
Returns:
The stylesheet string for the given theme.
Examples:
Set stylesheet to your Qt application.
1. Dark Theme::
app = QApplication([])
app.setStyleSheet(qdarktheme.load_stylesheet())
# or
app.setStyleSheet(qdarktheme.load_stylesheet("dark"))
2. Light Theme::
app = QApplication([])
app.setStyleSheet(qdarktheme.load_stylesheet("light"))
Change sharp frame.
Sharp Frame::
app = QApplication([])
app.setStyleSheet(qdarktheme.load_stylesheet(border="sharp"))
"""
if theme not in get_themes():
raise TypeError("The argument [theme] can only be specified as 'dark' or 'light'.") from None
if border not in ("rounded", "sharp"):
raise TypeError("The argument [border] can only be specified as 'rounded' or 'sharp'.")
theme_resources_dir = _RESOURCES_BASE_DIR / theme
theme_resources_dir.mkdir(parents=True, exist_ok=True)
_build_svg_files(theme, theme_resources_dir)
if theme == "dark":
from libs.qdarktheme.themes.dark.stylesheet import STYLE_SHEET
else:
from libs.qdarktheme.themes.light.stylesheet import STYLE_SHEET
# Build stylesheet
# Radius
replacements_radius = _parse_radius(STYLE_SHEET, border)
stylesheet = multi_replace(STYLE_SHEET, replacements_radius)
# Env
replacements_env = _parse_env_patch(stylesheet)
# Path
replacements_env["${path}"] = _RESOURCES_BASE_DIR.as_posix()
return multi_replace(stylesheet, replacements_env)
def clear_cache():
"""Clear the caches in system home path.
PyQtDarkTheme build the caches of resources in the system home path.You can clear the caches by running this
method.
"""
try:
shutil.rmtree(_RESOURCE_HOME_DIR)
_logger.info(f"The caches({_RESOURCE_HOME_DIR}) has been deleted")
except FileNotFoundError:
_logger.info("There is no caches")
def load_palette(theme: str = "dark"):
"""Load the QPalette for the dark or light theme.
Args:
theme: The name of the theme. Available theme are "dark" and "light".
Raises:
TypeError: If the arg name of theme is wrong.
Returns:
QPalette: The QPalette for the given theme.
Examples:
Set QPalette to your Qt application.
1. Dark Theme::
app = QApplication([])
app.setPalette(qdarktheme.load_palette())
# or
app.setPalette(qdarktheme.load_palette("dark"))
2. Light Theme::
app = QApplication([])
app.setPalette(qdarktheme.load_palette("light"))
"""
if theme not in get_themes():
raise TypeError("The argument [theme] can only be specified as 'dark' or 'light'.") from None
if theme == "dark":
from libs.qdarktheme.themes.dark.palette import PALETTE
else:
from libs.qdarktheme.themes.light.palette import PALETTE
return PALETTE

View File

@@ -0,0 +1,19 @@
"""Module for QtCore."""
from libs.qdarktheme.qtpy.qt_compat import QT_API, qt_import_error
if QT_API is None:
raise qt_import_error
if QT_API == "PySide6":
from PySide6.QtCore import * # type: ignore # noqa: F403
elif QT_API == "PyQt6":
from PyQt6.QtCore import * # type: ignore # noqa: F403
Slot = pyqtSlot # noqa: F405
Signal = pyqtSignal # noqa: F405
elif QT_API == "PyQt5":
from PyQt5.QtCore import * # type: ignore # noqa: F403
Slot = pyqtSlot # noqa: F405
Signal = pyqtSignal # noqa: F405
elif QT_API == "PySide2":
from PySide2.QtCore import * # type: ignore # noqa: F403

View File

@@ -0,0 +1,20 @@
"""Module for QtGui."""
from libs.qdarktheme.qtpy.qt_compat import QT_API, qt_import_error
if QT_API is None:
raise qt_import_error
if QT_API == "PySide6":
from PySide6.QtGui import * # type: ignore # noqa: F403
elif QT_API == "PyQt6":
from PyQt6.QtGui import * # type: ignore # noqa: F403
elif QT_API == "PyQt5":
from PyQt5.QtGui import * # type: ignore # noqa: F403
from PyQt5.QtWidgets import QAction, QActionGroup, QShortcut # type: ignore
elif QT_API == "PySide2":
from PySide2.QtGui import * # type: ignore # noqa: F403
from PySide2.QtWidgets import QAction, QActionGroup, QShortcut # type: ignore
if QT_API in ["PyQt5", "PySide2"]:
QAction = QAction # type: ignore # noqa: SIM909
QActionGroup = QActionGroup # type: ignore # noqa: SIM909
QShortcut = QShortcut # type: ignore # noqa: SIM909

View File

@@ -0,0 +1,13 @@
"""Module for QtSvg."""
from libs.qdarktheme.qtpy.qt_compat import QT_API, qt_import_error
if QT_API is None:
raise qt_import_error
if QT_API == "PySide6":
from PySide6.QtSvg import * # type: ignore # noqa: F403
elif QT_API == "PyQt6":
from PyQt6.QtSvg import * # type: ignore # noqa: F403
elif QT_API == "PyQt5":
from PyQt5.QtSvg import * # type: ignore # noqa: F403
elif QT_API == "PySide2":
from PySide2.QtSvg import * # type: ignore # noqa: F403

View File

@@ -0,0 +1,50 @@
"""Module for QtWidgets."""
from __future__ import annotations
from collections.abc import Sequence
from libs.qdarktheme.qtpy.qt_compat import QT_API
from libs.qdarktheme.qtpy.QtCore import Qt
from libs.qdarktheme.qtpy.QtGui import QPalette
if QT_API == "PySide6":
from PySide6.QtWidgets import * # type: ignore # noqa: F403
elif QT_API == "PyQt6":
from PyQt6.QtWidgets import * # type: ignore # noqa: F403
elif QT_API == "PyQt5":
from PyQt5.QtWidgets import * # type: ignore # noqa: F403
elif QT_API == "PySide2":
from PySide2.QtWidgets import * # type: ignore # noqa: F403
class Application(QApplication): # type: ignore # noqa: F405
"""Override QApplication."""
def __init__(self, args: Sequence[str] | None = None) -> None:
"""Override QApplication method."""
super().__init__(args)
def exec(self) -> int:
"""Override QApplication method."""
if hasattr(super(), "exec"):
return super().exec()
return super().exec_()
def exit(self, returnCode: int = 0) -> None: # noqa: N803
"""Override QApplication method."""
return super().exit(returnCode)
def setStyleSheet(self, sheet: str) -> None: # noqa: N802
"""Override QApplication method."""
return super().setStyleSheet(sheet)
def setAttribute(self, attribute: Qt.ApplicationAttribute, on: bool = True) -> None: # noqa: N802
"""Override QApplication method."""
super().setAttribute(attribute, on)
def setPalette(self, palette: QPalette, className: str | None = None) -> None: # noqa: N802, N803
"""Override QApplication method."""
super().setPalette(palette, className)
QApplication = Application

View File

@@ -0,0 +1,11 @@
"""Package applying Qt compat of PyQt6, PySide6, PyQt5 and PySide2."""
from libs.qdarktheme.qtpy.qt_compat import QtImportError
from libs.qdarktheme.qtpy.qt_version import __version__
try:
from libs.qdarktheme.qtpy import QtCore, QtGui, QtSvg, QtWidgets
except ImportError:
from libs.qdarktheme.util import get_logger as __get_logger
__logger = __get_logger(__name__)
__logger.warning("Failed to import QtCore, QtGui, QtSvg and QtWidgets.")

View File

@@ -0,0 +1,82 @@
"""Module for Qt compat."""
from __future__ import annotations
import os
import sys
class QtImportError(ImportError):
"""Error raise if no bindings could be selected."""
pass
qt_import_error = QtImportError(
"Failed to import qt-binding. Check packages(pip list)."
"\n\tAvailable Qt-binding packages: PySide6, PyQt6, PyQt5, PySide2."
)
# Qt6
_QT_API_PYSIDE6 = "PySide6"
_QT_API_PYQT6 = "PyQt6"
# Qt5
_QT_API_PYQT5 = "PyQt5"
_QT_API_PYSIDE2 = "PySide2"
_API_LIST = [_QT_API_PYSIDE6, _QT_API_PYQT6, _QT_API_PYQT5, _QT_API_PYSIDE2]
def _get_loaded_api() -> str | None:
"""Return which API is loaded.
If this returns anything besides None,
importing any other Qt-binding is unsafe.
"""
for api in _API_LIST:
if sys.modules.get(f"{api}.QtCore"):
return api
return None
def _get_environ_api() -> str | None:
"""Return which API is specified in environ."""
_qt_api_env = os.environ.get("QT_API")
if _qt_api_env is not None:
_qt_api_env = _qt_api_env.lower()
_env_to_module = {
"pyside6": _QT_API_PYSIDE6,
"pyqt6": _QT_API_PYQT6,
"pyqt5": _QT_API_PYQT5,
"pyside2": _QT_API_PYSIDE2,
None: None,
}
try:
return _env_to_module[_qt_api_env]
except KeyError:
raise KeyError(
"The environment variable QT_API has the unrecognized value "
f"{_qt_api_env!r}. "
f"Valid values are {[k for k in _env_to_module if k is not None]}"
) from None
def _get_installed_api() -> str | None:
"""Return which API is installed."""
# Fix [AttributeError: module 'importlib' has no attribute 'util']
# See https://stackoverflow.com/a/39661116/13452582
from importlib import util
for api in _API_LIST:
if util.find_spec(api) is not None:
return api
return None
QT_API = _get_loaded_api()
if QT_API is None:
QT_API = _get_environ_api()
if QT_API is None:
QT_API = _get_installed_api()

View File

@@ -0,0 +1,18 @@
"""Module for detecting Qt version."""
from __future__ import annotations
from libs.qdarktheme.qtpy.qt_compat import QT_API
__version__: str | None = None
if QT_API == "PySide6":
from PySide6 import __version__ # type: ignore
elif QT_API == "PyQt6":
from PyQt6.QtCore import PYQT_VERSION_STR # type: ignore
__version__ = PYQT_VERSION_STR
elif QT_API == "PyQt5":
from PyQt5.QtCore import PYQT_VERSION_STR # type: ignore
__version__ = PYQT_VERSION_STR
elif QT_API == "PySide2":
from PySide2 import __version__ # type: ignore # noqa: F401

View File

@@ -0,0 +1,45 @@
"""Package including resources.
**Warning**
This package created programmatically. All changes made in this file will be lost!
Created by the `qdarktheme/tools/build_resources`.
License Information
===================
Material design icons
---------------------
All svg files in PyQtDarkTheme is from Material design icons(which uses an Apache 2.0 license).
- Author: Google
- Site: https://fonts.google.com/icons
- Source: https://github.com/google/material-design-icons
- License: Apache License Version 2.0 | https://www.apache.org/licenses/LICENSE-2.0.txt
Modifications made to each files to change the icon color and angle and remove svg namespace.
The current Material design icons license summary can be viewed at:
https://github.com/google/material-design-icons/blob/master/LICENSE
QDarkStyleSheet(Source code)
----------------------------
Qt stylesheets are originally fork of QDarkStyleSheet(MIT License).
- Author: Colin Duquesnoy
- Site: https://github.com/ColinDuquesnoy/QDarkStyleSheet
- Source: https://github.com/ColinDuquesnoy/QDarkStyleSheet
- License: MIT License | https://opensource.org/licenses/MIT
Modifications made to a file to change the style.
The current QDarkStyleSheet license summary can be viewed at:
https://github.com/ColinDuquesnoy/QDarkStyleSheet/blob/master/LICENSE.rst
"""
THEMES = ("dark", "light")

View File

@@ -0,0 +1 @@
"""Package containing the resources for dark theme."""

View File

@@ -0,0 +1,45 @@
"""Module loading QPalette."""
from libs.qdarktheme.qtpy.QtGui import QColor, QPalette
_palette = QPalette()
# base
_palette.setColor(QPalette.ColorRole.WindowText, QColor("#e4e7eb"))
_palette.setColor(QPalette.ColorRole.Button, QColor("#202124"))
_palette.setColor(QPalette.ColorRole.Text, QColor("#eff1f1"))
_palette.setColor(QPalette.ColorRole.ButtonText, QColor("#8ab4f7"))
_palette.setColor(QPalette.ColorRole.Base, QColor("#202124"))
_palette.setColor(QPalette.ColorRole.Window, QColor("#202124"))
_palette.setColor(QPalette.ColorRole.Highlight, QColor("#8ab4f7"))
_palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#202124"))
_palette.setColor(QPalette.ColorRole.Link, QColor("#202124"))
_palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#292b2e"))
_palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#292a2d"))
_palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#e4e7eb"))
_palette.setColor(QPalette.ColorRole.LinkVisited, QColor("#c58af8"))
_palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#292a2d"))
_palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#e4e7eb"))
if hasattr(QPalette.ColorRole, "Foreground"):
_palette.setColor(QPalette.ColorRole.Foreground, QColor("#e4e7eb")) # type: ignore
if hasattr(QPalette.ColorRole, "PlaceholderText"):
_palette.setColor(QPalette.ColorRole.PlaceholderText, QColor("#8a8b8d"))
_palette.setColor(QPalette.ColorRole.Light, QColor("#3f4042"))
_palette.setColor(QPalette.ColorRole.Midlight, QColor("#3f4042"))
_palette.setColor(QPalette.ColorRole.Dark, QColor("#e4e7eb"))
_palette.setColor(QPalette.ColorRole.Mid, QColor("#3f4042"))
_palette.setColor(QPalette.ColorRole.Shadow, QColor("#3f4042"))
# disabled
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, QColor("#697177"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, QColor("#697177"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, QColor("#3f4042"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Highlight, QColor("#53575b"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.HighlightedText, QColor("#697177"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Link, QColor("#697177"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.LinkVisited, QColor("#697177"))
# inactive
_palette.setColor(QPalette.ColorGroup.Inactive, QPalette.ColorRole.Highlight, QColor("#393d41"))
PALETTE = _palette

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
"""Package containing the resources for light theme."""

View File

@@ -0,0 +1,45 @@
"""Module loading QPalette."""
from libs.qdarktheme.qtpy.QtGui import QColor, QPalette
_palette = QPalette()
# base
_palette.setColor(QPalette.ColorRole.WindowText, QColor("#4d5157"))
_palette.setColor(QPalette.ColorRole.Button, QColor("#f8f9fa"))
_palette.setColor(QPalette.ColorRole.Text, QColor("#4d5157"))
_palette.setColor(QPalette.ColorRole.ButtonText, QColor("#0081db"))
_palette.setColor(QPalette.ColorRole.Base, QColor("#f8f9fa"))
_palette.setColor(QPalette.ColorRole.Window, QColor("#f8f9fa"))
_palette.setColor(QPalette.ColorRole.Highlight, QColor("#0081db"))
_palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#f8f9fa"))
_palette.setColor(QPalette.ColorRole.Link, QColor("#f8f9fa"))
_palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#e9ecef"))
_palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#ffffff"))
_palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#4d5157"))
_palette.setColor(QPalette.ColorRole.LinkVisited, QColor("#660098"))
_palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#ffffff"))
_palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#4d5157"))
if hasattr(QPalette.ColorRole, "Foreground"):
_palette.setColor(QPalette.ColorRole.Foreground, QColor("#4d5157")) # type: ignore
if hasattr(QPalette.ColorRole, "PlaceholderText"):
_palette.setColor(QPalette.ColorRole.PlaceholderText, QColor("#696a6c"))
_palette.setColor(QPalette.ColorRole.Light, QColor("#dadce0"))
_palette.setColor(QPalette.ColorRole.Midlight, QColor("#dadce0"))
_palette.setColor(QPalette.ColorRole.Dark, QColor("#4d5157"))
_palette.setColor(QPalette.ColorRole.Mid, QColor("#dadce0"))
_palette.setColor(QPalette.ColorRole.Shadow, QColor("#dadce0"))
# disabled
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, QColor("#babdc2"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, QColor("#babdc2"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, QColor("#dadce0"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Highlight, QColor("#dadce0"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.HighlightedText, QColor("#babdc2"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Link, QColor("#babdc2"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.LinkVisited, QColor("#babdc2"))
# inactive
_palette.setColor(QPalette.ColorGroup.Inactive, QPalette.ColorRole.Highlight, QColor("#e4e6f2"))
PALETTE = _palette

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

67
libs/qdarktheme/util.py Normal file
View File

@@ -0,0 +1,67 @@
"""Utility methods for qdarktheme."""
from __future__ import annotations
import inspect
import logging
import operator as ope
import re
from pathlib import Path
import libs.qdarktheme
# greater_equal and less_equal must be evaluated before greater and less.
OPERATORS = {"==": ope.eq, "!=": ope.ne, ">=": ope.ge, "<=": ope.le, ">": ope.gt, "<": ope.lt}
def multi_replace(target: str, replacements: dict[str, str]) -> str:
"""Given a string and a replacement map, it returns the replaced string.
See https://gist.github.com/bgusach/a967e0587d6e01e889fd1d776c5f3729.
Args:
target: String to execute replacements on.
replacements: Replacement dictionary {value to find: value to replace}.
Returns:
str: Target string that replaced with `replacements`.
"""
if len(replacements) == 0:
return target
replacements_sorted = sorted(replacements, key=len, reverse=True)
replacements_escaped = [re.escape(i) for i in replacements_sorted]
pattern = re.compile("|".join(replacements_escaped))
return pattern.sub(lambda match: replacements[match.group()], target)
def get_logger(logger_name: str) -> logging.Logger:
"""Return the logger with the name specified by logger_name arg.
Args:
logger_name: The name of logger.
Returns:
Logger reformatted for this package.
"""
logger = logging.getLogger(logger_name)
logger.propagate = False
logger.setLevel(logging.INFO)
ch = logging.StreamHandler()
ch.setFormatter(logging.Formatter("[%(name)s] [%(levelname)s] %(message)s"))
logger.addHandler(ch)
return logger
def get_qdarktheme_root_path() -> Path:
"""Return the qdarktheme package root path.
Returns:
qdarktheme package root path.
"""
return Path(inspect.getfile(libs.qdarktheme)).parent
def compare_v(v1: str, operator: str, v2: str) -> bool:
"""Comparing two versions."""
v1_list, v2_list = (tuple(map(int, (v.split(".")))) for v in (v1, v2))
return OPERATORS[operator](v1_list, v2_list)

View File

@@ -0,0 +1,8 @@
"""Example of PyQtDarkTheme use for Qt applications.
To check example app, run:
```shell
python -m qdarktheme.widget_gallery
```
"""

View File

@@ -0,0 +1,17 @@
"""Module allowing for `python -m qdarktheme.widget_gallery`."""
import sys
import libs.qdarktheme
from libs.qdarktheme.qtpy.QtCore import Qt
from libs.qdarktheme.qtpy.QtWidgets import QApplication
from libs.qdarktheme.widget_gallery.mainwindow import WidgetGallery
if __name__ == "__main__":
app = QApplication(sys.argv)
if hasattr(Qt.ApplicationAttribute, "AA_UseHighDpiPixmaps"): # Enable High DPI display with Qt5
app.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps) # type: ignore
win = WidgetGallery()
win.menuBar().setNativeMenuBar(False)
app.setStyleSheet(libs.qdarktheme.load_stylesheet())
win.show()
app.exec()

View File

@@ -0,0 +1,204 @@
"""Main module of widget gallery."""
import libs.qdarktheme
from libs.qdarktheme.qtpy.QtCore import QDir, Qt, Slot
from libs.qdarktheme.qtpy.QtGui import QAction, QActionGroup, QFont, QIcon
from libs.qdarktheme.qtpy.QtWidgets import (
QApplication,
QColorDialog,
QFileDialog,
QFontDialog,
QLabel,
QMainWindow,
QMenuBar,
QMessageBox,
QSizePolicy,
QStackedWidget,
QStatusBar,
QToolBar,
QToolButton,
QWidget,
)
from libs.qdarktheme.util import get_qdarktheme_root_path
from libs.qdarktheme.widget_gallery.ui.dock_ui import DockUI
from libs.qdarktheme.widget_gallery.ui.frame_ui import FrameUI
from libs.qdarktheme.widget_gallery.ui.widgets_ui import WidgetsUI
class _WidgetGalleryUI:
def setup_ui(self, main_win: QMainWindow) -> None:
# Actions
self.action_open_folder = QAction(QIcon("icons:folder_open_24dp.svg"), "Open folder dialog")
self.action_open_color_dialog = QAction(QIcon("icons:palette_24dp.svg"), "Open color dialog")
self.action_open_font_dialog = QAction(QIcon("icons:font_download_24dp.svg"), "Open font dialog")
self.action_enable = QAction(QIcon("icons:circle_24dp.svg"), "Enable")
self.action_disable = QAction(QIcon("icons:clear_24dp.svg"), "Disable")
self.actions_theme = [QAction(theme, main_win) for theme in ["dark", "light"]]
self.actions_page = (
QAction(QIcon("icons:widgets_24dp.svg"), "Move to widgets"),
QAction(QIcon("icons:flip_to_front_24dp.svg"), "Move to dock"),
QAction(QIcon("icons:crop_din_24dp.svg"), "Move to frame"),
)
self.actions_message_box = (
QAction(text="Open question dialog"),
QAction(text="Open information dialog"),
QAction(text="Open warning dialog"),
QAction(text="Open critical dialog"),
)
self.actions_corner_radius = (QAction(text="rounded"), QAction(text="sharp"))
action_group_toolbar = QActionGroup(main_win)
# Widgets
self.central_window = QMainWindow()
self.stack_widget = QStackedWidget()
self.toolbar = QToolBar("Toolbar")
activitybar = QToolBar("activitybar")
statusbar = QStatusBar()
menubar = QMenuBar()
tool_btn_settings, tool_btn_theme, tool_btn_enable, tool_btn_disable, tool_btn_message_box = (
QToolButton() for _ in range(5)
)
spacer = QToolButton()
# Setup Actions
for action in self.actions_page:
action.setCheckable(True)
action_group_toolbar.addAction(action)
self.actions_page[0].setChecked(True)
# Setup Widgets
spacer.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
spacer.setEnabled(False)
activitybar.setMovable(False)
activitybar.addActions(self.actions_page)
activitybar.addWidget(spacer)
activitybar.addWidget(tool_btn_settings)
tool_btn_settings.setIcon(QIcon("icons:settings_24dp.svg"))
tool_btn_settings.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
tool_btn_enable.setDefaultAction(self.action_enable)
tool_btn_disable.setDefaultAction(self.action_disable)
tool_btn_message_box.setIcon(QIcon("icons:announcement_24dp.svg"))
tool_btn_message_box.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
tool_btn_theme.setIcon(QIcon("icons:contrast_24dp.svg"))
tool_btn_theme.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
self.toolbar.addActions((self.action_open_folder, self.action_open_color_dialog, self.action_open_font_dialog))
self.toolbar.addSeparator()
self.toolbar.addWidget(QLabel("Popup"))
self.toolbar.addWidget(tool_btn_message_box)
self.toolbar.addWidget(tool_btn_theme)
statusbar.addPermanentWidget(tool_btn_enable)
statusbar.addPermanentWidget(tool_btn_disable)
statusbar.showMessage("Enable")
menu_toggle = menubar.addMenu("&Toggle")
menu_toggle.addActions((self.action_enable, self.action_disable))
menu_theme = menubar.addMenu("&Theme")
menu_theme.addActions(self.actions_theme)
menu_dialog = menubar.addMenu("&Dialog")
menu_option = menubar.addMenu("&Option")
menu_option.addActions(self.actions_corner_radius)
menu_dialog.addActions((self.action_open_folder, self.action_open_color_dialog, self.action_open_font_dialog))
menu_message_box = menu_dialog.addMenu("&Messages")
menu_message_box.addActions(self.actions_message_box)
tool_btn_settings.setMenu(menu_toggle)
tool_btn_theme.setMenu(menu_theme)
tool_btn_message_box.setMenu(menu_message_box)
self.action_enable.setEnabled(False)
# Layout
for ui in (WidgetsUI, DockUI, FrameUI):
container = QWidget()
ui().setup_ui(container)
self.stack_widget.addWidget(container)
self.central_window.setCentralWidget(self.stack_widget)
self.central_window.addToolBar(self.toolbar)
main_win.setCentralWidget(self.central_window)
main_win.addToolBar(Qt.ToolBarArea.LeftToolBarArea, activitybar)
main_win.setMenuBar(menubar)
main_win.setStatusBar(statusbar)
class WidgetGallery(QMainWindow):
"""The main window class of example app."""
def __init__(self) -> None:
"""Initialize the WidgetGallery class."""
super().__init__()
QDir.addSearchPath("icons", f"{get_qdarktheme_root_path().as_posix()}/widget_gallery/svg")
self._ui = _WidgetGalleryUI()
self._ui.setup_ui(self)
self._theme = "dark"
self._border_radius = "rounded"
# Signal
self._ui.action_open_folder.triggered.connect(
lambda: QFileDialog.getOpenFileName(self, "Open File", options=QFileDialog.Option.DontUseNativeDialog)
)
self._ui.action_open_color_dialog.triggered.connect(
lambda: QColorDialog.getColor(parent=self, options=QColorDialog.ColorDialogOption.DontUseNativeDialog)
)
self._ui.action_open_font_dialog.triggered.connect(
lambda: QFontDialog.getFont(QFont(), parent=self, options=QFontDialog.FontDialogOption.DontUseNativeDialog)
)
self._ui.action_enable.triggered.connect(self._toggle_state)
self._ui.action_disable.triggered.connect(self._toggle_state)
for action in self._ui.actions_theme:
action.triggered.connect(self._change_theme)
for action in self._ui.actions_page:
action.triggered.connect(self._change_page)
for action in self._ui.actions_message_box:
action.triggered.connect(self._popup_message_box)
for action in self._ui.actions_corner_radius:
action.triggered.connect(self._change_corner_radius)
@Slot()
def _change_page(self) -> None:
action_name: str = self.sender().text() # type: ignore
if "widgets" in action_name:
index = 0
elif "dock" in action_name:
index = 1
else:
index = 2
self._ui.stack_widget.setCurrentIndex(index)
@Slot()
def _toggle_state(self) -> None:
state: str = self.sender().text() # type: ignore
self._ui.central_window.centralWidget().setEnabled(state == "Enable")
self._ui.toolbar.setEnabled(state == "Enable")
self._ui.action_enable.setEnabled(state == "Disable")
self._ui.action_disable.setEnabled(state == "Enable")
self.statusBar().showMessage(state)
@Slot()
def _change_theme(self) -> None:
self._theme = self.sender().text() # type: ignore
QApplication.instance().setStyleSheet(libs.qdarktheme.load_stylesheet(self._theme, self._border_radius))
@Slot()
def _change_corner_radius(self) -> None:
self._border_radius: str = self.sender().text() # type: ignore
QApplication.instance().setStyleSheet(libs.qdarktheme.load_stylesheet(self._theme, self._border_radius))
@Slot()
def _popup_message_box(self) -> None:
action_name: str = self.sender().text() # type: ignore
if "question" in action_name:
QMessageBox.question(self, "Question", "Question")
elif "information" in action_name:
QMessageBox.information(self, "Information", "Information")
elif "warning" in action_name:
QMessageBox.warning(self, "Warning", "Warning")
elif "critical" in action_name:
QMessageBox.critical(self, "Critical", "Critical")

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8 9c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1zm1 4h-2v-2h2v2z"/></svg>

After

Width:  |  Height:  |  Size: 308 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#008000"><g><rect fill="none" height="24" width="24"/></g><g><path d="M12,2C6.47,2,2,6.47,2,12c0,5.53,4.47,10,10,10s10-4.47,10-10C22,6.47,17.53,2,12,2z M12,20c-4.42,0-8-3.58-8-8 c0-4.42,3.58-8,8-8s8,3.58,8,8C20,16.42,16.42,20,12,20z"/></g></svg>

After

Width:  |  Height:  |  Size: 372 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FF0000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M18.3 5.71c-.39-.39-1.02-.39-1.41 0L12 10.59 7.11 5.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41L10.59 12 5.7 16.89c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L12 13.41l4.89 4.89c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L13.41 12l4.89-4.89c.38-.38.38-1.02 0-1.4z"/></svg>

After

Width:  |  Height:  |  Size: 430 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><g><rect fill="none" height="24" width="24"/></g><g><path d="M12,22c5.52,0,10-4.48,10-10S17.52,2,12,2S2,6.48,2,12S6.48,22,12,22z M13,4.07c3.94,0.49,7,3.85,7,7.93s-3.05,7.44-7,7.93 V4.07z"/></g></svg>

After

Width:  |  Height:  |  Size: 335 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"/></svg>

After

Width:  |  Height:  |  Size: 249 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#ff1493"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19.66 3.99c-2.64-1.8-5.9-.96-7.66 1.1-1.76-2.06-5.02-2.91-7.66-1.1-1.4.96-2.28 2.58-2.34 4.29-.14 3.88 3.3 6.99 8.55 11.76l.1.09c.76.69 1.93.69 2.69-.01l.11-.1c5.25-4.76 8.68-7.87 8.55-11.75-.06-1.7-.94-3.32-2.34-4.28zM12.1 18.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z"/></svg>

After

Width:  |  Height:  |  Size: 548 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm2 4v-2H3c0 1.1.89 2 2 2zM3 9h2V7H3v2zm12 12h2v-2h-2v2zm4-18H9c-1.11 0-2 .9-2 2v10c0 1.1.89 2 2 2h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 12h-8c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h8c.55 0 1 .45 1 1v8c0 .55-.45 1-1 1zm-7 6h2v-2h-2v2zm-4 0h2v-2H7v2z"/></svg>

After

Width:  |  Height:  |  Size: 432 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M20 6h-8l-1.41-1.41C10.21 4.21 9.7 4 9.17 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-1 12H5c-.55 0-1-.45-1-1V9c0-.55.45-1 1-1h14c.55 0 1 .45 1 1v8c0 .55-.45 1-1 1z"/></svg>

After

Width:  |  Height:  |  Size: 360 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M9.93 13.5h4.14L12 7.98 9.93 13.5zM20 2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-4.29 15.88l-.9-2.38H9.17l-.89 2.37c-.14.38-.5.63-.91.63-.68 0-1.15-.69-.9-1.32l4.25-10.81c.22-.53.72-.87 1.28-.87s1.06.34 1.27.87l4.25 10.81c.25.63-.22 1.32-.9 1.32-.4 0-.76-.25-.91-.62z"/></svg>

After

Width:  |  Height:  |  Size: 457 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><g><rect fill="none" height="24" width="24"/></g><g><path d="M12,2C6.49,2,2,6.49,2,12s4.49,10,10,10c1.38,0,2.5-1.12,2.5-2.5c0-0.61-0.23-1.2-0.64-1.67c-0.08-0.1-0.13-0.21-0.13-0.33 c0-0.28,0.22-0.5,0.5-0.5H16c3.31,0,6-2.69,6-6C22,6.04,17.51,2,12,2z M17.5,13c-0.83,0-1.5-0.67-1.5-1.5c0-0.83,0.67-1.5,1.5-1.5 s1.5,0.67,1.5,1.5C19,12.33,18.33,13,17.5,13z M14.5,9C13.67,9,13,8.33,13,7.5C13,6.67,13.67,6,14.5,6S16,6.67,16,7.5 C16,8.33,15.33,9,14.5,9z M5,11.5C5,10.67,5.67,10,6.5,10S8,10.67,8,11.5C8,12.33,7.33,13,6.5,13S5,12.33,5,11.5z M11,7.5 C11,8.33,10.33,9,9.5,9S8,8.33,8,7.5C8,6.67,8.67,6,9.5,6S11,6.67,11,7.5z"/></g></svg>

After

Width:  |  Height:  |  Size: 758 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><rect fill="none" height="24" width="24"/><path d="M19.5,12c0-0.23-0.01-0.45-0.03-0.68l1.86-1.41c0.4-0.3,0.51-0.86,0.26-1.3l-1.87-3.23c-0.25-0.44-0.79-0.62-1.25-0.42 l-2.15,0.91c-0.37-0.26-0.76-0.49-1.17-0.68l-0.29-2.31C14.8,2.38,14.37,2,13.87,2h-3.73C9.63,2,9.2,2.38,9.14,2.88L8.85,5.19 c-0.41,0.19-0.8,0.42-1.17,0.68L5.53,4.96c-0.46-0.2-1-0.02-1.25,0.42L2.41,8.62c-0.25,0.44-0.14,0.99,0.26,1.3l1.86,1.41 C4.51,11.55,4.5,11.77,4.5,12s0.01,0.45,0.03,0.68l-1.86,1.41c-0.4,0.3-0.51,0.86-0.26,1.3l1.87,3.23c0.25,0.44,0.79,0.62,1.25,0.42 l2.15-0.91c0.37,0.26,0.76,0.49,1.17,0.68l0.29,2.31C9.2,21.62,9.63,22,10.13,22h3.73c0.5,0,0.93-0.38,0.99-0.88l0.29-2.31 c0.41-0.19,0.8-0.42,1.17-0.68l2.15,0.91c0.46,0.2,1,0.02,1.25-0.42l1.87-3.23c0.25-0.44,0.14-0.99-0.26-1.3l-1.86-1.41 C19.49,12.45,19.5,12.23,19.5,12z M12.04,15.5c-1.93,0-3.5-1.57-3.5-3.5s1.57-3.5,3.5-3.5s3.5,1.57,3.5,3.5S13.97,15.5,12.04,15.5z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><path d="M0 0h24v24H0z" fill="none"/><path d="M13 13v8h8v-8h-8zM3 21h8v-8H3v8zM3 3v8h8V3H3zm13.66-1.31L11 7.34 16.66 13l5.66-5.66-5.66-5.65z"/></svg>

After

Width:  |  Height:  |  Size: 251 B

View File

@@ -0,0 +1 @@
"""Package including ui for WidgetGallery."""

View File

@@ -0,0 +1,40 @@
"""Module setting up ui of dock window."""
from libs.qdarktheme.qtpy.QtCore import Qt
from libs.qdarktheme.qtpy.QtWidgets import QDockWidget, QMainWindow, QTextEdit, QVBoxLayout, QWidget
class DockUI:
"""The ui class of dock window."""
def setup_ui(self, win: QWidget) -> None:
"""Set up ui."""
# Widgets
left_dock = QDockWidget("Left dock")
right_dock = QDockWidget("Right dock")
top_dock = QDockWidget("Top dock")
bottom_dock = QDockWidget("Bottom dock")
# Setup widgets
left_dock.setWidget(QTextEdit("This is the left widget."))
right_dock.setWidget(QTextEdit("This is the right widget."))
top_dock.setWidget(QTextEdit("This is the top widget."))
bottom_dock.setWidget(QTextEdit("This is the bottom widget."))
for dock in (left_dock, right_dock, top_dock, bottom_dock):
dock.setAllowedAreas(
Qt.DockWidgetArea.LeftDockWidgetArea
| Qt.DockWidgetArea.RightDockWidgetArea
| Qt.DockWidgetArea.BottomDockWidgetArea
| Qt.DockWidgetArea.TopDockWidgetArea
)
# Layout
main_win = QMainWindow()
main_win.setCentralWidget(QTextEdit("This is the central widget."))
main_win.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, left_dock)
main_win.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, right_dock)
main_win.addDockWidget(Qt.DockWidgetArea.TopDockWidgetArea, top_dock)
main_win.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, bottom_dock)
layout = QVBoxLayout(win)
layout.addWidget(main_win)
layout.setContentsMargins(0, 0, 0, 0)

View File

@@ -0,0 +1,75 @@
"""Module setting up ui of frame window."""
from libs.qdarktheme.qtpy.QtGui import QIcon
from libs.qdarktheme.qtpy.QtWidgets import (
QCalendarWidget,
QCheckBox,
QFrame,
QGridLayout,
QGroupBox,
QHBoxLayout,
QPushButton,
QRadioButton,
QScrollArea,
QSpinBox,
QToolButton,
QVBoxLayout,
QWidget,
)
class FrameUI:
"""The ui class of frame window."""
def setup_ui(self, win: QWidget) -> None:
"""Set up ui."""
# Widgets
group_box = QGroupBox("frameShape = Box")
group_panel = QGroupBox("frameShape = Panel")
group_none = QGroupBox("frameShape = NoFrame")
group_line = QGroupBox("frameShape = VLine HLine")
frame_box, frame_panel, frame_none, frame_v_line, frame_h_line = (QFrame() for _ in range(5))
# Setup widgets
frame_box.setFrameShape(QFrame.Shape.Box)
frame_panel.setFrameShape(QFrame.Shape.Panel)
frame_none.setFrameShape(QFrame.Shape.NoFrame)
frame_v_line.setFrameShape(QFrame.Shape.VLine)
frame_h_line.setFrameShape(QFrame.Shape.HLine)
# Layout
for frame in (frame_box, frame_panel, frame_none):
push_btn_flat = QPushButton("Push button(flat)")
push_btn_flat.setFlat(True)
tool_btn = QToolButton()
tool_btn.setIcon(QIcon("icons:favorite_border_24dp.svg"))
calender = QCalendarWidget()
g_layout = QGridLayout(frame)
g_layout.addWidget(QPushButton("Push button"), 0, 0)
g_layout.addWidget(push_btn_flat, 0, 1)
g_layout.addWidget(QSpinBox(), 1, 0)
g_layout.addWidget(tool_btn, 1, 1)
g_layout.addWidget(QRadioButton("Radio button"), 2, 0)
g_layout.addWidget(QCheckBox("Check box"), 2, 1)
g_layout.addWidget(calender, 3, 0, 1, 2)
for group, frame in ((group_box, frame_box), (group_panel, frame_panel), (group_none, frame_none)):
v_layout = QVBoxLayout(group)
v_layout.addWidget(frame)
h_layout = QHBoxLayout(group_line)
h_layout.addWidget(frame_v_line)
h_layout.addWidget(frame_h_line)
widget_container = QWidget()
g_layout = QGridLayout(widget_container)
g_layout.addWidget(group_box, 0, 0)
g_layout.addWidget(group_panel, 0, 1)
g_layout.addWidget(group_none, 1, 0)
g_layout.addWidget(group_line, 1, 1)
scroll_area = QScrollArea()
scroll_area.setWidget(widget_container)
v_main_layout = QVBoxLayout(win)
v_main_layout.addWidget(scroll_area)

View File

@@ -0,0 +1,293 @@
"""Module setting up ui of widgets window."""
from __future__ import annotations
from typing import Any
from libs.qdarktheme.qtpy.QtCore import QAbstractTableModel, QModelIndex, Qt
from libs.qdarktheme.qtpy.QtGui import QIcon, QTextOption
from libs.qdarktheme.qtpy.QtWidgets import (
QCheckBox,
QComboBox,
QDateTimeEdit,
QDial,
QGridLayout,
QGroupBox,
QLabel,
QLCDNumber,
QLineEdit,
QListWidget,
QProgressBar,
QPushButton,
QRadioButton,
QScrollArea,
QSlider,
QSpinBox,
QSplitter,
QTableView,
QTabWidget,
QTextEdit,
QToolBox,
QToolButton,
QTreeWidget,
QTreeWidgetItem,
QVBoxLayout,
QWidget,
)
class _Group1(QGroupBox):
def __init__(self) -> None:
super().__init__("Buttons")
# Widgets
group_push = QGroupBox("Push Button")
group_tool = QGroupBox("Tool Button")
group_radio = QGroupBox("Radio Button")
group_checkbox = QGroupBox("Check Box")
push_btn, push_btn_toggled = QPushButton("NORMAL"), QPushButton("TOGGLED")
push_btn_flat, push_btn_flat_toggled = QPushButton("NORMAL"), QPushButton("TOGGLED")
tool_btn, tool_btn_toggled, tool_btn_text = QToolButton(), QToolButton(), QToolButton()
radio_btn_1, radio_btn_2 = QRadioButton("Normal 1"), QRadioButton("Normal 2")
checkbox, checkbox_tristate = QCheckBox("Normal"), QCheckBox("Tristate")
# Setup widgets
self.setCheckable(True)
push_btn_flat.setFlat(True)
push_btn_flat_toggled.setFlat(True)
for btn in (push_btn_toggled, push_btn_flat_toggled):
btn.setCheckable(True)
btn.setChecked(True)
tool_btn.setIcon(QIcon("icons:favorite_border_24dp.svg"))
tool_btn_toggled.setIcon(QIcon("icons:favorite_border_24dp.svg"))
tool_btn_text.setIcon(QIcon("icons:favorite_border_24dp.svg"))
tool_btn_text.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
tool_btn_text.setText("Text")
tool_btn_toggled.setCheckable(True)
tool_btn_toggled.setChecked(True)
radio_btn_1.setChecked(True)
checkbox.setChecked(True)
checkbox_tristate.setTristate(True)
checkbox_tristate.setCheckState(Qt.CheckState.PartiallyChecked)
# Layout
g_layout_push = QGridLayout()
g_layout_push.addWidget(QLabel("Normal"), 0, 0)
g_layout_push.addWidget(push_btn, 1, 0)
g_layout_push.addWidget(push_btn_toggled, 2, 0)
g_layout_push.addWidget(QLabel("Flat"), 0, 1)
g_layout_push.addWidget(push_btn_flat, 1, 1)
g_layout_push.addWidget(push_btn_flat_toggled, 2, 1)
group_push.setLayout(g_layout_push)
v_layout_tool = QVBoxLayout()
v_layout_tool.addWidget(tool_btn)
v_layout_tool.addWidget(tool_btn_toggled)
v_layout_tool.addWidget(tool_btn_text)
group_tool.setLayout(v_layout_tool)
v_layout_radio = QVBoxLayout()
v_layout_radio.addWidget(radio_btn_1)
v_layout_radio.addWidget(radio_btn_2)
group_radio.setLayout(v_layout_radio)
v_layout_checkbox = QVBoxLayout()
v_layout_checkbox.addWidget(checkbox)
v_layout_checkbox.addWidget(checkbox_tristate)
group_checkbox.setLayout(v_layout_checkbox)
g_layout_main = QGridLayout(self)
g_layout_main.addWidget(group_push, 0, 0)
g_layout_main.addWidget(group_tool, 0, 1)
g_layout_main.addWidget(group_radio, 1, 0)
g_layout_main.addWidget(group_checkbox, 1, 1)
class _Group2(QGroupBox):
def __init__(self) -> None:
super().__init__("Line boxes")
# Widgets
group_spinbox = QGroupBox("Spinbox")
group_combobox = QGroupBox("Combobox")
group_editable = QGroupBox("Line edit")
group_date = QGroupBox("Date time edit")
spinbox, spinbox_suffix = QSpinBox(), QSpinBox()
combobox, combobox_line_edit = QComboBox(), QComboBox()
lineedit = QLineEdit()
date_time_edit, date_time_edit_calendar = QDateTimeEdit(), QDateTimeEdit()
# Setup widgets
self.setCheckable(True)
spinbox_suffix.setSuffix(" m")
combobox.addItems(("Item 1", "Item 2", "Item 3"))
combobox_line_edit.addItems(("Item 1", "Item 2", "Item 3"))
combobox_line_edit.setEditable(True)
lineedit.setPlaceholderText("Placeholder text")
date_time_edit_calendar.setCalendarPopup(True)
# Layout
v_layout_spin = QVBoxLayout()
v_layout_spin.addWidget(spinbox)
v_layout_spin.addWidget(spinbox_suffix)
group_spinbox.setLayout(v_layout_spin)
v_layout_combo = QVBoxLayout()
v_layout_combo.addWidget(combobox)
v_layout_combo.addWidget(combobox_line_edit)
group_combobox.setLayout(v_layout_combo)
v_layout_lineedit = QVBoxLayout()
v_layout_lineedit.addWidget(lineedit)
group_editable.setLayout(v_layout_lineedit)
v_layout_date = QVBoxLayout()
v_layout_date.addWidget(date_time_edit)
v_layout_date.addWidget(date_time_edit_calendar)
group_date.setLayout(v_layout_date)
g_layout_main = QGridLayout(self)
g_layout_main.addWidget(group_spinbox, 0, 0)
g_layout_main.addWidget(group_combobox, 0, 1)
g_layout_main.addWidget(group_editable, 1, 0)
g_layout_main.addWidget(group_date, 1, 1)
class _TableModel(QAbstractTableModel):
def __init__(self) -> None:
super().__init__()
self._data = [[i * 10 + j for j in range(4)] for i in range(5)]
def data(self, index: QModelIndex, role: int) -> Any:
if role == Qt.ItemDataRole.DisplayRole:
return self._data[index.row()][index.column()]
if role == Qt.ItemDataRole.CheckStateRole and index.column() == 1:
return Qt.CheckState.Checked if index.row() % 2 == 0 else Qt.CheckState.Unchecked
if role == Qt.ItemDataRole.EditRole and index.column() == 2:
return self._data[index.row()][index.column()] # pragma: no cover
return None
def rowCount(self, index) -> int: # noqa: N802
return len(self._data)
def columnCount(self, index) -> int: # noqa: N802
return len(self._data[0])
def flags(self, index: QModelIndex) -> Qt.ItemFlag:
flag = super().flags(index)
if index.column() == 1:
flag |= Qt.ItemFlag.ItemIsUserCheckable
elif index.column() in (2, 3):
flag |= Qt.ItemFlag.ItemIsEditable
return flag # type: ignore
def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...) -> Any: # noqa: N802
if role != Qt.ItemDataRole.DisplayRole:
return None
if orientation == Qt.Orientation.Horizontal:
return ["Normal", "Checkbox", "Spinbox", "LineEdit"][section]
return super().headerData(section, orientation, role)
class _Group3(QGroupBox):
def __init__(self) -> None:
super().__init__("Scroll area and QTabWidget (QGroupBox.flat = True)")
# Widgets
tab_widget = QTabWidget()
tab_text_edit = QTextEdit()
tab_table = QTableView()
tab_list = QListWidget()
tab_tree = QTreeWidget()
# Setup widgets
self.setCheckable(True)
self.setFlat(True)
tab_widget.setTabsClosable(True)
tab_widget.setMovable(True)
tab_text_edit.append("<b>PyQtDarkTheme</b>")
tab_text_edit.append("Dark theme for PySide and PyQt.")
tab_text_edit.append("This project is licensed under the MIT license.")
tab_text_edit.setWordWrapMode(QTextOption.WrapMode.NoWrap)
tab_table.setModel(_TableModel())
tab_table.setSortingEnabled(True)
tab_list.addItems([f"Item {i+1}" for i in range(30)])
tab_list.setAlternatingRowColors(True)
tab_tree.setColumnCount(2)
for i in range(5):
item = QTreeWidgetItem([f"Item {i+1}" for _ in range(2)])
for j in range(2):
item.addChild(QTreeWidgetItem([f"Child Item {i+1}_{j+1}" for _ in range(2)]))
tab_tree.insertTopLevelItem(i, item)
# layout
tab_widget.addTab(tab_table, "Table")
tab_widget.addTab(tab_text_edit, "Text Edit")
tab_widget.addTab(tab_list, "List")
tab_widget.addTab(tab_tree, "Tree")
v_layout_main = QVBoxLayout(self)
v_layout_main.addWidget(tab_widget)
class _Group4(QGroupBox):
def __init__(self) -> None:
super().__init__("QToolBox")
# Widgets
toolbox = QToolBox()
slider = QSlider(Qt.Orientation.Horizontal)
dial_ticks = QDial()
progressbar = QProgressBar()
lcd_number = QLCDNumber()
# Setup widgets
self.setCheckable(True)
toolbox.addItem(slider, "Slider")
toolbox.addItem(dial_ticks, "Dial")
toolbox.addItem(progressbar, "Progress Bar")
toolbox.addItem(lcd_number, "LCD Number")
slider.setValue(50)
dial_ticks.setNotchesVisible(True)
progressbar.setValue(50)
lcd_number.setSegmentStyle(QLCDNumber.SegmentStyle.Flat)
lcd_number.display(123)
# Layout
v_layout = QVBoxLayout(self)
v_layout.addWidget(toolbox)
class WidgetsUI:
"""The ui class of widgets window."""
def setup_ui(self, win: QWidget) -> None:
"""Set up ui."""
# Widgets
h_splitter_1, h_splitter_2 = QSplitter(Qt.Orientation.Horizontal), QSplitter(Qt.Orientation.Horizontal)
# Setup widgets
h_splitter_1.setMinimumHeight(350) # Fix bug layout crush
# Layout
h_splitter_1.addWidget(_Group1())
h_splitter_1.addWidget(_Group2())
h_splitter_2.addWidget(_Group3())
h_splitter_2.addWidget(_Group4())
widget_container = QWidget()
v_layout = QVBoxLayout(widget_container)
v_layout.addWidget(h_splitter_1)
v_layout.addWidget(h_splitter_2)
scroll_area = QScrollArea()
scroll_area.setWidget(widget_container)
v_main_layout = QVBoxLayout(win)
v_main_layout.addWidget(scroll_area)

View File

@@ -47,7 +47,7 @@ pyopengl
pyqt6>=6.1.0
freetype-py
vispy>=0.9.0
pyqtdarktheme==1.1.1
# pyqtdarktheme==1.1.1
svgtrace