Q2NSViz 0.1.0
Q2NS trace visualizer
Loading...
Searching...
No Matches
theme.py
Go to the documentation of this file.
1# -----------------------------------------------------------------------------
2# Q2NSViz - Quantum Network Trace Visualizer
3# Copyright (c) 2026 QuantumInternet.it
4#
5# This program is released under the MIT License - see LICENSE for details.
6# -----------------------------------------------------------------------------
7
8from __future__ import annotations
9
10from PyQt6.QtGui import QColor
11
12SANS_FONT = "Arial,Liberation Sans,DejaVu Sans,Helvetica,sans-serif"
13
14
15def ui_font_family() -> str:
16 """Native UI font family matching the application font, for QPainter text."""
17 from PyQt6.QtWidgets import QApplication
18
19 app = QApplication.instance()
20 return app.font().family() if app is not None else SANS_FONT
21
22
23# ---------------------------------------------------------------------------
24# Palette definitions
25# ---------------------------------------------------------------------------
26
27_LIGHT: dict[str, QColor] = {
28 "BG_DARK": QColor("#f5f7fa"), # canvas background
29 "BG_MEDIUM": QColor("#ffffff"), # node background, tooltip
30 "BG_LIGHT": QColor("#fafbfc"), # sidebar, legend, tooltip hover
31 "BG_TOPBAR": QColor("#ffffff"), # top bar background
32 "PRIMARY": QColor("#334261"), # buttons, highlights (Qnattynet navy)
33 "PRIMARY_HOVER": QColor("#3d4f76"), # button hover state (lighter navy)
34 "PRIMARY_DARK": QColor("#27324b"), # button active/pressed state (darker navy)
35 "QUANTUM_CHANNEL": QColor("#8250df"), # quantum channel
36 "CLASSICAL_CHANNEL": QColor("#d9b112"), # classical channel
37 "ENTANGLEMENT": QColor("#9527a3"), # entanglement
38 "TEXT_PRIMARY": QColor("#334261"), # primary text
39 "TEXT_SECONDARY": QColor("#57606a"), # secondary text
40 "TEXT_MUTED": QColor("#6e7781"), # muted text
41 "NODE_TEXT": QColor("#334261"), # node labels
42 "BORDER": QColor("#d0d7de"), # borders and dividers
43 "QUBIT_PRODUCT": QColor("#0969da"), # isolated qubit
44 "QUBIT_ENTANGLED": QColor("#9527a3"), # qubit in an entanglement cluster
45 "QUBIT_MEASURED": QColor("#8c959f"), # measured and removed qubit marker
46 "QUBIT_LOST": QColor("#da3633"), # lost marker
47 "HALO_GATE": QColor("#475569"), # quantum-gate processing ring (dashed)
48 "HALO_MEASURE": QColor("#0e7c86"), # measure operation ring (solid)
49 "HALO_GRAPH_MEASURE": QColor("#cf9a27"), # graphMeasure operation ring (double)
50 "PACKET_CLASSICAL": QColor("#d9b112"), # unknown / generic classical
51 "PACKET_TCP": QColor("#1a7f37"), # TCP classical packet
52 "PACKET_UDP": QColor("#bc4c00"), # UDP classical packet
53 "CBIT": QColor("#cc6600"), # classical bit square marker
54}
55
56_DARK: dict[str, QColor] = {
57 "BG_DARK": QColor("#0d1117"), # canvas background
58 "BG_MEDIUM": QColor("#21262d"), # node background, tooltip
59 "BG_LIGHT": QColor("#2d333b"), # sidebar, legend, tooltip hover
60 "BG_TOPBAR": QColor("#161b22"), # top bar background (dark surface, matches canvas family)
61 "PRIMARY": QColor("#334261"), # buttons, highlights (Qnattynet navy, lighter for dark bg)
62 "PRIMARY_HOVER": QColor("#3d4f76"), # button hover state (lighter navy)
63 "PRIMARY_DARK": QColor("#27324b"), # button active/pressed state (darker navy)
64 "QUANTUM_CHANNEL": QColor("#a371f7"), # quantum channel
65 "CLASSICAL_CHANNEL": QColor("#e3b341"), # classical channel
66 "ENTANGLEMENT": QColor("#d2a8ff"), # entanglement
67 "TEXT_PRIMARY": QColor("#e6edf3"), # primary text
68 "TEXT_SECONDARY": QColor("#8d96a0"), # secondary text
69 "TEXT_MUTED": QColor("#6e7681"), # muted text
70 "NODE_TEXT": QColor("#e6edf3"), # node labels
71 "BORDER": QColor("#30363d"), # borders and dividers
72 "QUBIT_PRODUCT": QColor("#388bfd"), # isolated qubit
73 "QUBIT_ENTANGLED": QColor("#d2a8ff"), # qubit in an entanglement cluster
74 "QUBIT_MEASURED": QColor("#6e7681"), # measured and removed qubit marker
75 "QUBIT_LOST": QColor("#f85149"), # lost marker
76 "HALO_GATE": QColor("#94a3b8"), # quantum-gate processing ring (dashed)
77 "HALO_MEASURE": QColor("#39c5bb"), # measure operation ring (solid)
78 "HALO_GRAPH_MEASURE": QColor("#e3b341"), # graphMeasure operation ring (double)
79 "PACKET_CLASSICAL": QColor("#e3b341"), # unknown / generic classical
80 "PACKET_TCP": QColor("#3fb950"), # TCP classical packet
81 "PACKET_UDP": QColor("#f0883e"), # UDP classical packet
82 "CBIT": QColor("#ffa657"), # classical bit square marker
83}
84
85_active_palette: dict[str, QColor] = _LIGHT
86
87
88def toggle_dark() -> bool:
89 """Switch the active palette between light and dark.
90
91 @returns ``True`` if the palette is now dark; ``False`` if light.
92 """
93 global _active_palette
94 _active_palette = _DARK if _active_palette is _LIGHT else _LIGHT
95 return _active_palette is _DARK
96
97
98def is_dark() -> bool:
99 """Return ``True`` when the dark palette is active."""
100 return _active_palette is _DARK
101
102
103# ---------------------------------------------------------------------------
104# Theme proxy --- exposes palette entries as class-level attributes.
105# All consumer code accesses colors via ``Theme.X``
106# ---------------------------------------------------------------------------
107
108
109class _ThemeMeta(type):
110 """Metaclass that forwards attribute access to the active palette dict."""
111
112 def __getattr__(cls, name: str) -> QColor:
113 try:
114 return _active_palette[name]
115 except KeyError:
116 raise AttributeError(f"Theme has no color attribute '{name}'") from None
117
118
119class Theme(metaclass=_ThemeMeta):
120 """Semantic color palette for Q2NSViz.
121
122 Access colors as class attributes (e.g. ``Theme.BG_DARK``). The active
123 palette (light or dark) is selected by calling ``toggle_dark()``.
124
125 **Qubit semantic states**
126
127 | Constant | Semantic |
128 |---|---|
129 | ``QUBIT_PRODUCT`` | Isolated qubit (no entanglement) |
130 | ``QUBIT_ENTANGLED`` | Qubit in an entanglement cluster |
131 | ``QUBIT_MEASURED`` | Measured and removed qubit marker |
132 | ``QUBIT_LOST`` | Lost qubit (removed without measurement) marker |
133
134 **Operation halos** (transient rings marking processing time during a
135 duration_ns window; entanglement itself is shown by qubit color, not a ring)
136
137 | Constant | Semantic | Symbol |
138 |---|---|---|
139 | ``HALO_GATE`` | quantum-gate processing (``entangle`` event) | dashed single |
140 | ``HALO_MEASURE`` | ``measure`` operation ring | solid single |
141 | ``HALO_GRAPH_MEASURE`` | ``graphMeasure`` operation ring | solid double |
142
143 **Classical packet types**
144
145 | Constant | Semantic |
146 |---|---|
147 | ``PACKET_CLASSICAL`` | Unknown or generic classical packet |
148 | ``PACKET_TCP`` | TCP classical packet |
149 | ``PACKET_UDP`` | UDP classical packet |
150 """
151
152
153__all__ = ["SANS_FONT", "Theme", "is_dark", "toggle_dark", "ui_font_family"]
Semantic color palette for Q2NSViz.
Definition theme.py:119
bool toggle_dark()
Switch the active palette between light and dark.
Definition theme.py:88
bool is_dark()
Return True when the dark palette is active.
Definition theme.py:98
str ui_font_family()
Native UI font family matching the application font, for QPainter text.
Definition theme.py:15