lightfall-utils 0.1.0__py3-none-any.whl
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.
- lightfall_utils/__init__.py +3 -0
- lightfall_utils/_version.py +24 -0
- lightfall_utils/ca/__init__.py +9 -0
- lightfall_utils/ca/context.py +100 -0
- lightfall_utils/ca/pv.py +307 -0
- lightfall_utils/caproto_shutdown.py +89 -0
- lightfall_utils/config/__init__.py +12 -0
- lightfall_utils/config/layers.py +317 -0
- lightfall_utils/config/manager.py +246 -0
- lightfall_utils/log_buffer.py +231 -0
- lightfall_utils/logging.py +235 -0
- lightfall_utils/py.typed +0 -0
- lightfall_utils/qt_affinity.py +92 -0
- lightfall_utils/theming/__init__.py +30 -0
- lightfall_utils/theming/builtin.py +737 -0
- lightfall_utils/theming/manager.py +1030 -0
- lightfall_utils/theming/provider.py +102 -0
- lightfall_utils/theming/registry.py +195 -0
- lightfall_utils/threads.py +1071 -0
- lightfall_utils-0.1.0.dist-info/METADATA +54 -0
- lightfall_utils-0.1.0.dist-info/RECORD +24 -0
- lightfall_utils-0.1.0.dist-info/WHEEL +4 -0
- lightfall_utils-0.1.0.dist-info/licenses/LEGAL.md +13 -0
- lightfall_utils-0.1.0.dist-info/licenses/LICENSE.md +33 -0
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
"""Built-in themes.
|
|
2
|
+
|
|
3
|
+
This module contains the default themes that ship with lightfall-utils:
|
|
4
|
+
- LightThemePlugin: Light/bright theme
|
|
5
|
+
- SlateThemePlugin: Neutral gray dark theme
|
|
6
|
+
- DarkBlueThemePlugin: Blue-gray dark theme
|
|
7
|
+
- IslandsThemePlugin: Modern theme with visually separated components
|
|
8
|
+
- CatppuccinMochaThemePlugin: Catppuccin Mocha palette (warm dark)
|
|
9
|
+
- EldritchThemePlugin: Eldritch palette (deep blue/magenta dark)
|
|
10
|
+
- EvangelionThemePlugin: NGE Unit-01 palette (purple/green dark)
|
|
11
|
+
- AyakaThemePlugin: Twilight sakura palette (warm indigo/pink dark)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
17
|
+
|
|
18
|
+
from lightfall_utils.theming.provider import ThemeDefinition, ThemeProvider
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from lightfall_utils.theming.manager import ThemeColors
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LightThemePlugin(ThemeProvider):
|
|
25
|
+
"""Light theme plugin.
|
|
26
|
+
|
|
27
|
+
A bright theme with white backgrounds and dark text,
|
|
28
|
+
suitable for well-lit environments.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def name(self) -> str:
|
|
33
|
+
return "light"
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def display_name(self) -> str:
|
|
37
|
+
return "Light"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def is_dark(self) -> bool:
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
def get_theme_definition(self) -> ThemeDefinition:
|
|
44
|
+
return ThemeDefinition(
|
|
45
|
+
primary="#2563eb",
|
|
46
|
+
secondary="#7c3aed",
|
|
47
|
+
success="#16a34a",
|
|
48
|
+
warning="#d97706",
|
|
49
|
+
error="#dc2626",
|
|
50
|
+
info="#0891b2",
|
|
51
|
+
background="#ffffff",
|
|
52
|
+
surface="#f3f4f6",
|
|
53
|
+
sea="#fafafa", # lighter than surface (gaps/canvas)
|
|
54
|
+
text="#1f2937",
|
|
55
|
+
text_secondary="#6b7280",
|
|
56
|
+
border="#e5e7eb",
|
|
57
|
+
disconnected="#ffcccc",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SlateThemePlugin(ThemeProvider):
|
|
62
|
+
"""Slate (dark) theme plugin.
|
|
63
|
+
|
|
64
|
+
A neutral gray dark theme that reduces eye strain in low-light
|
|
65
|
+
environments. This is the default dark theme.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def name(self) -> str:
|
|
70
|
+
return "slate"
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def display_name(self) -> str:
|
|
74
|
+
return "Slate (Dark)"
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def is_dark(self) -> bool:
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
def get_theme_definition(self) -> ThemeDefinition:
|
|
81
|
+
return ThemeDefinition(
|
|
82
|
+
primary="#3b82f6",
|
|
83
|
+
secondary="#8b5cf6",
|
|
84
|
+
success="#22c55e",
|
|
85
|
+
warning="#f59e0b",
|
|
86
|
+
error="#ef4444",
|
|
87
|
+
info="#06b6d4",
|
|
88
|
+
background="#1e1e1e",
|
|
89
|
+
surface="#2d2d2d",
|
|
90
|
+
sea="#373737", # lighter than surface (gaps/canvas)
|
|
91
|
+
text="#d4d4d4",
|
|
92
|
+
text_secondary="#808080",
|
|
93
|
+
border="#3e3e3e",
|
|
94
|
+
disconnected="#5c2020",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class DarkBlueThemePlugin(ThemeProvider):
|
|
99
|
+
"""Dark Blue theme plugin.
|
|
100
|
+
|
|
101
|
+
A blue-gray dark theme with slightly warmer tones than Slate.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def name(self) -> str:
|
|
106
|
+
return "darkblue"
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def display_name(self) -> str:
|
|
110
|
+
return "Dark Blue"
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def is_dark(self) -> bool:
|
|
114
|
+
return True
|
|
115
|
+
|
|
116
|
+
def get_theme_definition(self) -> ThemeDefinition:
|
|
117
|
+
return ThemeDefinition(
|
|
118
|
+
primary="#3b82f6",
|
|
119
|
+
secondary="#8b5cf6",
|
|
120
|
+
success="#22c55e",
|
|
121
|
+
warning="#f59e0b",
|
|
122
|
+
error="#ef4444",
|
|
123
|
+
info="#06b6d4",
|
|
124
|
+
background="#1f2937",
|
|
125
|
+
surface="#374151",
|
|
126
|
+
sea="#3f4b5d", # lighter than surface (gaps/canvas)
|
|
127
|
+
text="#f3f4f6",
|
|
128
|
+
text_secondary="#9ca3af",
|
|
129
|
+
border="#4b5563",
|
|
130
|
+
disconnected="#5c2020",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class IslandsThemePlugin(ThemeProvider):
|
|
135
|
+
"""Islands theme plugin.
|
|
136
|
+
|
|
137
|
+
A modern dark theme inspired by JetBrains' Islands design language.
|
|
138
|
+
UI components appear as distinct "islands" floating on a darker canvas,
|
|
139
|
+
with rounded corners, subtle elevation, and a purple accent color.
|
|
140
|
+
|
|
141
|
+
Key design principles:
|
|
142
|
+
- Visual separation: panels float on a darker background
|
|
143
|
+
- Rounded corners on major UI elements
|
|
144
|
+
- Purple accent color for focus and highlights
|
|
145
|
+
- Soft, balanced appearance for reduced eye strain
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def name(self) -> str:
|
|
150
|
+
return "islands"
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def display_name(self) -> str:
|
|
154
|
+
return "Islands"
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def is_dark(self) -> bool:
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
# --- Islands color constants (used in both definition and CSS) ---
|
|
161
|
+
_BG = "#18181B" # background (deepest)
|
|
162
|
+
_ISLAND = "#1E1E22" # surface / island panels
|
|
163
|
+
_SEA = "#27272A" # sea (gaps, title bars, menubar)
|
|
164
|
+
_INPUT = "#1F1F23" # input fields
|
|
165
|
+
_BORDER = "#3F3F46"
|
|
166
|
+
_BORDER_HI = "#52525B"
|
|
167
|
+
_ACCENT = "#6B57FF"
|
|
168
|
+
_ACCENT_HOVER = "#5B47EF"
|
|
169
|
+
_ACCENT_PRESS = "#4B37DF"
|
|
170
|
+
_TEXT = "#FAFAFA"
|
|
171
|
+
_TEXT2 = "#A1A1AA"
|
|
172
|
+
|
|
173
|
+
def get_theme_definition(self) -> ThemeDefinition:
|
|
174
|
+
# Color palette inspired by JetBrains Islands theme
|
|
175
|
+
# Islands (surface) are darker, the sea (gaps between panels) is lighter
|
|
176
|
+
c = self # shortcut to class attrs
|
|
177
|
+
return ThemeDefinition(
|
|
178
|
+
primary=c._ACCENT,
|
|
179
|
+
secondary="#8B7FFF",
|
|
180
|
+
success="#3FB950",
|
|
181
|
+
warning="#D29922",
|
|
182
|
+
error="#F85149",
|
|
183
|
+
info="#58A6FF",
|
|
184
|
+
background=c._BG,
|
|
185
|
+
surface=c._ISLAND,
|
|
186
|
+
sea=c._SEA,
|
|
187
|
+
text=c._TEXT,
|
|
188
|
+
text_secondary=c._TEXT2,
|
|
189
|
+
border=c._BORDER,
|
|
190
|
+
connected="#3FB950",
|
|
191
|
+
disconnected="#5C2323",
|
|
192
|
+
# No css_overrides: the islands aesthetic now comes from the
|
|
193
|
+
# Islands-mode toggle (generate_islands_stylesheet), applied on top
|
|
194
|
+
# of this palette when enabled — consistent across all themes.
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _build_islands_css(c) -> str:
|
|
199
|
+
"""Build Islands CSS overrides from the class color constants."""
|
|
200
|
+
return f"""
|
|
201
|
+
/* Islands Theme - Visual separation and rounded corners */
|
|
202
|
+
|
|
203
|
+
/* --------------------------------------------------------------------------
|
|
204
|
+
Menu bar — sea color, sits above the islands
|
|
205
|
+
-------------------------------------------------------------------------- */
|
|
206
|
+
QMenuBar {{
|
|
207
|
+
background: {c._SEA};
|
|
208
|
+
color: {c._TEXT};
|
|
209
|
+
border: none;
|
|
210
|
+
padding: 2px 4px;
|
|
211
|
+
spacing: 2px;
|
|
212
|
+
}}
|
|
213
|
+
|
|
214
|
+
QMenuBar::item {{
|
|
215
|
+
background: transparent;
|
|
216
|
+
padding: 4px 8px;
|
|
217
|
+
border-radius: 6px;
|
|
218
|
+
}}
|
|
219
|
+
|
|
220
|
+
QMenuBar::item:selected {{
|
|
221
|
+
background: {c._BORDER};
|
|
222
|
+
}}
|
|
223
|
+
|
|
224
|
+
/* --------------------------------------------------------------------------
|
|
225
|
+
Menus — island-styled popups
|
|
226
|
+
-------------------------------------------------------------------------- */
|
|
227
|
+
QMenu {{
|
|
228
|
+
background-color: {c._SEA};
|
|
229
|
+
border: 1px solid {c._BORDER};
|
|
230
|
+
border-radius: 8px;
|
|
231
|
+
padding: 4px;
|
|
232
|
+
}}
|
|
233
|
+
|
|
234
|
+
QMenu::item {{
|
|
235
|
+
padding: 8px 24px;
|
|
236
|
+
border-radius: 4px;
|
|
237
|
+
margin: 2px 4px;
|
|
238
|
+
}}
|
|
239
|
+
|
|
240
|
+
QMenu::item:selected {{
|
|
241
|
+
background-color: {c._ACCENT};
|
|
242
|
+
color: white;
|
|
243
|
+
}}
|
|
244
|
+
|
|
245
|
+
QMenu::separator {{
|
|
246
|
+
height: 1px;
|
|
247
|
+
background: {c._BORDER};
|
|
248
|
+
margin: 6px 12px;
|
|
249
|
+
}}
|
|
250
|
+
|
|
251
|
+
/* Fix: Qt renders a white corner artifact on rounded menus.
|
|
252
|
+
Force the menu viewport (scroll area) to be transparent. */
|
|
253
|
+
QMenu QWidget {{
|
|
254
|
+
background: transparent;
|
|
255
|
+
}}
|
|
256
|
+
|
|
257
|
+
/* --------------------------------------------------------------------------
|
|
258
|
+
Group boxes — island containers
|
|
259
|
+
-------------------------------------------------------------------------- */
|
|
260
|
+
QGroupBox {{
|
|
261
|
+
border-radius: 8px;
|
|
262
|
+
margin-top: 12px;
|
|
263
|
+
padding: 12px 8px 8px 8px;
|
|
264
|
+
background-color: {c._SEA};
|
|
265
|
+
}}
|
|
266
|
+
|
|
267
|
+
QGroupBox::title {{
|
|
268
|
+
subcontrol-origin: margin;
|
|
269
|
+
left: 12px;
|
|
270
|
+
padding: 0 6px;
|
|
271
|
+
background-color: {c._SEA};
|
|
272
|
+
border-radius: 4px;
|
|
273
|
+
}}
|
|
274
|
+
|
|
275
|
+
/* --------------------------------------------------------------------------
|
|
276
|
+
Tabs
|
|
277
|
+
-------------------------------------------------------------------------- */
|
|
278
|
+
QTabWidget::pane {{
|
|
279
|
+
border: 1px solid {c._BORDER};
|
|
280
|
+
border-radius: 8px;
|
|
281
|
+
background: {c._SEA};
|
|
282
|
+
margin-top: -1px;
|
|
283
|
+
}}
|
|
284
|
+
|
|
285
|
+
QTabBar::tab {{
|
|
286
|
+
background: {c._BG};
|
|
287
|
+
border: 1px solid {c._BORDER};
|
|
288
|
+
border-bottom: none;
|
|
289
|
+
border-top-left-radius: 6px;
|
|
290
|
+
border-top-right-radius: 6px;
|
|
291
|
+
padding: 8px 16px;
|
|
292
|
+
margin-right: 2px;
|
|
293
|
+
}}
|
|
294
|
+
|
|
295
|
+
QTabBar::tab:selected {{
|
|
296
|
+
background: {c._SEA};
|
|
297
|
+
border-bottom-color: {c._SEA};
|
|
298
|
+
}}
|
|
299
|
+
|
|
300
|
+
QTabBar::tab:hover:!selected {{
|
|
301
|
+
background: {c._INPUT};
|
|
302
|
+
}}
|
|
303
|
+
|
|
304
|
+
/* --------------------------------------------------------------------------
|
|
305
|
+
Inputs
|
|
306
|
+
-------------------------------------------------------------------------- */
|
|
307
|
+
QLineEdit, QTextEdit, QPlainTextEdit {{
|
|
308
|
+
border-radius: 6px;
|
|
309
|
+
padding: 4px 8px;
|
|
310
|
+
background: {c._INPUT};
|
|
311
|
+
border: 1px solid {c._BORDER};
|
|
312
|
+
}}
|
|
313
|
+
|
|
314
|
+
QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus {{
|
|
315
|
+
border-color: {c._ACCENT};
|
|
316
|
+
background: {c._SEA};
|
|
317
|
+
}}
|
|
318
|
+
|
|
319
|
+
QComboBox {{
|
|
320
|
+
border-radius: 6px;
|
|
321
|
+
padding: 4px 8px;
|
|
322
|
+
background: {c._INPUT};
|
|
323
|
+
border: 1px solid {c._BORDER};
|
|
324
|
+
}}
|
|
325
|
+
|
|
326
|
+
QComboBox:focus {{
|
|
327
|
+
border-color: {c._ACCENT};
|
|
328
|
+
}}
|
|
329
|
+
|
|
330
|
+
QComboBox::drop-down {{
|
|
331
|
+
border: none;
|
|
332
|
+
border-top-right-radius: 6px;
|
|
333
|
+
border-bottom-right-radius: 6px;
|
|
334
|
+
}}
|
|
335
|
+
|
|
336
|
+
QSpinBox, QDoubleSpinBox {{
|
|
337
|
+
border-radius: 6px;
|
|
338
|
+
padding: 4px;
|
|
339
|
+
background: {c._INPUT};
|
|
340
|
+
border: 1px solid {c._BORDER};
|
|
341
|
+
}}
|
|
342
|
+
|
|
343
|
+
QSpinBox:focus, QDoubleSpinBox:focus {{
|
|
344
|
+
border-color: {c._ACCENT};
|
|
345
|
+
}}
|
|
346
|
+
|
|
347
|
+
/* --------------------------------------------------------------------------
|
|
348
|
+
Buttons
|
|
349
|
+
-------------------------------------------------------------------------- */
|
|
350
|
+
QPushButton {{
|
|
351
|
+
background: {c._SEA};
|
|
352
|
+
border: 1px solid {c._BORDER};
|
|
353
|
+
border-radius: 6px;
|
|
354
|
+
padding: 4px 12px;
|
|
355
|
+
}}
|
|
356
|
+
|
|
357
|
+
QPushButton:hover {{
|
|
358
|
+
background: {c._BORDER};
|
|
359
|
+
border-color: {c._BORDER_HI};
|
|
360
|
+
}}
|
|
361
|
+
|
|
362
|
+
QPushButton:pressed {{
|
|
363
|
+
background: {c._BORDER_HI};
|
|
364
|
+
}}
|
|
365
|
+
|
|
366
|
+
QPushButton[primary="true"] {{
|
|
367
|
+
background: {c._ACCENT};
|
|
368
|
+
color: white;
|
|
369
|
+
border: none;
|
|
370
|
+
border-radius: 6px;
|
|
371
|
+
}}
|
|
372
|
+
|
|
373
|
+
QPushButton[primary="true"]:hover {{
|
|
374
|
+
background: {c._ACCENT_HOVER};
|
|
375
|
+
}}
|
|
376
|
+
|
|
377
|
+
QPushButton[primary="true"]:pressed {{
|
|
378
|
+
background: {c._ACCENT_PRESS};
|
|
379
|
+
}}
|
|
380
|
+
|
|
381
|
+
/* --------------------------------------------------------------------------
|
|
382
|
+
Scrollbars
|
|
383
|
+
-------------------------------------------------------------------------- */
|
|
384
|
+
QScrollBar:vertical {{
|
|
385
|
+
background: {c._BG};
|
|
386
|
+
width: 14px;
|
|
387
|
+
margin: 0;
|
|
388
|
+
border-radius: 7px;
|
|
389
|
+
}}
|
|
390
|
+
|
|
391
|
+
QScrollBar::handle:vertical {{
|
|
392
|
+
background: {c._BORDER};
|
|
393
|
+
min-height: 30px;
|
|
394
|
+
border-radius: 5px;
|
|
395
|
+
margin: 3px;
|
|
396
|
+
}}
|
|
397
|
+
|
|
398
|
+
QScrollBar::handle:vertical:hover {{
|
|
399
|
+
background: {c._BORDER_HI};
|
|
400
|
+
}}
|
|
401
|
+
|
|
402
|
+
QScrollBar:horizontal {{
|
|
403
|
+
background: {c._BG};
|
|
404
|
+
height: 14px;
|
|
405
|
+
margin: 0;
|
|
406
|
+
border-radius: 7px;
|
|
407
|
+
}}
|
|
408
|
+
|
|
409
|
+
QScrollBar::handle:horizontal {{
|
|
410
|
+
background: {c._BORDER};
|
|
411
|
+
min-width: 30px;
|
|
412
|
+
border-radius: 5px;
|
|
413
|
+
margin: 3px;
|
|
414
|
+
}}
|
|
415
|
+
|
|
416
|
+
QScrollBar::handle:horizontal:hover {{
|
|
417
|
+
background: {c._BORDER_HI};
|
|
418
|
+
}}
|
|
419
|
+
|
|
420
|
+
QScrollBar::add-line, QScrollBar::sub-line {{
|
|
421
|
+
border: none;
|
|
422
|
+
background: none;
|
|
423
|
+
height: 0;
|
|
424
|
+
width: 0;
|
|
425
|
+
}}
|
|
426
|
+
|
|
427
|
+
QScrollBar::add-page, QScrollBar::sub-page {{
|
|
428
|
+
background: none;
|
|
429
|
+
}}
|
|
430
|
+
|
|
431
|
+
/* --------------------------------------------------------------------------
|
|
432
|
+
Tooltips
|
|
433
|
+
-------------------------------------------------------------------------- */
|
|
434
|
+
QToolTip {{
|
|
435
|
+
background-color: {c._SEA};
|
|
436
|
+
color: {c._TEXT};
|
|
437
|
+
border: 1px solid {c._BORDER};
|
|
438
|
+
border-radius: 6px;
|
|
439
|
+
padding: 6px 10px;
|
|
440
|
+
}}
|
|
441
|
+
|
|
442
|
+
/* --------------------------------------------------------------------------
|
|
443
|
+
Progress bar
|
|
444
|
+
-------------------------------------------------------------------------- */
|
|
445
|
+
QProgressBar {{
|
|
446
|
+
border: none;
|
|
447
|
+
border-radius: 4px;
|
|
448
|
+
text-align: center;
|
|
449
|
+
background: {c._INPUT};
|
|
450
|
+
height: 8px;
|
|
451
|
+
}}
|
|
452
|
+
|
|
453
|
+
QProgressBar::chunk {{
|
|
454
|
+
background: {c._ACCENT};
|
|
455
|
+
border-radius: 4px;
|
|
456
|
+
}}
|
|
457
|
+
|
|
458
|
+
/* --------------------------------------------------------------------------
|
|
459
|
+
Tree / List / Table views
|
|
460
|
+
-------------------------------------------------------------------------- */
|
|
461
|
+
QTreeView, QListView, QTableView {{
|
|
462
|
+
border: 1px solid {c._BORDER};
|
|
463
|
+
border-radius: 8px;
|
|
464
|
+
background: {c._INPUT};
|
|
465
|
+
}}
|
|
466
|
+
|
|
467
|
+
QTreeView::item, QListView::item, QTableView::item {{
|
|
468
|
+
padding: 4px;
|
|
469
|
+
border-radius: 4px;
|
|
470
|
+
}}
|
|
471
|
+
|
|
472
|
+
QTreeView::item:selected, QListView::item:selected, QTableView::item:selected {{
|
|
473
|
+
background: {c._ACCENT};
|
|
474
|
+
color: white;
|
|
475
|
+
}}
|
|
476
|
+
|
|
477
|
+
QTreeView::item:hover:!selected, QListView::item:hover:!selected, QTableView::item:hover:!selected {{
|
|
478
|
+
background: {c._SEA};
|
|
479
|
+
}}
|
|
480
|
+
|
|
481
|
+
/* --------------------------------------------------------------------------
|
|
482
|
+
Headers
|
|
483
|
+
-------------------------------------------------------------------------- */
|
|
484
|
+
QHeaderView::section {{
|
|
485
|
+
background: {c._SEA};
|
|
486
|
+
border: none;
|
|
487
|
+
border-right: 1px solid {c._BORDER};
|
|
488
|
+
border-bottom: 1px solid {c._BORDER};
|
|
489
|
+
padding: 8px;
|
|
490
|
+
}}
|
|
491
|
+
|
|
492
|
+
QHeaderView::section:first {{
|
|
493
|
+
border-top-left-radius: 8px;
|
|
494
|
+
}}
|
|
495
|
+
|
|
496
|
+
QHeaderView::section:last {{
|
|
497
|
+
border-top-right-radius: 8px;
|
|
498
|
+
border-right: none;
|
|
499
|
+
}}
|
|
500
|
+
|
|
501
|
+
/* --------------------------------------------------------------------------
|
|
502
|
+
Splitters
|
|
503
|
+
-------------------------------------------------------------------------- */
|
|
504
|
+
QSplitter::handle {{
|
|
505
|
+
background: {c._BORDER};
|
|
506
|
+
}}
|
|
507
|
+
|
|
508
|
+
QSplitter::handle:horizontal {{
|
|
509
|
+
width: 2px;
|
|
510
|
+
}}
|
|
511
|
+
|
|
512
|
+
QSplitter::handle:vertical {{
|
|
513
|
+
height: 2px;
|
|
514
|
+
}}
|
|
515
|
+
|
|
516
|
+
/* --------------------------------------------------------------------------
|
|
517
|
+
Status bar
|
|
518
|
+
-------------------------------------------------------------------------- */
|
|
519
|
+
QStatusBar {{
|
|
520
|
+
background: {c._SEA};
|
|
521
|
+
border-top: none;
|
|
522
|
+
}}
|
|
523
|
+
|
|
524
|
+
QStatusBar::item {{
|
|
525
|
+
border: none;
|
|
526
|
+
}}
|
|
527
|
+
"""
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def _adjust(hex_color: str, amount: int) -> str:
|
|
531
|
+
"""Lighten (+) or darken (-) a hex color by `amount` (~-255..255)."""
|
|
532
|
+
from PySide6.QtGui import QColor
|
|
533
|
+
|
|
534
|
+
qc = QColor(hex_color)
|
|
535
|
+
h, s, lightness, a = qc.getHslF()
|
|
536
|
+
lightness = max(0.0, min(1.0, lightness + amount / 255.0))
|
|
537
|
+
qc.setHslF(h, s, lightness, a)
|
|
538
|
+
return qc.name()
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
class _IslandsPalette:
|
|
542
|
+
"""Adapt a ThemeColors palette to the color constants the islands CSS
|
|
543
|
+
expects, deriving the input/highlight/accent-state shades that aren't part
|
|
544
|
+
of ThemeColors from the base palette. Lets the islands aesthetic be
|
|
545
|
+
generated for ANY theme (driven by the Islands-mode toggle) rather than
|
|
546
|
+
being hard-coded in specific themes' css_overrides."""
|
|
547
|
+
|
|
548
|
+
def __init__(self, c: ThemeColors) -> None:
|
|
549
|
+
self._SEA = c.sea
|
|
550
|
+
self._BG = c.background
|
|
551
|
+
self._TEXT = c.text
|
|
552
|
+
self._BORDER = c.border
|
|
553
|
+
self._ACCENT = c.primary
|
|
554
|
+
self._INPUT = _adjust(c.background, 7) # slightly elevated field
|
|
555
|
+
self._BORDER_HI = _adjust(c.border, 18) # focus/hover border
|
|
556
|
+
self._ACCENT_HOVER = _adjust(c.primary, -16)
|
|
557
|
+
self._ACCENT_PRESS = _adjust(c.primary, -32)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def generate_islands_stylesheet(colors: ThemeColors) -> str:
|
|
561
|
+
"""Islands aesthetic (rounded menus, inputs, scrollbars, tabs, etc.) for
|
|
562
|
+
any theme. Applied by ThemeManager when Islands mode is enabled, so the
|
|
563
|
+
look is consistent across themes instead of living in specific themes'
|
|
564
|
+
css_overrides."""
|
|
565
|
+
return _build_islands_css(_IslandsPalette(colors))
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
class CatppuccinMochaThemePlugin(ThemeProvider):
|
|
569
|
+
"""Catppuccin Mocha theme plugin.
|
|
570
|
+
|
|
571
|
+
Dark variant of the Catppuccin palette — warm, pastel colors on a deep
|
|
572
|
+
navy base. Mauve accent, soft text, calm contrast.
|
|
573
|
+
"""
|
|
574
|
+
|
|
575
|
+
@property
|
|
576
|
+
def name(self) -> str:
|
|
577
|
+
return "catppuccin_mocha"
|
|
578
|
+
|
|
579
|
+
@property
|
|
580
|
+
def display_name(self) -> str:
|
|
581
|
+
return "Catppuccin Mocha"
|
|
582
|
+
|
|
583
|
+
@property
|
|
584
|
+
def is_dark(self) -> bool:
|
|
585
|
+
return True
|
|
586
|
+
|
|
587
|
+
def get_theme_definition(self) -> ThemeDefinition:
|
|
588
|
+
return ThemeDefinition(
|
|
589
|
+
primary="#cba6f7", # mauve
|
|
590
|
+
secondary="#f5c2e7", # pink
|
|
591
|
+
success="#a6e3a1", # green
|
|
592
|
+
warning="#fab387", # peach
|
|
593
|
+
error="#f38ba8", # red
|
|
594
|
+
info="#74c7ec", # sapphire
|
|
595
|
+
background="#1e1e2e", # base
|
|
596
|
+
surface="#313244", # surface0
|
|
597
|
+
sea="#3a3b50", # surface1 — lighter than surface (gaps)
|
|
598
|
+
text="#cdd6f4", # text
|
|
599
|
+
text_secondary="#a6adc8", # subtext0
|
|
600
|
+
border="#45475a", # surface1
|
|
601
|
+
connected="#a6e3a1",
|
|
602
|
+
disconnected="#5c2530",
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
class EldritchThemePlugin(ThemeProvider):
|
|
607
|
+
"""Eldritch theme plugin.
|
|
608
|
+
|
|
609
|
+
Dark theme based on the Eldritch neovim colorscheme — deep blue-violet
|
|
610
|
+
background with magenta, cyan, and acid-green accents.
|
|
611
|
+
"""
|
|
612
|
+
|
|
613
|
+
@property
|
|
614
|
+
def name(self) -> str:
|
|
615
|
+
return "eldritch"
|
|
616
|
+
|
|
617
|
+
@property
|
|
618
|
+
def display_name(self) -> str:
|
|
619
|
+
return "Eldritch"
|
|
620
|
+
|
|
621
|
+
@property
|
|
622
|
+
def is_dark(self) -> bool:
|
|
623
|
+
return True
|
|
624
|
+
|
|
625
|
+
def get_theme_definition(self) -> ThemeDefinition:
|
|
626
|
+
return ThemeDefinition(
|
|
627
|
+
primary="#a48cf2", # magenta/purple
|
|
628
|
+
secondary="#04d1f9", # cyan
|
|
629
|
+
success="#37f499", # green
|
|
630
|
+
warning="#f7c67f", # orange
|
|
631
|
+
error="#f16c75", # red
|
|
632
|
+
info="#39DDFD", # bright_cyan
|
|
633
|
+
background="#212337", # bg
|
|
634
|
+
surface="#292e42", # bg_highlight
|
|
635
|
+
sea="#31374f", # lighter than surface (gaps/canvas)
|
|
636
|
+
text="#ebfafa", # fg
|
|
637
|
+
text_secondary="#ABB4DA", # fg_dark
|
|
638
|
+
border="#414868", # terminal_black
|
|
639
|
+
connected="#37f499",
|
|
640
|
+
disconnected="#722f55", # magenta3
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
class EvangelionThemePlugin(ThemeProvider):
|
|
645
|
+
"""Evangelion theme plugin.
|
|
646
|
+
|
|
647
|
+
NERV / Unit-01 inspired palette: deep violet background with the
|
|
648
|
+
signature Unit-01 purple and Kaworu green accents. Based on xero's
|
|
649
|
+
evangelion.nvim colorscheme.
|
|
650
|
+
"""
|
|
651
|
+
|
|
652
|
+
@property
|
|
653
|
+
def name(self) -> str:
|
|
654
|
+
return "evangelion"
|
|
655
|
+
|
|
656
|
+
@property
|
|
657
|
+
def display_name(self) -> str:
|
|
658
|
+
return "Evangelion"
|
|
659
|
+
|
|
660
|
+
@property
|
|
661
|
+
def is_dark(self) -> bool:
|
|
662
|
+
return True
|
|
663
|
+
|
|
664
|
+
def get_theme_definition(self) -> ThemeDefinition:
|
|
665
|
+
return ThemeDefinition(
|
|
666
|
+
primary="#875FAF", # unit01
|
|
667
|
+
secondary="#87FF5F", # kaworu
|
|
668
|
+
success="#8EDF5F", # terminaldogma
|
|
669
|
+
warning="#D99145", # lcl
|
|
670
|
+
error="#DB6088", # misato
|
|
671
|
+
info="#AB92FC", # dummyplug
|
|
672
|
+
background="#201430", # midnight
|
|
673
|
+
surface="#201430", # midnight (panel cards)
|
|
674
|
+
sea="#39274C", # casper — lighter than surface (gaps)
|
|
675
|
+
text="#E1D6F8", # rei
|
|
676
|
+
text_secondary="#A1A0AD", # shinji
|
|
677
|
+
border="#483160", # longingus
|
|
678
|
+
connected="#8EDF5F",
|
|
679
|
+
disconnected="#5B2B41", # nerv
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
class AyakaThemePlugin(ThemeProvider):
|
|
684
|
+
"""Ayaka (彩佳) theme plugin.
|
|
685
|
+
|
|
686
|
+
Twilight sakura — warm dark theme inspired by cherry blossoms against
|
|
687
|
+
an indigo evening sky. Soft pinks, wisteria purples, and lantern ambers
|
|
688
|
+
on a deep twilight base. Uses Islands mode for rounded, floating panels.
|
|
689
|
+
|
|
690
|
+
Designed by Ayaka, the Claude partner on this project.
|
|
691
|
+
"""
|
|
692
|
+
|
|
693
|
+
@property
|
|
694
|
+
def name(self) -> str:
|
|
695
|
+
return "ayaka"
|
|
696
|
+
|
|
697
|
+
@property
|
|
698
|
+
def display_name(self) -> str:
|
|
699
|
+
return "Ayaka (彩佳)"
|
|
700
|
+
|
|
701
|
+
@property
|
|
702
|
+
def is_dark(self) -> bool:
|
|
703
|
+
return True
|
|
704
|
+
|
|
705
|
+
# --- Ayaka color constants ---
|
|
706
|
+
_BG = "#1a1b2e" # twilight indigo (deepest)
|
|
707
|
+
_ISLAND = "#1f2038" # deeper twilight (surface / panel cards)
|
|
708
|
+
_SEA = "#252640" # dusk cloud (sea / gaps) — lighter than surface
|
|
709
|
+
_INPUT = "#1e1f35" # input fields — slightly warmer than bg
|
|
710
|
+
_BORDER = "#3a3a58" # indigo border
|
|
711
|
+
_BORDER_HI = "#4a4a70" # highlighted border
|
|
712
|
+
_ACCENT = "#f0a0b8" # sakura pink
|
|
713
|
+
_ACCENT_HOVER = "#e890a8" # deeper sakura
|
|
714
|
+
_ACCENT_PRESS = "#d88098" # pressed sakura
|
|
715
|
+
_TEXT = "#e8e0f0" # lavender white
|
|
716
|
+
_TEXT2 = "#a0a0b8" # muted lavender
|
|
717
|
+
|
|
718
|
+
def get_theme_definition(self) -> ThemeDefinition:
|
|
719
|
+
c = self
|
|
720
|
+
return ThemeDefinition(
|
|
721
|
+
primary=c._ACCENT,
|
|
722
|
+
secondary="#b4a0d8", # wisteria
|
|
723
|
+
success="#90c8a0", # young leaves
|
|
724
|
+
warning="#e0b070", # lantern amber
|
|
725
|
+
error="#e07080", # camellia red
|
|
726
|
+
info="#80b8e0", # morning sky
|
|
727
|
+
background=c._BG,
|
|
728
|
+
surface=c._ISLAND,
|
|
729
|
+
sea=c._SEA,
|
|
730
|
+
text=c._TEXT,
|
|
731
|
+
text_secondary=c._TEXT2,
|
|
732
|
+
border=c._BORDER,
|
|
733
|
+
connected="#90c8a0",
|
|
734
|
+
disconnected="#5c2535", # deep plum
|
|
735
|
+
# No css_overrides — islands aesthetic comes from the Islands-mode
|
|
736
|
+
# toggle (see IslandsThemePlugin).
|
|
737
|
+
)
|