medusa-style 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- medusa_style-0.3.0/.gitignore +221 -0
- medusa_style-0.3.0/CHANGELOG.md +106 -0
- medusa_style-0.3.0/LICENSE +29 -0
- medusa_style-0.3.0/PKG-INFO +243 -0
- medusa_style-0.3.0/README.md +216 -0
- medusa_style-0.3.0/examples/qt_eeg_demo.py +220 -0
- medusa_style-0.3.0/medusa_style/__init__.py +103 -0
- medusa_style-0.3.0/medusa_style/api.py +164 -0
- medusa_style-0.3.0/medusa_style/appearance.py +200 -0
- medusa_style-0.3.0/medusa_style/assets/brand/medusa_about.png +0 -0
- medusa_style-0.3.0/medusa_style/assets/brand/medusa_login.png +0 -0
- medusa_style-0.3.0/medusa_style/assets/brand/medusa_splash.png +0 -0
- medusa_style-0.3.0/medusa_style/assets/brand/medusa_task_icon.ico +0 -0
- medusa_style-0.3.0/medusa_style/assets/brand/medusa_task_icon.png +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Abel-Regular.ttf +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Dense-Regular.otf +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Roboto-Black.ttf +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Roboto-BlackItalic.ttf +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Roboto-Bold.ttf +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Roboto-BoldItalic.ttf +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Roboto-Italic.ttf +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Roboto-Light.ttf +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Roboto-LightItalic.ttf +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Roboto-Medium.ttf +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Roboto-MediumItalic.ttf +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Roboto-Regular.ttf +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Roboto-Thin.ttf +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Roboto-ThinItalic.ttf +0 -0
- medusa_style-0.3.0/medusa_style/assets/fonts/Womby-Regular.otf +0 -0
- medusa_style-0.3.0/medusa_style/assets/icons/add.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/add_element.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/add_session.svg +11 -0
- medusa_style-0.3.0/medusa_style/assets/icons/arrow_back.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/arrow_downward.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/arrow_forward.svg +2 -0
- medusa_style-0.3.0/medusa_style/assets/icons/arrow_upward.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/calendar_clock.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/chevron_right.svg +3 -0
- medusa_style-0.3.0/medusa_style/assets/icons/chevron_right_grey.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/close.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/config_session.svg +17 -0
- medusa_style-0.3.0/medusa_style/assets/icons/delete_forever.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/delete_sweep.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/download.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/edit.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/expand_more.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/expand_more_grey.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/fast_forward.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/fast_rewind.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/fit_screen.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/folder.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/keyboard_double_arrow_down.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/keyboard_double_arrow_up.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/link.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/link_off.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/load_session.svg +11 -0
- medusa_style-0.3.0/medusa_style/assets/icons/open_in_new.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/open_in_new_down.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/pause.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/person.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/play.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/power.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/refresh.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/remove.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/route.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/save_as.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/science.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/search.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/settings.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/stop.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/unfold_less.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/unfold_more.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/view_column.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/visibility.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/visibility_login.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/visibility_login_off.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/visibility_off.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/waves.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/wrap_text.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/zoom_in.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/icons/zoom_out.svg +1 -0
- medusa_style-0.3.0/medusa_style/assets/qss/base.qss +383 -0
- medusa_style-0.3.0/medusa_style/dispatch.py +172 -0
- medusa_style-0.3.0/medusa_style/mpl/__init__.py +531 -0
- medusa_style-0.3.0/medusa_style/mpl/__main__.py +64 -0
- medusa_style-0.3.0/medusa_style/palette.py +461 -0
- medusa_style-0.3.0/medusa_style/py.typed +1 -0
- medusa_style-0.3.0/medusa_style/qt/__init__.py +451 -0
- medusa_style-0.3.0/medusa_style/qt/assets.py +245 -0
- medusa_style-0.3.0/medusa_style/resources.py +140 -0
- medusa_style-0.3.0/medusa_style/style.py +203 -0
- medusa_style-0.3.0/pyproject.toml +74 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
# Temporary file for partial code execution
|
|
204
|
+
tempCodeRunnerFile.py
|
|
205
|
+
|
|
206
|
+
# Ruff stuff:
|
|
207
|
+
.ruff_cache/
|
|
208
|
+
|
|
209
|
+
# PyPI configuration file
|
|
210
|
+
.pypirc
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
216
|
+
|
|
217
|
+
# Streamlit
|
|
218
|
+
.streamlit/secrets.toml
|
|
219
|
+
|
|
220
|
+
# Claude Code
|
|
221
|
+
.claude/
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **medusa-style** are documented here. This project follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/) and [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [0.3.0] — 2026-07-01
|
|
7
|
+
|
|
8
|
+
Ground-up refactor around **rendering environments** and an orthogonal
|
|
9
|
+
**Style × Theme** model. The applied look is now a simple `(Palette, Style)`
|
|
10
|
+
pair — one axis for color, one for visual language — and the package is organized
|
|
11
|
+
by backend (`qt/`, `mpl/`) so new environments plug in without touching existing
|
|
12
|
+
code. Net **−470 lines** with more capability, and a stricter, adversarially
|
|
13
|
+
reviewed correctness bar.
|
|
14
|
+
|
|
15
|
+
### Highlights
|
|
16
|
+
- **Two independent axes.** A **theme** is a color scheme (`dark`, `light`); a
|
|
17
|
+
**style** is a visual language (`modern`, `presentation`). Any style renders in
|
|
18
|
+
any theme, and each switches on its own.
|
|
19
|
+
- **One entry point, broader reach.** `apply(target)` auto-detects its target and
|
|
20
|
+
now covers a Qt **`QWidget`** and a Matplotlib **`Axes`** in addition to a
|
|
21
|
+
`QApplication`, a `Figure`, and global theming — you never pick a backend.
|
|
22
|
+
- **Assets decoupled from code.** The ~370-line QSS moved out of Python into
|
|
23
|
+
`assets/qss/base.qss` and is fully tokenized, so a style changes UI density
|
|
24
|
+
with zero code. All assets load through one zip-safe `resources` module.
|
|
25
|
+
- **Extensible by design.** Add a theme, a style, or a whole new environment
|
|
26
|
+
(Plotly, VisPy, …) with a single `register_*` call and no edits elsewhere.
|
|
27
|
+
- **Same lazy-import discipline.** `import medusa_style` stays stdlib-only; the Qt
|
|
28
|
+
path never imports Matplotlib and vice-versa.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- `use_style()`, `current_style()`, `styles()` and the built-in **`presentation`**
|
|
32
|
+
style (larger type, thicker plot lines, roomier controls).
|
|
33
|
+
- `apply(widget)` (scoped Qt stylesheet) and `apply(ax)` (single Axes).
|
|
34
|
+
- `register_theme()`, `register_style()`, `register_environment()` extension doors.
|
|
35
|
+
- `medusa_style.mpl.style_axes()` and `medusa_style.resources.asset_names()`.
|
|
36
|
+
- One-off overrides: `apply(target, theme=…, style=…)`.
|
|
37
|
+
- `BSD-3-Clause` `LICENSE` file, a `CHANGELOG`, and a GitHub Actions workflow that
|
|
38
|
+
publishes to PyPI on release via Trusted Publishing (OIDC).
|
|
39
|
+
|
|
40
|
+
### Changed (breaking, relative to 0.2.x)
|
|
41
|
+
- The look is a `(Palette, Style)` pair — the `Theme` composite and the
|
|
42
|
+
`Typography`/`Geometry` split are gone. `Style` is one flat dataclass.
|
|
43
|
+
- `on_theme_change(cb)` → **`on_change(cb)`**; the callback now receives
|
|
44
|
+
`(palette, style)`.
|
|
45
|
+
- `current_theme()` now returns a **`Palette`** (was a `Theme`).
|
|
46
|
+
- `customize(fonts=…, spacing=…)` → **`customize(style=…)`** (`colors=` unchanged);
|
|
47
|
+
it can derive a theme and/or a style and switch both atomically.
|
|
48
|
+
- Data-viz **encodings** (categorical cycle, sequential/diverging colormaps) moved
|
|
49
|
+
from `Palette` fields to module constants in `medusa_style.palette`
|
|
50
|
+
(`CATEGORICAL_CYCLE`, `SEQUENTIAL_STOPS`, …) — they are theme- **and**
|
|
51
|
+
style-independent.
|
|
52
|
+
- `medusa_style.mpl` / `medusa_style.qt` are now packages (import paths unchanged);
|
|
53
|
+
`write_mplstyle()` signature is `(path, theme=…, style=…)`.
|
|
54
|
+
|
|
55
|
+
### Removed
|
|
56
|
+
- `medusa_style.theme` and its `ThemeManager`, `ThemeRegistry`, `QssGenerator`,
|
|
57
|
+
`MplStyleGenerator`, `QssTemplateSource` and the Qt-signal observer proxy.
|
|
58
|
+
- Pre-generated `assets/styles/*.mplstyle` and `mpl.apply_packaged_style()` —
|
|
59
|
+
generate on demand with `mpl.write_mplstyle()` (no second source of truth).
|
|
60
|
+
- Dead tokens `Palette.shadow` and `Geometry.elevation`.
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
Correctness issues surfaced by an adversarial review of the new code:
|
|
64
|
+
- A live `use_theme`/`use_style` no longer forces Fusion back on when the app was
|
|
65
|
+
built with `fusion=False`.
|
|
66
|
+
- `style_figure(surface=…)` now applies the surface to the **axes** too (cohesion
|
|
67
|
+
held on the override path).
|
|
68
|
+
- Live `use_style` now resizes chrome fonts (title/labels/ticks) on already-open
|
|
69
|
+
tracked figures — presentation's larger type reaches embedded plots.
|
|
70
|
+
- `customize(colors=…, style=…)` switches both axes in a **single** atomic step
|
|
71
|
+
(one repaint, one `on_change`) instead of a transient half-applied look.
|
|
72
|
+
- `use_theme(<active>)` re-syncs a target that had a one-off `theme=`/`style=`
|
|
73
|
+
override, instead of a no-op.
|
|
74
|
+
- `apply()` on a `QCoreApplication` (headless, no GUI) is a graceful no-op.
|
|
75
|
+
- `icon(color=…)` reorders 8-digit `#RRGGBBAA` roles to Qt's byte order like every
|
|
76
|
+
other color path; `font.monospace` uses a monospace fallback tail.
|
|
77
|
+
|
|
78
|
+
### Compatibility
|
|
79
|
+
- Python **3.11–3.13**; `matplotlib >= 3.6`, `PySide6 >= 6.5` (both core deps).
|
|
80
|
+
- Two themes (`dark` default, `light`) × two styles (`modern` default,
|
|
81
|
+
`presentation`). The colorblind-aware categorical cycle and the
|
|
82
|
+
`medusa_sequential` / `medusa_diverging` colormaps are unchanged.
|
|
83
|
+
- Verified headless + Qt-offscreen and as an installed wheel; the wheel and sdist
|
|
84
|
+
both pass `twine check`.
|
|
85
|
+
|
|
86
|
+
### Upgrading from 0.2.x
|
|
87
|
+
```diff
|
|
88
|
+
- ms.on_theme_change(lambda theme: ...theme.name)
|
|
89
|
+
+ ms.on_change(lambda palette, style: ...palette.name)
|
|
90
|
+
|
|
91
|
+
- theme = ms.current_theme() # a Theme
|
|
92
|
+
+ palette = ms.current_theme() # a Palette
|
|
93
|
+
|
|
94
|
+
- ms.customize(colors={...}, fonts={"ui_size_body": 15}, spacing={"radius_md": 10})
|
|
95
|
+
+ ms.customize(colors={...}, style={"ui_size_body": 15, "radius_md": 10})
|
|
96
|
+
|
|
97
|
+
- from medusa_style.theme import ThemeManager # removed
|
|
98
|
+
+ # use ms.apply / ms.use_theme / ms.use_style, or medusa_style.appearance
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Install
|
|
102
|
+
```bash
|
|
103
|
+
pip install medusa-style==0.3.0
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
[0.3.0]: https://github.com/medusabci/medusa-style/releases/tag/v0.3.0
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, MEDUSA (https://www.medusabci.com)
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: medusa-style
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Single source of truth for MEDUSA styling: one Style x Theme look for PySide6 QSS + Matplotlib, organized by rendering environment.
|
|
5
|
+
Project-URL: Homepage, https://www.medusabci.com/
|
|
6
|
+
Project-URL: Source, https://github.com/medusabci/medusa-style
|
|
7
|
+
Author: MEDUSA
|
|
8
|
+
License-Expression: BSD-3-Clause
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: bci,eeg,matplotlib,neuroscience,pyside6,qss,theming
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
17
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: matplotlib>=3.6
|
|
20
|
+
Requires-Dist: pyside6>=6.5
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: build; extra == 'dev'
|
|
23
|
+
Requires-Dist: numpy; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
25
|
+
Requires-Dist: twine; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# medusa-style
|
|
29
|
+
|
|
30
|
+
**Single source of truth (SSOT) for MEDUSA styling.** One look themes BOTH
|
|
31
|
+
PySide6 desktop apps (via generated QSS) and Matplotlib scientific plots (built
|
|
32
|
+
on Matplotlib's bundled seaborn baseline) — so a microvolt is the same color in
|
|
33
|
+
the dark acquisition app and on a white-paper export, and an embedded plot is
|
|
34
|
+
seamless with the panel that hosts it.
|
|
35
|
+
|
|
36
|
+
The look has **two independent axes**:
|
|
37
|
+
|
|
38
|
+
- a **theme** — the *color scheme* (`dark`, `light`, …), a `Palette`.
|
|
39
|
+
- a **style** — the *visual language* (`modern`, `presentation`, …): fonts,
|
|
40
|
+
density and plot line widths, a `Style`.
|
|
41
|
+
|
|
42
|
+
Any style renders in any theme. The code is organized by **rendering
|
|
43
|
+
environment** (`qt/`, `mpl/`, …), so adding a backend never touches the others.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install git+https://github.com/medusabci/medusa-style.git
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
That's all — PySide6 and Matplotlib are both core dependencies (every MEDUSA
|
|
52
|
+
component needs both). The only extra is `[dev]` for building/testing the
|
|
53
|
+
package itself. The seaborn *library* is **not** required: the seaborn base
|
|
54
|
+
styles ship inside Matplotlib.
|
|
55
|
+
|
|
56
|
+
## The whole API
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import medusa_style as ms
|
|
60
|
+
|
|
61
|
+
ms.apply(app) # paint a Qt application (widgets + seamless plots)
|
|
62
|
+
ms.apply(widget) # scope the theme to one Qt widget
|
|
63
|
+
ms.apply() # theme every Matplotlib figure you draw
|
|
64
|
+
ms.apply(fig) # restyle one figure in place
|
|
65
|
+
ms.apply(ax) # restyle one axes in place
|
|
66
|
+
ms.use_theme("light") # switch COLORS — restyles everything already styled
|
|
67
|
+
ms.use_style("presentation")# switch VISUAL LANGUAGE — restyles everything
|
|
68
|
+
ms.current_theme() # the active Palette
|
|
69
|
+
ms.current_style() # the active Style
|
|
70
|
+
ms.themes() # ['dark', 'light'] — for a theme picker
|
|
71
|
+
ms.styles() # ['modern', 'presentation'] — for a style picker
|
|
72
|
+
ms.customize(colors=..., style=...) # one-line house theme / style
|
|
73
|
+
ms.on_change(cb) # react to switches — cb(palette, style)
|
|
74
|
+
ms.categorical_color(7) # SSOT categorical color #7
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`apply()` dispatches on what you give it: nothing → global Matplotlib, a
|
|
78
|
+
`QApplication`/`QWidget` → Qt, a `Figure`/`Axes` → Matplotlib. You never choose a
|
|
79
|
+
backend. Whatever you pass is remembered, so a later `use_theme()`/`use_style()`
|
|
80
|
+
repaints it with no loop on your side.
|
|
81
|
+
|
|
82
|
+
### Desktop app (PySide6)
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
import sys
|
|
86
|
+
import medusa_style as ms
|
|
87
|
+
from medusa_style.qt import application, PlotPanel
|
|
88
|
+
from PySide6.QtWidgets import QMainWindow
|
|
89
|
+
|
|
90
|
+
app = application(sys.argv) # Hi-DPI handled before construction; themed
|
|
91
|
+
ms.apply() # theme Matplotlib figures too
|
|
92
|
+
|
|
93
|
+
win = QMainWindow()
|
|
94
|
+
panel = PlotPanel(toolbar=True) # seamless embed — no objectName, no restyle call
|
|
95
|
+
panel.figure.add_subplot(111).plot(range(10), color=ms.categorical_color(0))
|
|
96
|
+
win.setCentralWidget(panel)
|
|
97
|
+
win.show()
|
|
98
|
+
|
|
99
|
+
# View menu -> app AND embedded figure restyle, one line each:
|
|
100
|
+
view = win.menuBar().addMenu("View")
|
|
101
|
+
view.addAction("Light", lambda: ms.use_theme("light"))
|
|
102
|
+
view.addAction("Presentation", lambda: ms.use_style("presentation"))
|
|
103
|
+
sys.exit(app.exec())
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`PlotPanel` is the borderless Matplotlib↔Qt seam as a drop-in widget: its
|
|
107
|
+
background equals the plot's, and it tracks itself for live theme/style switches.
|
|
108
|
+
For a frame you already own, use `medusa_style.qt.embed_figure(frame, fig)`.
|
|
109
|
+
|
|
110
|
+
### Notebook / headless (Qt never imported)
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
import medusa_style as ms
|
|
114
|
+
ms.apply() # theme Matplotlib globally
|
|
115
|
+
fig = make_figure()
|
|
116
|
+
ms.apply(fig) # ...or restyle one figure
|
|
117
|
+
ms.use_theme("dark") # restyles tracked figures in place
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Importing `medusa_style` pulls in **neither** PySide6 **nor** Matplotlib until
|
|
121
|
+
you actually style something.
|
|
122
|
+
|
|
123
|
+
## Themes and styles
|
|
124
|
+
|
|
125
|
+
Two built-in **themes** (color schemes), both tuned for long medical/scientific
|
|
126
|
+
sessions:
|
|
127
|
+
|
|
128
|
+
- **dark** (default) — deep, cool, near-black canvas for dim-lab real-time use.
|
|
129
|
+
- **light** — a calm cool-gray elevation ramp for reports/paper export
|
|
130
|
+
(`plot_bg` is pure white).
|
|
131
|
+
|
|
132
|
+
Two built-in **styles** (visual languages):
|
|
133
|
+
|
|
134
|
+
- **modern** (default) — the clean flat MEDUSA look (`assets/qss/base.qss`).
|
|
135
|
+
- **presentation** — larger type, thicker plot lines and roomier controls for
|
|
136
|
+
projectors and slides (token-only; reuses `base.qss`).
|
|
137
|
+
|
|
138
|
+
The data-visualization encodings — a 16-color colorblind-aware categorical cycle
|
|
139
|
+
and the `medusa_sequential` (PSD) / `medusa_diverging` (topomap) colormaps — are
|
|
140
|
+
**theme- and style-independent**, so results stay comparable across every look.
|
|
141
|
+
|
|
142
|
+
## Customize
|
|
143
|
+
|
|
144
|
+
Zero config by default; override in one line. `colors` derive a new **theme**,
|
|
145
|
+
`style` derives a new **style**:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
ms.customize(
|
|
149
|
+
colors={"accent_primary": "#FF6600"}, # -> a registered theme
|
|
150
|
+
style={"ui_size_body": 15, "plot_line_width": 1.4}, # -> a registered style
|
|
151
|
+
) # activates + live-restyles; returns the created value(s)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Architecture & extensibility
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
medusa_style/
|
|
158
|
+
api.py the public verbs
|
|
159
|
+
dispatch.py target detection + environment routing (stdlib-only)
|
|
160
|
+
appearance.py the active (theme, style) + live switching + observer
|
|
161
|
+
palette.py the color SSOT (Palette, DARK, LIGHT) + encodings
|
|
162
|
+
style.py the visual languages (Style, MODERN, PRESENTATION)
|
|
163
|
+
resources.py zip-safe asset loading
|
|
164
|
+
qt/ the Qt environment (QSS render, PlotPanel, icons, fonts)
|
|
165
|
+
mpl/ the Matplotlib environment (rcParams, colormaps, figure styling)
|
|
166
|
+
assets/ qss/ (structural templates), icons/, fonts/, brand/
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- **Add a theme** — one `Palette(...)` literal in `palette.py` (+ `PALETTES`
|
|
170
|
+
entry) or `ms.register_theme(...)`. Works in every environment immediately.
|
|
171
|
+
- **Add a style** — one `Style(...)` literal in `style.py` (+ `STYLES` entry) or
|
|
172
|
+
`ms.register_style(...)`. A token-only style is pure data; only a structurally
|
|
173
|
+
different widget language needs its own `assets/qss/<name>.qss`.
|
|
174
|
+
- **Add an environment** (Plotly, VisPy, Bokeh, …) — a new package with a
|
|
175
|
+
`matches`/`_apply`/`reapply` trio and one `ms.register_environment(...)`. No
|
|
176
|
+
existing file changes.
|
|
177
|
+
|
|
178
|
+
## Power-user namespaces
|
|
179
|
+
|
|
180
|
+
The tiny top-level surface is enough for almost everything; reach into these only
|
|
181
|
+
when you need the knobs:
|
|
182
|
+
|
|
183
|
+
| Namespace | What's there |
|
|
184
|
+
|------------------------|--------------|
|
|
185
|
+
| `medusa_style.qt` | `application()`, `PlotPanel`, `embed_figure()`, `style_app()`, `stylesheet()`, `prepare_high_dpi()`, `icon()`, `pixmap()`, `load_fonts()` |
|
|
186
|
+
| `medusa_style.mpl` | `style_figure()`, `style_axes()`, `rcparams()`, `build_rcparams()`, `mplstyle_text()`, `register_colormaps()`, `sequential_cmap()`, `diverging_cmap()` |
|
|
187
|
+
| `medusa_style.palette` | the raw SSOT colors: `Palette`, `DARK`, `LIGHT`, `CATEGORICAL_CYCLE`, WCAG utils |
|
|
188
|
+
| `medusa_style.style` | the visual languages: `Style`, `MODERN`, `PRESENTATION` |
|
|
189
|
+
|
|
190
|
+
`medusa_style.qt` imports PySide6 (never Matplotlib); `medusa_style.mpl` imports
|
|
191
|
+
Matplotlib (never Qt). `import medusa_style` loads neither until you style
|
|
192
|
+
something, so a headless figure-export job never pulls in Qt.
|
|
193
|
+
|
|
194
|
+
## Icons, fonts & brand assets
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from medusa_style import qt
|
|
198
|
+
|
|
199
|
+
button.setIcon(qt.icon("save_as")) # theme-agnostic action icon
|
|
200
|
+
button.setIcon(qt.icon("delete_forever", color="error")) # ...or a semantic color
|
|
201
|
+
splash = qt.pixmap("medusa_splash") # brand images (splash/login/about)
|
|
202
|
+
win.setWindowIcon(qt.app_icon()) # the MEDUSA app icon
|
|
203
|
+
path = qt.asset_path("fonts/Roboto-Regular.ttf") # any bundled file, real path
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**`qt.icon(name)` icons are theme-agnostic.** The color is not baked in: each
|
|
207
|
+
icon recolors to the active theme on every repaint and changes with Qt's state
|
|
208
|
+
automatically — a disabled widget dims its icon, and a theme switch repaints it
|
|
209
|
+
live, with no re-fetch. Pass `color=` (a palette role like `"error"`/`"accent_primary"`,
|
|
210
|
+
or a `"#RRGGBB"` literal) to force a specific color; it still dims when disabled.
|
|
211
|
+
|
|
212
|
+
`qt.application()` does the common setup for you: it registers the bundled fonts
|
|
213
|
+
(Roboto family, Abel, Dense, Womby) and sets the MEDUSA window icon app-wide
|
|
214
|
+
(both opt-out via `application(set_icon=False, load_bundled_fonts=False)`).
|
|
215
|
+
|
|
216
|
+
## Packaging
|
|
217
|
+
|
|
218
|
+
All runtime data lives under `medusa_style/assets/` — `qss/` (the structural QSS
|
|
219
|
+
templates), `icons/`, `fonts/` and `brand/`. They are declared in `pyproject.toml`
|
|
220
|
+
so the wheel always includes them, while the editable `.psd` design sources stay
|
|
221
|
+
in the repo-level `design_source/` directory (outside the package, never shipped):
|
|
222
|
+
|
|
223
|
+
```toml
|
|
224
|
+
[tool.hatch.build.targets.wheel]
|
|
225
|
+
packages = ["medusa_style"]
|
|
226
|
+
artifacts = [
|
|
227
|
+
"medusa_style/assets/**/*.qss",
|
|
228
|
+
"medusa_style/assets/**/*.svg",
|
|
229
|
+
"medusa_style/assets/**/*.png",
|
|
230
|
+
"medusa_style/assets/**/*.ico",
|
|
231
|
+
"medusa_style/assets/**/*.ttf",
|
|
232
|
+
"medusa_style/assets/**/*.otf",
|
|
233
|
+
"medusa_style/py.typed",
|
|
234
|
+
]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
At runtime the assets are loaded zip-safely via `medusa_style.resources` (which
|
|
238
|
+
wraps `importlib.resources`), so it works even from inside a zipped wheel.
|
|
239
|
+
Matplotlib `.mplstyle` files are generated on demand
|
|
240
|
+
(`medusa_style.mpl.write_mplstyle(...)`) rather than shipped, so they can never
|
|
241
|
+
drift from the SSOT.
|
|
242
|
+
```
|
|
243
|
+
```
|