ms-components 0.3.2__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ms_components-0.3.2/src/ms_components.egg-info → ms_components-0.3.3}/PKG-INFO +1 -1
- ms_components-0.3.3/src/ms_components/backdrop.py +34 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/base.qss +16 -4
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_settings/panel.py +3 -2
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/smart_table_view.py +13 -8
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/table_config.py +2 -0
- ms_components-0.3.3/src/ms_components/palettes.py +39 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/theme.py +80 -135
- {ms_components-0.3.2 → ms_components-0.3.3/src/ms_components.egg-info}/PKG-INFO +1 -1
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components.egg-info/SOURCES.txt +1 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components.egg-info/scm_file_list.json +1 -0
- ms_components-0.3.3/src/ms_components.egg-info/scm_version.json +8 -0
- ms_components-0.3.3/tests/test_theme_contrast.py +68 -0
- ms_components-0.3.2/src/ms_components/palettes.py +0 -29
- ms_components-0.3.2/src/ms_components.egg-info/scm_version.json +0 -8
- ms_components-0.3.2/tests/test_theme_contrast.py +0 -31
- {ms_components-0.3.2 → ms_components-0.3.3}/.github/workflows/docs.yml +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/.github/workflows/publish.yml +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/.gitignore +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/LICENSE +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/README.md +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/docs/api_reference.md +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/docs/examples.md +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/docs/index.md +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/docs/ms_tutorial.md +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/docs/usage.md +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/examples/tutorial_demo.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/examples/tutorial_demo.toml +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/pyproject.toml +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/setup.cfg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/__init__.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/chips.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/listview.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_dockwidget/__init__.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_dockwidget/resources/__init__.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_dockwidget/resources/icons/__init__.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_dockwidget/resources/icons/folder.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_dockwidget/widget.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_executors.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_monitor/__init__.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_monitor/bridge.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_monitor/cards.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_monitor/config.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_monitor/detail_panel.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_monitor/formatting.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_monitor/job_cards.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_monitor/models.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_monitor/palette.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_monitor/polling.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_monitor/widget.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_projects/__init__.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_projects/app.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_projects/browser.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_projects/scratch.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_projects/widget.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/__init__.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/icons/actions.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/icons/box.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/icons/center.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/icons/display.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/icons/hide.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/icons/menu.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/icons/orient.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/icons/presets.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/icons/rock.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/icons/selection.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/icons/settings.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/icons/zoom.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/plugins.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_pymol/widget.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_settings/__init__.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_stepper.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/__init__.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/action_header.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/cell_delegate.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/column_panel.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/example_usage.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/filter_panel.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/icons/columns.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/icons/export.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/icons/filter.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/icons/menu.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/icons/reload.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/icons/search.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/icons/settings.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/icons/sort-asc.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/icons/sort-desc.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/icons/sort.svg +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/metric_filter_bar.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/pagination_control.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/query_builder.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/session_provider.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/sort_panel.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/table_model.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/test_external_filters.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/test_toolbar.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_tutorial/__init__.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_tutorial/bindings.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_tutorial/controller.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_tutorial/models.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_tutorial/overlay.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/opengl.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/pagination.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/step_dialog.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/tool_panel.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/wheel.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components.egg-info/dependency_links.txt +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components.egg-info/requires.txt +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components.egg-info/top_level.txt +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_app_settings.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_chips.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_dock_window.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_executors_ui.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_molsuite_monitor_bridge.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_molsuite_monitor_widgets.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_monitor_config.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_ms_projects_widget.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_opengl.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_progress_column.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_project_browser.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_pymol_grid.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_pymol_toolbar.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_query_builder_keyset.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_shift_hscroll.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_split_button_style.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_step_dialog.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_table_toolbar.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_table_view_state.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/tests/test_tutorial.py +0 -0
- {ms_components-0.3.2 → ms_components-0.3.3}/zensical.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ms_components
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: PySide6 UI components for MolSuite: smart table, project browser, job monitor, PyMOL dock, steppers.
|
|
5
5
|
Author: Mario S. Valdés-Tresanco, Mario E. Valdés-Tresanco
|
|
6
6
|
License-Expression: MIT
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""A faint, centred icon painted under a widget's own children (welcome pages, empty states)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from PySide6.QtCore import QRect, QSize
|
|
6
|
+
from PySide6.QtGui import QIcon, QPainter
|
|
7
|
+
from PySide6.QtWidgets import QWidget
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Backdrop(QWidget):
|
|
11
|
+
"""``icon`` drawn at ``opacity``, scaled to 80 % of the shorter side; ``None`` draws nothing."""
|
|
12
|
+
|
|
13
|
+
# One fixed-size render, scaled: asking the icon for each widget size would cache a
|
|
14
|
+
# multi-MB pixmap per size (~1 GB after a long window resize with a caching icon engine).
|
|
15
|
+
RENDER_PX = 512
|
|
16
|
+
|
|
17
|
+
def __init__(self, icon: QIcon | None = None, parent: QWidget | None = None, *, opacity: float = 0.06):
|
|
18
|
+
super().__init__(parent)
|
|
19
|
+
self._icon = icon
|
|
20
|
+
self._opacity = opacity
|
|
21
|
+
|
|
22
|
+
def paintEvent(self, event) -> None:
|
|
23
|
+
if self._icon is not None:
|
|
24
|
+
side = int(min(self.width(), self.height()) * 0.8)
|
|
25
|
+
painter = QPainter(self)
|
|
26
|
+
painter.setOpacity(self._opacity)
|
|
27
|
+
painter.setRenderHint(QPainter.SmoothPixmapTransform)
|
|
28
|
+
painter.drawPixmap(QRect((self.width() - side) // 2, (self.height() - side) // 2, side, side),
|
|
29
|
+
self._icon.pixmap(QSize(self.RENDER_PX, self.RENDER_PX)))
|
|
30
|
+
painter.end()
|
|
31
|
+
super().paintEvent(event)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__all__ = ["Backdrop"]
|
|
@@ -111,7 +111,8 @@ QTextEdit,
|
|
|
111
111
|
QSpinBox,
|
|
112
112
|
QDoubleSpinBox,
|
|
113
113
|
QComboBox {
|
|
114
|
-
|
|
114
|
+
/* shadow = the control edge (3:1 on every surface); mid stays for hairlines */
|
|
115
|
+
border: 1px solid palette(shadow);
|
|
115
116
|
border-radius: 4px;
|
|
116
117
|
padding: 3px 6px;
|
|
117
118
|
}
|
|
@@ -136,7 +137,7 @@ QComboBox:focus {
|
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
QPushButton {
|
|
139
|
-
border: 1px solid palette(
|
|
140
|
+
border: 1px solid palette(shadow);
|
|
140
141
|
border-radius: 4px;
|
|
141
142
|
padding: 5px 14px;
|
|
142
143
|
}
|
|
@@ -154,16 +155,27 @@ QPushButton:default {
|
|
|
154
155
|
border-color: palette(highlight);
|
|
155
156
|
}
|
|
156
157
|
|
|
158
|
+
QLineEdit:disabled,
|
|
159
|
+
QPlainTextEdit:disabled,
|
|
160
|
+
QTextEdit:disabled,
|
|
161
|
+
QSpinBox:disabled,
|
|
162
|
+
QDoubleSpinBox:disabled,
|
|
163
|
+
QComboBox:disabled,
|
|
164
|
+
QPushButton:disabled,
|
|
165
|
+
QToolButton[popupMode="MenuButtonPopup"]:disabled {
|
|
166
|
+
border-color: palette(mid);
|
|
167
|
+
}
|
|
168
|
+
|
|
157
169
|
/* A split button (QToolButton with a menu arrow) is an action button, not a toolbar icon:
|
|
158
170
|
framed like QPushButton, the arrow in its own segment. */
|
|
159
171
|
QToolButton[popupMode="MenuButtonPopup"] {
|
|
160
|
-
border: 1px solid palette(
|
|
172
|
+
border: 1px solid palette(shadow);
|
|
161
173
|
padding: 5px 14px;
|
|
162
174
|
padding-right: 24px;
|
|
163
175
|
}
|
|
164
176
|
|
|
165
177
|
QToolButton[popupMode="MenuButtonPopup"]::menu-button {
|
|
166
|
-
border-left: 1px solid palette(
|
|
178
|
+
border-left: 1px solid palette(shadow);
|
|
167
179
|
width: 18px;
|
|
168
180
|
}
|
|
169
181
|
|
|
@@ -311,8 +311,8 @@ class AppSettingsPanel(QWidget):
|
|
|
311
311
|
font-weight: 700;
|
|
312
312
|
}
|
|
313
313
|
QLabel#sourceDefault { color: palette(text); background: rgba(128, 128, 128, 0.18); }
|
|
314
|
-
QLabel#sourceGlobal {
|
|
315
|
-
QLabel#sourceProject {
|
|
314
|
+
QLabel#sourceGlobal { background: rgba(76, 155, 232, 0.16); }
|
|
315
|
+
QLabel#sourceProject { background: rgba(52, 200, 138, 0.16); }
|
|
316
316
|
"""
|
|
317
317
|
)
|
|
318
318
|
|
|
@@ -743,6 +743,7 @@ class AppSettingsPanel(QWidget):
|
|
|
743
743
|
normalized = source if source in _SOURCE_LABELS else "default"
|
|
744
744
|
label.setText(_SOURCE_LABELS[normalized])
|
|
745
745
|
label.setObjectName(f"source{normalized.title()}")
|
|
746
|
+
label.setProperty("tone", {"global": "blue", "project": "green"}.get(normalized, "")) # theme text hue
|
|
746
747
|
label.style().unpolish(label)
|
|
747
748
|
label.style().polish(label)
|
|
748
749
|
|
|
@@ -57,7 +57,7 @@ from PySide6.QtWidgets import (
|
|
|
57
57
|
QHeaderView, QLabel, QMenu, QPushButton, QSizePolicy, QFrame,
|
|
58
58
|
QTableView, QToolBar, QToolButton, QVBoxLayout, QWidget
|
|
59
59
|
)
|
|
60
|
-
from ..
|
|
60
|
+
from ..backdrop import Backdrop
|
|
61
61
|
from .action_header import ActionHeader, FilterPopup, SearchPopup
|
|
62
62
|
from .cell_delegate import CellWidgetDelegate
|
|
63
63
|
from .column_panel import ColumnConfigDialog
|
|
@@ -335,21 +335,26 @@ class SmartTableView(QWidget):
|
|
|
335
335
|
self._empty_label.setObjectName("EmptyLabel")
|
|
336
336
|
self._empty_label.setAlignment(Qt.AlignCenter)
|
|
337
337
|
action = self._config.empty_action
|
|
338
|
-
if action is None:
|
|
338
|
+
if action is None and self._config.empty_backdrop is None:
|
|
339
339
|
self._empty_widget = self._empty_label
|
|
340
340
|
else:
|
|
341
|
-
self._empty_widget =
|
|
341
|
+
self._empty_widget = Backdrop(self._config.empty_backdrop, opacity=0.08)
|
|
342
342
|
empty_layout = QVBoxLayout(self._empty_widget)
|
|
343
343
|
empty_layout.setAlignment(Qt.AlignCenter)
|
|
344
344
|
empty_layout.setSpacing(12)
|
|
345
|
+
self._empty_label.setStyleSheet("font-size: 16px;")
|
|
346
|
+
empty_layout.addWidget(self._empty_label)
|
|
347
|
+
if action is not None:
|
|
345
348
|
button = QPushButton(action.label)
|
|
346
349
|
button.setObjectName("EmptyActionButton")
|
|
347
350
|
if action.icon is not None:
|
|
348
351
|
button.setIcon(action.icon)
|
|
349
352
|
button.setToolTip(action.tooltip or action.label)
|
|
353
|
+
button.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
354
|
+
button.setStyleSheet("font-size: 15px; padding: 8px 24px;")
|
|
350
355
|
if action.on_click is not None:
|
|
351
356
|
button.clicked.connect(lambda _=False, cb=action.on_click: cb([]))
|
|
352
|
-
empty_layout.
|
|
357
|
+
empty_layout.addSpacing(16)
|
|
353
358
|
empty_layout.addWidget(button, alignment=Qt.AlignCenter)
|
|
354
359
|
self._empty_widget.setVisible(False)
|
|
355
360
|
root.addWidget(self._empty_widget, stretch=1)
|
|
@@ -849,14 +854,14 @@ class SmartTableView(QWidget):
|
|
|
849
854
|
def _apply_toolbar_chrome(self) -> None:
|
|
850
855
|
"""Paint the toolbar as a band so the actions read as chrome, not as content.
|
|
851
856
|
|
|
852
|
-
|
|
853
|
-
of
|
|
854
|
-
|
|
857
|
+
The header's tone (`dark`): window/base/alternate-base are within a couple of steps
|
|
858
|
+
of each other in the flat light themes, so they separate nothing. Re-applied on
|
|
859
|
+
PaletteChange, so it still follows a live theme switch.
|
|
855
860
|
"""
|
|
856
861
|
if self._toolbar_row is None:
|
|
857
862
|
return
|
|
858
863
|
self._toolbar_row.setStyleSheet(
|
|
859
|
-
|
|
864
|
+
"#SmartTableToolbar { background: palette(dark);"
|
|
860
865
|
" border-bottom: 1px solid palette(mid); }"
|
|
861
866
|
" QToolBar { background: transparent; border: 0; }"
|
|
862
867
|
)
|
|
@@ -377,6 +377,8 @@ class TableConfig:
|
|
|
377
377
|
# action (import, create), a button that triggers it is more useful than
|
|
378
378
|
# `empty_message`. When set it replaces the text. `on_click` receives [].
|
|
379
379
|
empty_action: Optional["ToolbarAction"] = None
|
|
380
|
+
# Faint illustration under the empty state (a QIcon; see ms_components.backdrop).
|
|
381
|
+
empty_backdrop: Optional[Any] = None
|
|
380
382
|
# (row_data: dict) -> QColor | None. Tints a whole row from host state that is not a
|
|
381
383
|
# column (a marked/selected set, a validation flag). None = leave the default painting.
|
|
382
384
|
row_background: Optional[Callable] = None
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""The color values of every theme, as data: no Qt, so a settings model can list their names.
|
|
2
|
+
|
|
3
|
+
Palette data from **qt-themes** by Beat Reichenbach (MIT, see `ms_components.theme`), stored
|
|
4
|
+
here per QPalette role and nudged to the contrast floors that `tests/test_theme_contrast.py`
|
|
5
|
+
enforces. How the keys become a QPalette: `ms_components.theme.build_palette`.
|
|
6
|
+
|
|
7
|
+
Keys are the QPalette roles in snake_case, so a value is exactly what `palette(role)` returns
|
|
8
|
+
in a stylesheet. What MolSuite asks of the roles Qt leaves loosely defined:
|
|
9
|
+
|
|
10
|
+
* ``mid``: hairlines (separators, frames, table and group borders). Decorative.
|
|
11
|
+
* ``shadow``: the edge of a control (field, button, checkbox, radio); >= 3:1 on every surface.
|
|
12
|
+
* ``dark``: table header surface. ``midlight``: thin progress tracks.
|
|
13
|
+
* ``placeholder_text``: muted text (hints, meta lines); >= 4.5:1, below the body text.
|
|
14
|
+
* ``disabled_text``: the Disabled group's text; dimmer than a hint, so "off" reads as off.
|
|
15
|
+
|
|
16
|
+
The hues (red, orange, yellow, green, cyan, blue, magenta) have no QPalette role: read them
|
|
17
|
+
with `ms_components.theme.color`. Each is >= 4.5:1 on the theme's surfaces, since they are
|
|
18
|
+
used as text (errors, warnings) as well as fills.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
# Auto mode picks these based on the OS color scheme.
|
|
23
|
+
DEFAULT_LIGHT = "github_light"
|
|
24
|
+
DEFAULT_DARK = "catppuccin_mocha"
|
|
25
|
+
|
|
26
|
+
THEMES: dict[str, dict[str, str]] = {
|
|
27
|
+
"catppuccin_frappe": {"window": "#303446", "window_text": "#c6d0f5", "base": "#292c3c", "alternate_base": "#303446", "tooltip_base": "#292c3c", "tooltip_text": "#949cbb", "placeholder_text": "#959dbb", "text": "#c6d0f5", "button": "#303446", "button_text": "#c6d0f5", "bright_text": "#08080a", "light": "#232634", "midlight": "#292c3c", "mid": "#626880", "dark": "#51576d", "shadow": "#777d97", "highlight": "#ca9ee6", "highlighted_text": "#232634", "link": "#8caaee", "link_visited": "#8caaee", "accent": "#8caaee", "disabled_text": "#6c728c", "red": "#e78284", "orange": "#ef9f76", "yellow": "#e5c890", "green": "#a6d189", "cyan": "#99d1db", "blue": "#8caaee", "magenta": "#ca9ee6"},
|
|
28
|
+
"catppuccin_latte": {"window": "#ccd0da", "window_text": "#4c4f69", "base": "#ffffff", "alternate_base": "#e6e9ef", "tooltip_base": "#e6e9ef", "tooltip_text": "#666879", "placeholder_text": "#555871", "text": "#4c4f69", "button": "#ccd0da", "button_text": "#4c4f69", "bright_text": "#6d7196", "light": "#ffffff", "midlight": "#e6e9ef", "mid": "#9ca0b0", "dark": "#bfc3ce", "shadow": "#70737f", "highlight": "#8839ef", "highlighted_text": "#ffffff", "link": "#094cd2", "link_visited": "#094cd2", "accent": "#094cd2", "disabled_text": "#7a7d92", "red": "#b30d31", "orange": "#9b3901", "yellow": "#7c4f10", "green": "#29661b", "cyan": "#025f83", "blue": "#094cd2", "magenta": "#7113ec"},
|
|
29
|
+
"catppuccin_macchiato": {"window": "#24273a", "window_text": "#cad3f5", "base": "#1e2030", "alternate_base": "#24273a", "tooltip_base": "#1e2030", "tooltip_text": "#939ab7", "placeholder_text": "#888eaa", "text": "#cad3f5", "button": "#24273a", "button_text": "#cad3f5", "bright_text": "#08090a", "light": "#181926", "midlight": "#1e2030", "mid": "#5b6078", "dark": "#494d64", "shadow": "#6e738d", "highlight": "#c6a0f6", "highlighted_text": "#181926", "link": "#8aadf4", "link_visited": "#8aadf4", "accent": "#8aadf4", "disabled_text": "#61677f", "red": "#ed8796", "orange": "#f5a97f", "yellow": "#eed49f", "green": "#a6da95", "cyan": "#91d7e3", "blue": "#8aadf4", "magenta": "#c6a0f6"},
|
|
30
|
+
"catppuccin_mocha": {"window": "#1e1e2e", "window_text": "#cdd6f4", "base": "#181825", "alternate_base": "#1e1e2e", "tooltip_base": "#181825", "tooltip_text": "#9399b2", "placeholder_text": "#82879f", "text": "#cdd6f4", "button": "#1e1e2e", "button_text": "#cdd6f4", "bright_text": "#090a0b", "light": "#11111b", "midlight": "#181825", "mid": "#585b70", "dark": "#45475a", "shadow": "#6c7086", "highlight": "#cba6f7", "highlighted_text": "#11111b", "link": "#89b4fa", "link_visited": "#89b4fa", "accent": "#89b4fa", "disabled_text": "#5d6075", "red": "#f38ba8", "orange": "#fab387", "yellow": "#f9e2af", "green": "#a6e3a1", "cyan": "#89dceb", "blue": "#89b4fa", "magenta": "#cba6f7"},
|
|
31
|
+
"dracula": {"window": "#2d2f3c", "window_text": "#f8f8f2", "base": "#282a36", "alternate_base": "#2d2f3c", "tooltip_base": "#282a36", "tooltip_text": "#9ea0a6", "placeholder_text": "#97989b", "text": "#f8f8f2", "button": "#2d2f3c", "button_text": "#f8f8f2", "bright_text": "#070707", "light": "#1d1b22", "midlight": "#282a36", "mid": "#44475a", "dark": "#474959", "shadow": "#767985", "highlight": "#bd93f9", "highlighted_text": "#1d1b22", "link": "#ff79c6", "link_visited": "#ff79c6", "accent": "#ff79c6", "disabled_text": "#6e6f76", "red": "#ff6262", "orange": "#ffb86c", "yellow": "#f1fa8c", "green": "#50fa7b", "cyan": "#8be9fd", "blue": "#8a96bb", "magenta": "#bd93f9"},
|
|
32
|
+
"github_dark": {"window": "#262c36", "window_text": "#d1d7e0", "base": "#212830", "alternate_base": "#262c36", "tooltip_base": "#212830", "tooltip_text": "#989ea7", "placeholder_text": "#8d939c", "text": "#d1d7e0", "button": "#262c36", "button_text": "#d1d7e0", "bright_text": "#1d1e1f", "light": "#151b23", "midlight": "#212830", "mid": "#5f656f", "dark": "#4c525c", "shadow": "#727882", "highlight": "#79c0ff", "highlighted_text": "#151b23", "link": "#79c0ff", "link_visited": "#79c0ff", "accent": "#79c0ff", "disabled_text": "#656b75", "red": "#ffa198", "orange": "#ffa657", "yellow": "#e3b341", "green": "#56d364", "cyan": "#68cab2", "blue": "#79c0ff", "magenta": "#d2a8ff"},
|
|
33
|
+
"github_light": {"window": "#f6f8fa", "window_text": "#1f2328", "base": "#ffffff", "alternate_base": "#fbfcfd", "tooltip_base": "#fbfcfd", "tooltip_text": "#444952", "placeholder_text": "#61666e", "text": "#1f2328", "button": "#f6f8fa", "button_text": "#1f2328", "bright_text": "#a7bcd7", "light": "#ffffff", "midlight": "#fbfcfd", "mid": "#9da1a6", "dark": "#bbbec2", "shadow": "#7f838a", "highlight": "#5a94c8", "highlighted_text": "#1f2328", "link": "#0071d5", "link_visited": "#0071d5", "accent": "#0071d5", "disabled_text": "#979a9e", "red": "#e21400", "orange": "#b85600", "yellow": "#8f6b15", "green": "#22822d", "cyan": "#2c7e6a", "blue": "#0071d5", "magenta": "#9838ff"},
|
|
34
|
+
"modern_dark": {"window": "#2a2a2a", "window_text": "#c8c8c8", "base": "#1f1f1f", "alternate_base": "#2a2a2a", "tooltip_base": "#1f1f1f", "tooltip_text": "#868686", "placeholder_text": "#919191", "text": "#c8c8c8", "button": "#2a2a2a", "button_text": "#c8c8c8", "bright_text": "#373737", "light": "#141414", "midlight": "#1f1f1f", "mid": "#424242", "dark": "#444444", "shadow": "#737373", "highlight": "#2aa5da", "highlighted_text": "#141414", "link": "#2ad7da", "link_visited": "#2ad7da", "accent": "#2ad7da", "disabled_text": "#696969", "red": "#e16f53", "orange": "#da8a2a", "yellow": "#d7da2a", "green": "#2ada6f", "cyan": "#2ad7da", "blue": "#2aa5da", "magenta": "#c06fe4"},
|
|
35
|
+
"modern_light": {"window": "#e6ebf0", "window_text": "#000000", "base": "#ffffff", "alternate_base": "#eef2f6", "tooltip_base": "#eef2f6", "tooltip_text": "#484b4d", "placeholder_text": "#626568", "text": "#000000", "button": "#e6ebf0", "button_text": "#000000", "bright_text": "#ffffff", "light": "#ffffff", "midlight": "#eef2f6", "mid": "#979b9f", "dark": "#b1b6ba", "shadow": "#7d8084", "highlight": "#2590be", "highlighted_text": "#000000", "link": "#157677", "link_visited": "#157677", "accent": "#157677", "disabled_text": "#8f9295", "red": "#bb3f20", "orange": "#945c1a", "yellow": "#6b6d13", "green": "#157a3d", "cyan": "#157677", "blue": "#1a7196", "magenta": "#a22ad7"},
|
|
36
|
+
"monokai": {"window": "#2d2a2e", "window_text": "#fcfcfa", "base": "#221f22", "alternate_base": "#2d2a2e", "tooltip_base": "#221f22", "tooltip_text": "#939293", "placeholder_text": "#939293", "text": "#fcfcfa", "button": "#2d2a2e", "button_text": "#fcfcfa", "bright_text": "#030303", "light": "#19181a", "midlight": "#221f22", "mid": "#5b595c", "dark": "#4e4c4f", "shadow": "#757375", "highlight": "#ffd866", "highlighted_text": "#19181a", "link": "#ff6188", "link_visited": "#ff6188", "accent": "#ff6188", "disabled_text": "#6b696b", "red": "#ff6188", "orange": "#fc9867", "yellow": "#ffd866", "green": "#a9dc76", "cyan": "#91dcaf", "blue": "#78dce8", "magenta": "#ab9df2"},
|
|
37
|
+
"nord": {"window": "#3b4252", "window_text": "#eceff4", "base": "#2e3440", "alternate_base": "#3b4252", "tooltip_base": "#2e3440", "tooltip_text": "#d8dee9", "placeholder_text": "#abafb8", "text": "#eceff4", "button": "#3b4252", "button_text": "#eceff4", "bright_text": "#0b0b0b", "light": "#2e3440", "midlight": "#2e3440", "mid": "#4c566a", "dark": "#525a6b", "shadow": "#878e9b", "highlight": "#81a1c1", "highlighted_text": "#2e3440", "link": "#88c0d0", "link_visited": "#88c0d0", "accent": "#88c0d0", "disabled_text": "#7c828e", "red": "#d89fa4", "orange": "#daa18f", "yellow": "#ebcb8b", "green": "#a3be8c", "cyan": "#88c0d0", "blue": "#97b2cc", "magenta": "#c3a5be"},
|
|
38
|
+
"one_dark_two": {"window": "#282c34", "window_text": "#e6e6e6", "base": "#21252b", "alternate_base": "#282c34", "tooltip_base": "#21252b", "tooltip_text": "#969dab", "placeholder_text": "#919296", "text": "#e6e6e6", "button": "#282c34", "button_text": "#e6e6e6", "bright_text": "#191919", "light": "#1d1f23", "midlight": "#21252b", "mid": "#5b626d", "dark": "#4a505a", "shadow": "#6f7583", "highlight": "#62bac6", "highlighted_text": "#1d1f23", "link": "#eac786", "link_visited": "#eac786", "accent": "#eac786", "disabled_text": "#696b71", "red": "#e27881", "orange": "#e79c7e", "yellow": "#eac786", "green": "#98c379", "cyan": "#62bac6", "blue": "#71b9f4", "magenta": "#c88bda"},
|
|
39
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Color themes for every MolSuite Qt app (QPalette-based).
|
|
2
2
|
|
|
3
|
-
Palette data
|
|
4
|
-
Beat Reichenbach, MIT-licensed:
|
|
3
|
+
Palette data is derived from **qt-themes** by Beat Reichenbach, MIT-licensed:
|
|
5
4
|
|
|
6
5
|
https://github.com/beatreichenbach/qt-themes
|
|
7
6
|
Copyright (c) 2024 Beat Reichenbach
|
|
@@ -25,13 +24,39 @@ from __future__ import annotations
|
|
|
25
24
|
|
|
26
25
|
from importlib.resources import files
|
|
27
26
|
|
|
28
|
-
from PySide6.
|
|
29
|
-
from PySide6.
|
|
27
|
+
from PySide6.QtCore import Qt
|
|
28
|
+
from PySide6.QtGui import QColor, QFont, QFontDatabase, QPalette, QPen
|
|
29
|
+
from PySide6.QtWidgets import QApplication, QProxyStyle, QStyle
|
|
30
30
|
|
|
31
31
|
from ms_components.palettes import DEFAULT_DARK, DEFAULT_LIGHT, THEMES
|
|
32
32
|
|
|
33
33
|
_current: dict[str, str] = THEMES[DEFAULT_DARK]
|
|
34
34
|
|
|
35
|
+
#: theme key -> QPalette role (the keys are the roles in snake_case).
|
|
36
|
+
_ROLES = {
|
|
37
|
+
"window": QPalette.ColorRole.Window,
|
|
38
|
+
"window_text": QPalette.ColorRole.WindowText,
|
|
39
|
+
"base": QPalette.ColorRole.Base,
|
|
40
|
+
"alternate_base": QPalette.ColorRole.AlternateBase,
|
|
41
|
+
"tooltip_base": QPalette.ColorRole.ToolTipBase,
|
|
42
|
+
"tooltip_text": QPalette.ColorRole.ToolTipText,
|
|
43
|
+
"placeholder_text": QPalette.ColorRole.PlaceholderText,
|
|
44
|
+
"text": QPalette.ColorRole.Text,
|
|
45
|
+
"button": QPalette.ColorRole.Button,
|
|
46
|
+
"button_text": QPalette.ColorRole.ButtonText,
|
|
47
|
+
"bright_text": QPalette.ColorRole.BrightText,
|
|
48
|
+
"light": QPalette.ColorRole.Light,
|
|
49
|
+
"midlight": QPalette.ColorRole.Midlight,
|
|
50
|
+
"mid": QPalette.ColorRole.Mid,
|
|
51
|
+
"dark": QPalette.ColorRole.Dark,
|
|
52
|
+
"shadow": QPalette.ColorRole.Shadow,
|
|
53
|
+
"highlight": QPalette.ColorRole.Highlight,
|
|
54
|
+
"highlighted_text": QPalette.ColorRole.HighlightedText,
|
|
55
|
+
"link": QPalette.ColorRole.Link,
|
|
56
|
+
"link_visited": QPalette.ColorRole.LinkVisited,
|
|
57
|
+
"accent": QPalette.ColorRole.Accent,
|
|
58
|
+
}
|
|
59
|
+
|
|
35
60
|
_REMOVED_THEME_FALLBACKS = {
|
|
36
61
|
"atom_one": "one_dark_two",
|
|
37
62
|
"blender": DEFAULT_DARK,
|
|
@@ -39,9 +64,9 @@ _REMOVED_THEME_FALLBACKS = {
|
|
|
39
64
|
|
|
40
65
|
|
|
41
66
|
def is_dark(theme: dict[str, str] | None = None) -> bool:
|
|
42
|
-
"""A theme is dark when its text is brighter than its
|
|
67
|
+
"""A theme is dark when its text is brighter than its window."""
|
|
43
68
|
theme = theme or _current
|
|
44
|
-
return QColor(theme["
|
|
69
|
+
return QColor(theme["window_text"]).value() > QColor(theme["window"]).value()
|
|
45
70
|
|
|
46
71
|
|
|
47
72
|
def current_theme() -> dict[str, str]:
|
|
@@ -74,48 +99,6 @@ def contrast_ratio(left: QColor, right: QColor) -> float:
|
|
|
74
99
|
return (lighter + 0.05) / (darker + 0.05)
|
|
75
100
|
|
|
76
101
|
|
|
77
|
-
def _mix(left: QColor, right: QColor, amount: float) -> QColor:
|
|
78
|
-
return QColor.fromRgbF(*(
|
|
79
|
-
a + (b - a) * amount
|
|
80
|
-
for a, b in zip(left.getRgbF(), right.getRgbF())
|
|
81
|
-
))
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _surface_with_contrast(
|
|
85
|
-
source: QColor,
|
|
86
|
-
*,
|
|
87
|
-
foreground: QColor,
|
|
88
|
-
adjacent: QColor | tuple[QColor, ...],
|
|
89
|
-
text_minimum: float,
|
|
90
|
-
separation_minimum: float,
|
|
91
|
-
) -> QColor:
|
|
92
|
-
"""Closest tint/shade satisfying both readable text and visible separation."""
|
|
93
|
-
adjacent_colors = adjacent if isinstance(adjacent, tuple) else (adjacent,)
|
|
94
|
-
targets = (QColor("#000000"), QColor("#ffffff"))
|
|
95
|
-
for step in range(101):
|
|
96
|
-
amount = step / 100
|
|
97
|
-
for target in targets:
|
|
98
|
-
candidate = _mix(source, target, amount)
|
|
99
|
-
if (contrast_ratio(foreground, candidate) >= text_minimum
|
|
100
|
-
and all(
|
|
101
|
-
contrast_ratio(candidate, background) >= separation_minimum
|
|
102
|
-
for background in adjacent_colors
|
|
103
|
-
)):
|
|
104
|
-
return candidate
|
|
105
|
-
return source
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def _contrasting_text(background: QColor, theme: dict[str, str]) -> QColor:
|
|
109
|
-
themed = [QColor(theme[key]) for key in ("text", "base", "mantle", "crust")]
|
|
110
|
-
best = max(themed, key=lambda candidate: contrast_ratio(candidate, background))
|
|
111
|
-
if contrast_ratio(best, background) >= 4.5:
|
|
112
|
-
return best
|
|
113
|
-
return max(
|
|
114
|
-
(QColor("#000000"), QColor("#ffffff")),
|
|
115
|
-
key=lambda candidate: contrast_ratio(candidate, background),
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
|
|
119
102
|
#: role -> (scale over the app font, bold). Relative on purpose: the base size is
|
|
120
103
|
#: whatever Qt resolved for this desktop, so a user with a 14pt system font gets a
|
|
121
104
|
#: proportional title instead of the 18px somebody typed into a stylesheet once.
|
|
@@ -175,92 +158,19 @@ def set_base_font(points: float = DEFAULT_FONT_PT, *, family: str | None = None,
|
|
|
175
158
|
|
|
176
159
|
|
|
177
160
|
def build_palette(theme: dict[str, str]) -> QPalette:
|
|
178
|
-
"""Build a QPalette from a theme dict.
|
|
179
|
-
|
|
180
|
-
Port of qt-themes' `update_palette` (MIT, Beat Reichenbach), adapted to read
|
|
181
|
-
from a flat dict instead of a dataclass.
|
|
182
|
-
"""
|
|
183
|
-
c = lambda role: QColor(theme[role]) # noqa: E731
|
|
161
|
+
"""Build a QPalette from a theme dict: one key per role, plus the Disabled text."""
|
|
184
162
|
Role = QPalette.ColorRole
|
|
185
163
|
Group = QPalette.ColorGroup
|
|
186
164
|
pal = QPalette()
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
separation_minimum=3.0,
|
|
197
|
-
)
|
|
198
|
-
highlight_text = _contrasting_text(highlight, theme)
|
|
199
|
-
header = _surface_with_contrast(
|
|
200
|
-
c("surface1"),
|
|
201
|
-
foreground=c("text"),
|
|
202
|
-
adjacent=(table_base, table_alternate),
|
|
203
|
-
text_minimum=4.5,
|
|
204
|
-
separation_minimum=1.45,
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
h, s, v, a = c("text").getHsvF()
|
|
208
|
-
bright_text = QColor.fromHsvF(h, s, 1 - v, a)
|
|
209
|
-
|
|
210
|
-
# Active/Normal group
|
|
211
|
-
if is_dark(theme):
|
|
212
|
-
pal.setColor(Role.Base, c("mantle"))
|
|
213
|
-
pal.setColor(Role.AlternateBase, c("base"))
|
|
214
|
-
else:
|
|
215
|
-
pal.setColor(Role.Base, c("crust"))
|
|
216
|
-
pal.setColor(Role.AlternateBase, c("mantle"))
|
|
217
|
-
pal.setColor(Role.Window, c("base"))
|
|
218
|
-
pal.setColor(Role.WindowText, c("text"))
|
|
219
|
-
pal.setColor(Role.PlaceholderText, c("overlay1"))
|
|
220
|
-
pal.setColor(Role.Text, c("text"))
|
|
221
|
-
pal.setColor(Role.Button, c("base"))
|
|
222
|
-
pal.setColor(Role.ButtonText, c("text"))
|
|
223
|
-
pal.setColor(Role.BrightText, bright_text)
|
|
224
|
-
pal.setColor(Role.ToolTipBase, c("mantle"))
|
|
225
|
-
pal.setColor(Role.ToolTipText, c("overlay2"))
|
|
226
|
-
|
|
227
|
-
pal.setColor(Role.Highlight, highlight)
|
|
228
|
-
pal.setColor(Role.HighlightedText, highlight_text)
|
|
229
|
-
pal.setColor(Role.Link, c("secondary"))
|
|
230
|
-
pal.setColor(Role.LinkVisited, c("secondary"))
|
|
231
|
-
|
|
232
|
-
pal.setColor(Role.Light, c("crust"))
|
|
233
|
-
pal.setColor(Role.Midlight, c("mantle"))
|
|
234
|
-
# Mid is *the* border tone: `palette(mid)` is what every stylesheet uses for
|
|
235
|
-
# hairlines, so it must be the conventional Catppuccin-style separator.
|
|
236
|
-
pal.setColor(Role.Mid, c("surface2"))
|
|
237
|
-
pal.setColor(Role.Dark, header)
|
|
238
|
-
pal.setColor(Role.Shadow, c("overlay0"))
|
|
239
|
-
|
|
240
|
-
# Inactive group
|
|
241
|
-
pal.setColor(Group.Inactive, Role.Highlight, highlight)
|
|
242
|
-
pal.setColor(Group.Inactive, Role.HighlightedText, highlight_text)
|
|
243
|
-
pal.setColor(Group.Inactive, Role.Link, c("surface1"))
|
|
244
|
-
pal.setColor(Group.Inactive, Role.LinkVisited, c("surface1"))
|
|
245
|
-
|
|
246
|
-
# Disabled group
|
|
247
|
-
pal.setColor(Group.Disabled, Role.WindowText, c("overlay1"))
|
|
248
|
-
pal.setColor(Group.Disabled, Role.Base, c("base"))
|
|
249
|
-
pal.setColor(Group.Disabled, Role.AlternateBase, c("base"))
|
|
250
|
-
pal.setColor(Group.Disabled, Role.Text, c("overlay1"))
|
|
251
|
-
pal.setColor(Group.Disabled, Role.PlaceholderText, c("overlay1"))
|
|
252
|
-
pal.setColor(Group.Disabled, Role.Button, c("base"))
|
|
253
|
-
pal.setColor(Group.Disabled, Role.ButtonText, c("overlay1"))
|
|
254
|
-
pal.setColor(Group.Disabled, Role.BrightText, c("mantle"))
|
|
255
|
-
pal.setColor(Group.Disabled, Role.Highlight, c("surface2"))
|
|
256
|
-
pal.setColor(Group.Disabled, Role.HighlightedText, _contrasting_text(c("surface2"), theme))
|
|
257
|
-
pal.setColor(Group.Disabled, Role.Link, c("surface0"))
|
|
258
|
-
pal.setColor(Group.Disabled, Role.LinkVisited, c("surface0"))
|
|
259
|
-
|
|
260
|
-
pal.setColor(Role.Accent, c("secondary"))
|
|
261
|
-
pal.setColor(Group.Inactive, Role.Accent, c("surface1"))
|
|
262
|
-
pal.setColor(Group.Disabled, Role.Accent, c("surface2"))
|
|
263
|
-
|
|
165
|
+
for key, role in _ROLES.items():
|
|
166
|
+
pal.setColor(role, QColor(theme[key])) # all groups; Disabled is overridden below
|
|
167
|
+
disabled = QColor(theme["disabled_text"])
|
|
168
|
+
for role in (Role.WindowText, Role.Text, Role.ButtonText, Role.PlaceholderText, Role.BrightText,
|
|
169
|
+
Role.HighlightedText, Role.Link, Role.LinkVisited, Role.Accent):
|
|
170
|
+
pal.setColor(Group.Disabled, role, disabled)
|
|
171
|
+
for role in (Role.Base, Role.AlternateBase, Role.Button):
|
|
172
|
+
pal.setColor(Group.Disabled, role, QColor(theme["window"]))
|
|
173
|
+
pal.setColor(Group.Disabled, Role.Highlight, QColor(theme["mid"]))
|
|
264
174
|
return pal
|
|
265
175
|
|
|
266
176
|
|
|
@@ -278,13 +188,15 @@ def resolve(name: str) -> str:
|
|
|
278
188
|
|
|
279
189
|
#: The accents a widget can wear through `setProperty("tone", name)`.
|
|
280
190
|
TONES = ("green", "red")
|
|
191
|
+
HUES = ("red", "orange", "yellow", "green", "cyan", "blue", "magenta")
|
|
281
192
|
|
|
282
193
|
|
|
283
194
|
def tone_qss(theme: dict[str, str] | None = None) -> str:
|
|
284
|
-
"""`QProgressBar[tone="green"]` and siblings, from the live theme.
|
|
195
|
+
"""`QProgressBar[tone="green"]`, `QLabel[tone="blue"]` and siblings, from the live theme.
|
|
285
196
|
|
|
286
197
|
QSS has no palette role for the accents, so these rules are written per theme.
|
|
287
|
-
|
|
198
|
+
Bars are translucent, so their text stays readable on dark and light themes alike;
|
|
199
|
+
labels take the hue as text (every hue is >= 4.5:1 on the theme's surfaces).
|
|
288
200
|
"""
|
|
289
201
|
theme = theme or _current
|
|
290
202
|
rules = []
|
|
@@ -292,6 +204,7 @@ def tone_qss(theme: dict[str, str] | None = None) -> str:
|
|
|
292
204
|
accent = QColor(theme[tone])
|
|
293
205
|
rgba = f"rgba({accent.red()}, {accent.green()}, {accent.blue()}, 150)"
|
|
294
206
|
rules.append(f'QProgressBar[tone="{tone}"]::chunk {{ background: {rgba}; }}')
|
|
207
|
+
rules += [f'QLabel[tone="{hue}"] {{ color: {theme[hue]}; }}' for hue in HUES]
|
|
295
208
|
return "\n".join(rules) + "\n"
|
|
296
209
|
|
|
297
210
|
|
|
@@ -300,6 +213,37 @@ def base_qss() -> str:
|
|
|
300
213
|
return files(__package__).joinpath("base.qss").read_text(encoding="utf-8")
|
|
301
214
|
|
|
302
215
|
|
|
216
|
+
class _EdgedFusion(QProxyStyle):
|
|
217
|
+
"""Fusion with checkbox/radio outlines in `shadow`.
|
|
218
|
+
|
|
219
|
+
Fusion draws them as `window.darker(140)`, which no palette role reaches and which
|
|
220
|
+
sits near 1.5:1 in most themes. A QSS `::indicator` rule would replace the check mark
|
|
221
|
+
too, so the outline is redrawn on top of Fusion's own indicator instead.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
def __init__(self):
|
|
225
|
+
super().__init__("Fusion")
|
|
226
|
+
|
|
227
|
+
def drawPrimitive(self, element, option, painter, widget=None):
|
|
228
|
+
super().drawPrimitive(element, option, painter, widget)
|
|
229
|
+
if element not in (QStyle.PrimitiveElement.PE_IndicatorCheckBox,
|
|
230
|
+
QStyle.PrimitiveElement.PE_IndicatorRadioButton):
|
|
231
|
+
return
|
|
232
|
+
if not option.state & QStyle.StateFlag.State_Enabled:
|
|
233
|
+
return # disabled keeps Fusion's faint edge: it should read as "off"
|
|
234
|
+
painter.save()
|
|
235
|
+
painter.setBrush(Qt.BrushStyle.NoBrush)
|
|
236
|
+
edge = option.palette.color(QPalette.ColorRole.Shadow)
|
|
237
|
+
if element == QStyle.PrimitiveElement.PE_IndicatorCheckBox:
|
|
238
|
+
painter.setPen(QPen(edge, 1))
|
|
239
|
+
painter.drawRect(option.rect.adjusted(0, 0, -1, -1))
|
|
240
|
+
else: # 1.5 px antialiased: a 1 px ring blurs to ~2.7:1
|
|
241
|
+
painter.setRenderHint(painter.RenderHint.Antialiasing)
|
|
242
|
+
painter.setPen(QPen(edge, 1.5))
|
|
243
|
+
painter.drawEllipse(option.rect.toRectF().adjusted(1.5, 1.5, -1.5, -1.5))
|
|
244
|
+
painter.restore()
|
|
245
|
+
|
|
246
|
+
|
|
303
247
|
def apply_theme(name: str, app: QApplication | None = None, *,
|
|
304
248
|
base_font_pt: float | None = None) -> None:
|
|
305
249
|
"""Apply a theme (or "auto") to the QApplication.
|
|
@@ -314,7 +258,8 @@ def apply_theme(name: str, app: QApplication | None = None, *,
|
|
|
314
258
|
_current = THEMES[resolve(name)]
|
|
315
259
|
if base_font_pt is not None: # optional: omit it and the system font stays
|
|
316
260
|
set_base_font(base_font_pt, app=app)
|
|
317
|
-
app.
|
|
261
|
+
if not isinstance(app.style(), _EdgedFusion):
|
|
262
|
+
app.setStyle(_EdgedFusion()) # Fusion honors QPalette ColorRoles most faithfully
|
|
318
263
|
app.setPalette(build_palette(_current))
|
|
319
264
|
app.setStyleSheet(base_qss() + tone_qss())
|
|
320
265
|
|
|
@@ -322,7 +267,7 @@ def apply_theme(name: str, app: QApplication | None = None, *,
|
|
|
322
267
|
if __name__ == "__main__":
|
|
323
268
|
# Self-check: every theme builds a palette; light/dark classification holds.
|
|
324
269
|
for _name, _t in THEMES.items():
|
|
325
|
-
assert set(_t) >=
|
|
270
|
+
assert set(_t) >= set(_ROLES) | {"disabled_text", *HUES}, _name
|
|
326
271
|
assert build_palette(_t).color(QPalette.ColorRole.Window).isValid(), _name
|
|
327
272
|
assert is_dark(THEMES["dracula"]) is True
|
|
328
273
|
assert is_dark(THEMES["catppuccin_latte"]) is False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ms_components
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: PySide6 UI components for MolSuite: smart table, project browser, job monitor, PyMOL dock, steppers.
|
|
5
5
|
Author: Mario S. Valdés-Tresanco, Mario E. Valdés-Tresanco
|
|
6
6
|
License-Expression: MIT
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from PySide6.QtGui import QColor, QPalette
|
|
2
|
+
|
|
3
|
+
from ms_components.theme import HUES, TONES, THEMES, base_qss, build_palette, contrast_ratio, resolve
|
|
4
|
+
|
|
5
|
+
def _floor(theme, key, minimum, *against):
|
|
6
|
+
against = against or ("window", "base", "alternate_base")
|
|
7
|
+
for surface in against:
|
|
8
|
+
ratio = contrast_ratio(QColor(theme[key]), QColor(theme[surface]))
|
|
9
|
+
assert ratio >= minimum, f"{key} on {surface}: {ratio:.2f} < {minimum}"
|
|
10
|
+
|
|
11
|
+
def test_removed_themes_fall_back():
|
|
12
|
+
assert "atom_one" not in THEMES
|
|
13
|
+
assert "blender" not in THEMES
|
|
14
|
+
assert resolve("atom_one") == "one_dark_two"
|
|
15
|
+
assert resolve("blender") == "catppuccin_mocha"
|
|
16
|
+
assert "QHeaderView::section" in base_qss()
|
|
17
|
+
assert "background: palette(dark)" in base_qss()
|
|
18
|
+
|
|
19
|
+
def test_every_theme_meets_the_contrast_floors():
|
|
20
|
+
for name, theme in THEMES.items():
|
|
21
|
+
try:
|
|
22
|
+
_floor(theme, "text", 4.5)
|
|
23
|
+
_floor(theme, "window_text", 4.5)
|
|
24
|
+
_floor(theme, "button_text", 4.5, "button")
|
|
25
|
+
_floor(theme, "text", 4.5, "dark") # table header
|
|
26
|
+
_floor(theme, "dark", 1.4, "base", "alternate_base") # header band: decorative, just visible
|
|
27
|
+
_floor(theme, "highlighted_text", 4.5, "highlight")
|
|
28
|
+
_floor(theme, "highlight", 3.0) # selection, focus ring, tab underline
|
|
29
|
+
_floor(theme, "shadow", 3.0) # control edges (WCAG 1.4.11)
|
|
30
|
+
_floor(theme, "placeholder_text", 4.5)
|
|
31
|
+
_floor(theme, "tooltip_text", 4.5, "tooltip_base")
|
|
32
|
+
for key in ("link", "link_visited", "accent", *HUES):
|
|
33
|
+
_floor(theme, key, 4.5)
|
|
34
|
+
except AssertionError as error:
|
|
35
|
+
raise AssertionError(f"{name}: {error}") from None
|
|
36
|
+
|
|
37
|
+
def test_muted_and_disabled_text_read_as_different_levels():
|
|
38
|
+
for name, theme in THEMES.items():
|
|
39
|
+
window = QColor(theme["window"])
|
|
40
|
+
body = contrast_ratio(QColor(theme["text"]), window)
|
|
41
|
+
hint = contrast_ratio(QColor(theme["placeholder_text"]), window)
|
|
42
|
+
off = contrast_ratio(QColor(theme["disabled_text"]), window)
|
|
43
|
+
assert off < hint < body, (name, off, hint, body)
|
|
44
|
+
|
|
45
|
+
def test_the_palette_carries_every_role_and_the_disabled_text():
|
|
46
|
+
role, group = QPalette.ColorRole, QPalette.ColorGroup
|
|
47
|
+
theme = THEMES["dracula"]
|
|
48
|
+
palette = build_palette(theme)
|
|
49
|
+
assert palette.color(role.Shadow).name() == theme["shadow"]
|
|
50
|
+
assert palette.color(group.Inactive, role.Link).name() == theme["link"] # no greyed links
|
|
51
|
+
assert palette.color(group.Disabled, role.Text).name() == theme["disabled_text"]
|
|
52
|
+
assert set(TONES) <= set(theme)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_checkbox_and_radio_edges_reach_three_to_one():
|
|
56
|
+
from PySide6.QtWidgets import QApplication, QCheckBox, QRadioButton
|
|
57
|
+
|
|
58
|
+
from ms_components.theme import apply_theme
|
|
59
|
+
|
|
60
|
+
app = QApplication.instance() or QApplication([])
|
|
61
|
+
apply_theme("catppuccin_mocha", app) # Fusion alone draws these near 1.5:1
|
|
62
|
+
window = app.palette().window().color()
|
|
63
|
+
for box in (QCheckBox(), QRadioButton()):
|
|
64
|
+
box.resize(box.sizeHint())
|
|
65
|
+
image = box.grab().toImage()
|
|
66
|
+
edge = max(contrast_ratio(image.pixelColor(x, y), window)
|
|
67
|
+
for x in range(min(image.width(), 24)) for y in range(image.height()))
|
|
68
|
+
assert edge >= 3.0, (type(box).__name__, edge)
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
"""The color values of every theme, as data: no Qt, so a settings model can list their names.
|
|
2
|
-
|
|
3
|
-
Where they come from and how they become a QPalette: `ms_components.theme`.
|
|
4
|
-
"""
|
|
5
|
-
from __future__ import annotations
|
|
6
|
-
|
|
7
|
-
# Auto mode picks these based on the OS color scheme. Catppuccin is the only
|
|
8
|
-
# family here with a light+dark pair designed together, so it's the coherent auto
|
|
9
|
-
# default; dracula and the rest stay selectable.
|
|
10
|
-
DEFAULT_LIGHT = "github_light"
|
|
11
|
-
DEFAULT_DARK = "catppuccin_mocha"
|
|
12
|
-
|
|
13
|
-
# name -> {role: "#hex"}. Roles: primary/secondary, accents (magenta/red/orange/
|
|
14
|
-
# yellow/green/cyan/blue), text/subtext1/subtext0, overlay2..0, surface2..0,
|
|
15
|
-
# base/mantle/crust.
|
|
16
|
-
THEMES: dict[str, dict[str, str]] = {
|
|
17
|
-
"catppuccin_frappe": {"primary": "#ca9ee6", "secondary": "#8caaee", "magenta": "#ca9ee6", "red": "#e78284", "orange": "#ef9f76", "yellow": "#e5c890", "green": "#a6d189", "cyan": "#99d1db", "blue": "#8caaee", "text": "#c6d0f5", "subtext1": "#b5bfe2", "subtext0": "#a5adce", "overlay2": "#949cbb", "overlay1": "#838ba7", "overlay0": "#737994", "surface2": "#626880", "surface1": "#51576d", "surface0": "#414559", "base": "#303446", "mantle": "#292c3c", "crust": "#232634"},
|
|
18
|
-
"catppuccin_latte": {"primary": "#8839ef", "secondary": "#1e66f5", "magenta": "#8839ef", "red": "#d20f39", "orange": "#fe640b", "yellow": "#df8e1d", "green": "#40a02b", "cyan": "#04a5e5", "blue": "#1e66f5", "text": "#4c4f69", "subtext1": "#5c5f77", "subtext0": "#6c6f85", "overlay2": "#7c7f93", "overlay1": "#8c8fa1", "overlay0": "#9ca0b0", "surface2": "#9ca0b0", "surface1": "#bcc0cc", "surface0": "#ccd0da", "base": "#ccd0da", "mantle": "#e6e9ef", "crust": "#ffffff"},
|
|
19
|
-
"catppuccin_macchiato": {"primary": "#c6a0f6", "secondary": "#8aadf4", "magenta": "#c6a0f6", "red": "#ed8796", "orange": "#f5a97f", "yellow": "#eed49f", "green": "#a6da95", "cyan": "#91d7e3", "blue": "#8aadf4", "text": "#cad3f5", "subtext1": "#b8c0e0", "subtext0": "#a5adcb", "overlay2": "#939ab7", "overlay1": "#8087a2", "overlay0": "#6e738d", "surface2": "#5b6078", "surface1": "#494d64", "surface0": "#363a4f", "base": "#24273a", "mantle": "#1e2030", "crust": "#181926"},
|
|
20
|
-
"catppuccin_mocha": {"primary": "#cba6f7", "secondary": "#89b4fa", "magenta": "#cba6f7", "red": "#f38ba8", "orange": "#fab387", "yellow": "#f9e2af", "green": "#a6e3a1", "cyan": "#89dceb", "blue": "#89b4fa", "text": "#cdd6f4", "subtext1": "#bac2de", "subtext0": "#a6adc8", "overlay2": "#9399b2", "overlay1": "#7f849c", "overlay0": "#6c7086", "surface2": "#585b70", "surface1": "#45475a", "surface0": "#313244", "base": "#1e1e2e", "mantle": "#181825", "crust": "#11111b"},
|
|
21
|
-
"dracula": {"primary": "#bd93f9", "secondary": "#ff79c6", "magenta": "#bd93f9", "red": "#ff5555", "orange": "#ffb86c", "yellow": "#f1fa8c", "green": "#50fa7b", "cyan": "#8be9fd", "blue": "#6272a4", "text": "#f8f8f2", "subtext1": "#DADBD9", "subtext0": "#BCBDBF", "overlay2": "#9EA0A6", "overlay1": "#80828D", "overlay0": "#626573", "surface2": "#44475a", "surface1": "#3B3D4E", "surface0": "#313442", "base": "#2D2F3C", "mantle": "#282a36", "crust": "#1d1b22"},
|
|
22
|
-
"github_dark": {"primary": "#79c0ff", "secondary": "#56d364", "magenta": "#d2a8ff", "red": "#ffa198", "orange": "#ffa657", "yellow": "#e3b341", "green": "#56d364", "cyan": "#68CAB2", "blue": "#79c0ff", "text": "#d1d7e0", "subtext1": "#BEC4CD", "subtext0": "#ABB1BA", "overlay2": "#989EA7", "overlay1": "#858B94", "overlay0": "#727882", "surface2": "#5F656F", "surface1": "#4C525C", "surface0": "#3E444E", "base": "#262c36", "mantle": "#212830", "crust": "#151b23"},
|
|
23
|
-
"github_light": {"primary": "#79c0ff", "secondary": "#56d364", "magenta": "#d2a8ff", "red": "#ffa198", "orange": "#ffa657", "yellow": "#e3b341", "green": "#56d364", "cyan": "#68CAB2", "blue": "#79c0ff", "text": "#1f2328", "subtext1": "#212830", "subtext0": "#262c36", "overlay2": "#444952", "overlay1": "#61666E", "overlay0": "#7F838A", "surface2": "#9DA1A6", "surface1": "#BBBEC2", "surface0": "#D8DBDE", "base": "#f6f8fa", "mantle": "#FBFCFD", "crust": "#ffffff"},
|
|
24
|
-
"modern_dark": {"primary": "#2aa5da", "secondary": "#2ad7da", "magenta": "#b351de", "red": "#da4d2a", "orange": "#da8a2a", "yellow": "#d7da2a", "green": "#2ada6f", "cyan": "#2ad7da", "blue": "#2aa5da", "text": "#c8c8c8", "subtext1": "#b2b2b2", "subtext0": "#9b9b9b", "overlay2": "#858585", "overlay1": "#6f6f6f", "overlay0": "#585858", "surface2": "#424242", "surface1": "#3c3c3c", "surface0": "#353535", "base": "#2a2a2a", "mantle": "#1f1f1f", "crust": "#141414"},
|
|
25
|
-
"modern_light": {"primary": "#2aa5da", "secondary": "#2ad7da", "magenta": "#b351de", "red": "#da4d2a", "orange": "#da8a2a", "yellow": "#d7da2a", "green": "#2ada6f", "cyan": "#2ad7da", "blue": "#2aa5da", "text": "#000000", "subtext1": "#131517", "subtext0": "#2d3032", "overlay2": "#484b4d", "overlay1": "#626568", "overlay0": "#7d8084", "surface2": "#979b9f", "surface1": "#b1b6ba", "surface0": "#ccd0d5", "base": "#e6ebf0", "mantle": "#eef2f6", "crust": "#ffffff"},
|
|
26
|
-
"monokai": {"primary": "#ffd866", "secondary": "#ff6188", "magenta": "#ab9df2", "red": "#ff6188", "orange": "#fc9867", "yellow": "#ffd866", "green": "#a9dc76", "cyan": "#91dcaf", "blue": "#78dce8", "text": "#fcfcfa", "subtext1": "#c1c0c0", "subtext0": "#c1c0c0", "overlay2": "#939293", "overlay1": "#939293", "overlay0": "#727072", "surface2": "#5b595c", "surface1": "#4e4c4f", "surface0": "#403e41", "base": "#2d2a2e", "mantle": "#221f22", "crust": "#19181a"},
|
|
27
|
-
"nord": {"primary": "#81a1c1", "secondary": "#88c0d0", "magenta": "#b48ead", "red": "#bf616a", "orange": "#d08770", "yellow": "#ebcb8b", "green": "#a3be8c", "cyan": "#88c0d0", "blue": "#81a1c1", "text": "#eceff4", "subtext1": "#e5e9f0", "subtext0": "#d8dee9", "overlay2": "#d8dee9", "overlay1": "#d8dee9", "overlay0": "#4c566a", "surface2": "#4c566a", "surface1": "#434c5e", "surface0": "#3b4252", "base": "#3b4252", "mantle": "#2e3440", "crust": "#2e3440"},
|
|
28
|
-
"one_dark_two": {"primary": "#62bac6", "secondary": "#eac786", "magenta": "#c88bda", "red": "#e27881", "orange": "#e79c7e", "yellow": "#eac786", "green": "#98c379", "cyan": "#62bac6", "blue": "#71b9f4", "text": "#e6e6e6", "subtext1": "#c9ccd3", "subtext0": "#abb2bf", "overlay2": "#969dab", "overlay1": "#818896", "overlay0": "#6c7280", "surface2": "#5b626d", "surface1": "#4a505a", "surface0": "#393e47", "base": "#282c34", "mantle": "#21252b", "crust": "#1d1f23"},
|
|
29
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
from PySide6.QtGui import QPalette
|
|
2
|
-
|
|
3
|
-
from ms_components.theme import THEMES, base_qss, build_palette, contrast_ratio, resolve
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_remaining_themes_keep_tables_and_selections_legible():
|
|
7
|
-
role = QPalette.ColorRole
|
|
8
|
-
group = QPalette.ColorGroup
|
|
9
|
-
|
|
10
|
-
assert "atom_one" not in THEMES
|
|
11
|
-
assert "blender" not in THEMES
|
|
12
|
-
assert resolve("atom_one") == "one_dark_two"
|
|
13
|
-
assert resolve("blender") == "catppuccin_mocha"
|
|
14
|
-
assert "QHeaderView::section" in base_qss()
|
|
15
|
-
assert "background: palette(dark)" in base_qss()
|
|
16
|
-
|
|
17
|
-
for name, theme in THEMES.items():
|
|
18
|
-
palette = build_palette(theme)
|
|
19
|
-
row_backgrounds = (
|
|
20
|
-
palette.color(group.Active, role.Base),
|
|
21
|
-
palette.color(group.Active, role.AlternateBase),
|
|
22
|
-
)
|
|
23
|
-
header = palette.color(group.Active, role.Dark)
|
|
24
|
-
assert contrast_ratio(palette.color(group.Active, role.Text), header) >= 4.5, name
|
|
25
|
-
assert min(contrast_ratio(header, bg) for bg in row_backgrounds) >= 1.45, name
|
|
26
|
-
|
|
27
|
-
for state in (group.Active, group.Inactive):
|
|
28
|
-
highlight = palette.color(state, role.Highlight)
|
|
29
|
-
highlighted_text = palette.color(state, role.HighlightedText)
|
|
30
|
-
assert contrast_ratio(highlighted_text, highlight) >= 4.5, (name, state)
|
|
31
|
-
assert min(contrast_ratio(highlight, bg) for bg in row_backgrounds) >= 3.0, (name, state)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_dockwidget/resources/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/pagination_control.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ms_components-0.3.2 → ms_components-0.3.3}/src/ms_components/ms_table/test_external_filters.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|