canns 0.13.1__py3-none-any.whl → 0.14.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.
- canns/analyzer/data/__init__.py +5 -1
- canns/analyzer/data/asa/__init__.py +27 -12
- canns/analyzer/data/asa/cohospace.py +336 -10
- canns/analyzer/data/asa/config.py +3 -0
- canns/analyzer/data/asa/embedding.py +48 -45
- canns/analyzer/data/asa/path.py +104 -2
- canns/analyzer/data/asa/plotting.py +88 -19
- canns/analyzer/data/asa/tda.py +11 -4
- canns/analyzer/data/cell_classification/__init__.py +97 -0
- canns/analyzer/data/cell_classification/core/__init__.py +26 -0
- canns/analyzer/data/cell_classification/core/grid_cells.py +633 -0
- canns/analyzer/data/cell_classification/core/grid_modules_leiden.py +288 -0
- canns/analyzer/data/cell_classification/core/head_direction.py +347 -0
- canns/analyzer/data/cell_classification/core/spatial_analysis.py +431 -0
- canns/analyzer/data/cell_classification/io/__init__.py +5 -0
- canns/analyzer/data/cell_classification/io/matlab_loader.py +417 -0
- canns/analyzer/data/cell_classification/utils/__init__.py +39 -0
- canns/analyzer/data/cell_classification/utils/circular_stats.py +383 -0
- canns/analyzer/data/cell_classification/utils/correlation.py +318 -0
- canns/analyzer/data/cell_classification/utils/geometry.py +442 -0
- canns/analyzer/data/cell_classification/utils/image_processing.py +416 -0
- canns/analyzer/data/cell_classification/visualization/__init__.py +19 -0
- canns/analyzer/data/cell_classification/visualization/grid_plots.py +292 -0
- canns/analyzer/data/cell_classification/visualization/hd_plots.py +200 -0
- canns/analyzer/metrics/__init__.py +2 -1
- canns/analyzer/visualization/core/config.py +46 -4
- canns/data/__init__.py +6 -1
- canns/data/datasets.py +154 -1
- canns/data/loaders.py +37 -0
- canns/pipeline/__init__.py +13 -9
- canns/pipeline/__main__.py +6 -0
- canns/pipeline/asa/runner.py +105 -41
- canns/pipeline/asa_gui/__init__.py +68 -0
- canns/pipeline/asa_gui/__main__.py +6 -0
- canns/pipeline/asa_gui/analysis_modes/__init__.py +42 -0
- canns/pipeline/asa_gui/analysis_modes/base.py +39 -0
- canns/pipeline/asa_gui/analysis_modes/batch_mode.py +21 -0
- canns/pipeline/asa_gui/analysis_modes/cohomap_mode.py +56 -0
- canns/pipeline/asa_gui/analysis_modes/cohospace_mode.py +194 -0
- canns/pipeline/asa_gui/analysis_modes/decode_mode.py +52 -0
- canns/pipeline/asa_gui/analysis_modes/fr_mode.py +81 -0
- canns/pipeline/asa_gui/analysis_modes/frm_mode.py +92 -0
- canns/pipeline/asa_gui/analysis_modes/gridscore_mode.py +123 -0
- canns/pipeline/asa_gui/analysis_modes/pathcompare_mode.py +199 -0
- canns/pipeline/asa_gui/analysis_modes/tda_mode.py +112 -0
- canns/pipeline/asa_gui/app.py +29 -0
- canns/pipeline/asa_gui/controllers/__init__.py +6 -0
- canns/pipeline/asa_gui/controllers/analysis_controller.py +59 -0
- canns/pipeline/asa_gui/controllers/preprocess_controller.py +89 -0
- canns/pipeline/asa_gui/core/__init__.py +15 -0
- canns/pipeline/asa_gui/core/cache.py +14 -0
- canns/pipeline/asa_gui/core/runner.py +1936 -0
- canns/pipeline/asa_gui/core/state.py +324 -0
- canns/pipeline/asa_gui/core/worker.py +260 -0
- canns/pipeline/asa_gui/main_window.py +184 -0
- canns/pipeline/asa_gui/models/__init__.py +7 -0
- canns/pipeline/asa_gui/models/config.py +14 -0
- canns/pipeline/asa_gui/models/job.py +31 -0
- canns/pipeline/asa_gui/models/presets.py +21 -0
- canns/pipeline/asa_gui/resources/__init__.py +16 -0
- canns/pipeline/asa_gui/resources/dark.qss +167 -0
- canns/pipeline/asa_gui/resources/light.qss +163 -0
- canns/pipeline/asa_gui/resources/styles.qss +130 -0
- canns/pipeline/asa_gui/utils/__init__.py +1 -0
- canns/pipeline/asa_gui/utils/formatters.py +15 -0
- canns/pipeline/asa_gui/utils/io_adapters.py +40 -0
- canns/pipeline/asa_gui/utils/validators.py +41 -0
- canns/pipeline/asa_gui/views/__init__.py +1 -0
- canns/pipeline/asa_gui/views/help_content.py +171 -0
- canns/pipeline/asa_gui/views/pages/__init__.py +6 -0
- canns/pipeline/asa_gui/views/pages/analysis_page.py +565 -0
- canns/pipeline/asa_gui/views/pages/preprocess_page.py +492 -0
- canns/pipeline/asa_gui/views/panels/__init__.py +1 -0
- canns/pipeline/asa_gui/views/widgets/__init__.py +21 -0
- canns/pipeline/asa_gui/views/widgets/artifacts_tab.py +44 -0
- canns/pipeline/asa_gui/views/widgets/drop_zone.py +80 -0
- canns/pipeline/asa_gui/views/widgets/file_list.py +27 -0
- canns/pipeline/asa_gui/views/widgets/gridscore_tab.py +308 -0
- canns/pipeline/asa_gui/views/widgets/help_dialog.py +27 -0
- canns/pipeline/asa_gui/views/widgets/image_tab.py +50 -0
- canns/pipeline/asa_gui/views/widgets/image_viewer.py +97 -0
- canns/pipeline/asa_gui/views/widgets/log_box.py +16 -0
- canns/pipeline/asa_gui/views/widgets/pathcompare_tab.py +200 -0
- canns/pipeline/asa_gui/views/widgets/popup_combo.py +25 -0
- canns/pipeline/gallery/__init__.py +15 -5
- canns/pipeline/gallery/__main__.py +11 -0
- canns/pipeline/gallery/app.py +705 -0
- canns/pipeline/gallery/runner.py +790 -0
- canns/pipeline/gallery/state.py +51 -0
- canns/pipeline/gallery/styles.tcss +123 -0
- canns/pipeline/launcher.py +81 -0
- {canns-0.13.1.dist-info → canns-0.14.0.dist-info}/METADATA +11 -1
- canns-0.14.0.dist-info/RECORD +163 -0
- canns-0.14.0.dist-info/entry_points.txt +5 -0
- canns/pipeline/_base.py +0 -50
- canns-0.13.1.dist-info/RECORD +0 -89
- canns-0.13.1.dist-info/entry_points.txt +0 -3
- {canns-0.13.1.dist-info → canns-0.14.0.dist-info}/WHEEL +0 -0
- {canns-0.13.1.dist-info → canns-0.14.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Main window for ASA GUI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from PySide6.QtCore import QSettings, Qt, QUrl, Signal
|
|
8
|
+
from PySide6.QtGui import QDesktopServices, QIcon
|
|
9
|
+
from PySide6.QtWidgets import (
|
|
10
|
+
QApplication,
|
|
11
|
+
QComboBox,
|
|
12
|
+
QHBoxLayout,
|
|
13
|
+
QLabel,
|
|
14
|
+
QMainWindow,
|
|
15
|
+
QPushButton,
|
|
16
|
+
QStackedWidget,
|
|
17
|
+
QVBoxLayout,
|
|
18
|
+
QWidget,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from .controllers import AnalysisController, PreprocessController
|
|
22
|
+
from .core import PipelineRunner, StateManager, WorkerManager
|
|
23
|
+
from .resources import load_theme_qss
|
|
24
|
+
from .views.pages.analysis_page import AnalysisPage
|
|
25
|
+
from .views.pages.preprocess_page import PreprocessPage
|
|
26
|
+
from .views.widgets.popup_combo import PopupComboBox
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class LogoLabel(QLabel):
|
|
30
|
+
clicked = Signal()
|
|
31
|
+
|
|
32
|
+
def mousePressEvent(self, event) -> None: # pragma: no cover - UI callback
|
|
33
|
+
if event.button() == Qt.LeftButton:
|
|
34
|
+
self.clicked.emit()
|
|
35
|
+
super().mousePressEvent(event)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class MainWindow(QMainWindow):
|
|
39
|
+
def __init__(self, parent=None) -> None:
|
|
40
|
+
super().__init__(parent)
|
|
41
|
+
self.setWindowTitle("ASA GUI")
|
|
42
|
+
self.resize(1200, 800)
|
|
43
|
+
|
|
44
|
+
self._settings = QSettings("canns", "asa_gui")
|
|
45
|
+
|
|
46
|
+
self._state_manager = StateManager()
|
|
47
|
+
self._runner = PipelineRunner()
|
|
48
|
+
self._workers = WorkerManager()
|
|
49
|
+
|
|
50
|
+
self._preprocess_controller = PreprocessController(self._state_manager, self._runner)
|
|
51
|
+
self._analysis_controller = AnalysisController(self._state_manager, self._runner)
|
|
52
|
+
|
|
53
|
+
self._build_ui()
|
|
54
|
+
|
|
55
|
+
def _build_ui(self) -> None:
|
|
56
|
+
root = QWidget()
|
|
57
|
+
layout = QVBoxLayout(root)
|
|
58
|
+
|
|
59
|
+
nav = QHBoxLayout()
|
|
60
|
+
self.logo_label = LogoLabel()
|
|
61
|
+
self.logo_label.setCursor(Qt.PointingHandCursor)
|
|
62
|
+
logo_height = 32 if sys.platform == "darwin" else 40
|
|
63
|
+
logo_pixmap = self._load_logo_pixmap(logo_height)
|
|
64
|
+
if logo_pixmap is not None:
|
|
65
|
+
self.logo_label.setPixmap(logo_pixmap)
|
|
66
|
+
self.logo_label.setFixedSize(logo_pixmap.size())
|
|
67
|
+
self.logo_label.setAlignment(Qt.AlignCenter)
|
|
68
|
+
nav.addWidget(self.logo_label)
|
|
69
|
+
self.logo_label.clicked.connect(self._open_homepage)
|
|
70
|
+
self.btn_preprocess = QPushButton("Preprocess")
|
|
71
|
+
self.btn_preprocess.setObjectName("navButton")
|
|
72
|
+
self.btn_preprocess.setCheckable(True)
|
|
73
|
+
self.btn_analysis = QPushButton("Analysis")
|
|
74
|
+
self.btn_analysis.setObjectName("navButton")
|
|
75
|
+
self.btn_analysis.setCheckable(True)
|
|
76
|
+
self.btn_preprocess.clicked.connect(lambda: self._stack.setCurrentIndex(0))
|
|
77
|
+
self.btn_analysis.clicked.connect(lambda: self._stack.setCurrentIndex(1))
|
|
78
|
+
nav.addWidget(self.btn_preprocess)
|
|
79
|
+
nav.addWidget(self.btn_analysis)
|
|
80
|
+
nav.addStretch(1)
|
|
81
|
+
self.theme_switch = PopupComboBox()
|
|
82
|
+
self.theme_switch.addItem("Light", userData="Light")
|
|
83
|
+
self.theme_switch.addItem("Dark", userData="Dark")
|
|
84
|
+
self.theme_switch.currentIndexChanged.connect(self._apply_theme)
|
|
85
|
+
self.theme_switch.setSizeAdjustPolicy(QComboBox.AdjustToContents)
|
|
86
|
+
self.theme_switch.setMinimumWidth(90)
|
|
87
|
+
nav.addWidget(self.theme_switch)
|
|
88
|
+
self.lang_switch = PopupComboBox()
|
|
89
|
+
self.lang_switch.addItem("EN", userData="en")
|
|
90
|
+
self.lang_switch.addItem("中文", userData="zh")
|
|
91
|
+
self.lang_switch.currentIndexChanged.connect(self._apply_language)
|
|
92
|
+
self.lang_switch.setSizeAdjustPolicy(QComboBox.AdjustToContents)
|
|
93
|
+
self.lang_switch.setMinimumWidth(90)
|
|
94
|
+
nav.addWidget(self.lang_switch)
|
|
95
|
+
layout.addLayout(nav)
|
|
96
|
+
|
|
97
|
+
self._stack = QStackedWidget()
|
|
98
|
+
self.preprocess_page = PreprocessPage(self._preprocess_controller, self._workers)
|
|
99
|
+
self.analysis_page = AnalysisPage(self._analysis_controller, self._workers)
|
|
100
|
+
|
|
101
|
+
self._stack.addWidget(self.preprocess_page)
|
|
102
|
+
self._stack.addWidget(self.analysis_page)
|
|
103
|
+
layout.addWidget(self._stack, 1)
|
|
104
|
+
|
|
105
|
+
self.preprocess_page.preprocess_completed.connect(self._on_preprocess_completed)
|
|
106
|
+
self._stack.currentChanged.connect(self._sync_nav)
|
|
107
|
+
|
|
108
|
+
self.setCentralWidget(root)
|
|
109
|
+
self._init_theme()
|
|
110
|
+
self._init_language()
|
|
111
|
+
self._init_icons(str(self.theme_switch.currentData() or "Light"))
|
|
112
|
+
self._sync_nav(self._stack.currentIndex())
|
|
113
|
+
|
|
114
|
+
def _go_analysis(self) -> None:
|
|
115
|
+
self._stack.setCurrentIndex(1)
|
|
116
|
+
|
|
117
|
+
def _on_preprocess_completed(self) -> None:
|
|
118
|
+
self._stack.setCurrentIndex(1)
|
|
119
|
+
self.analysis_page.load_state(self._state_manager.state)
|
|
120
|
+
|
|
121
|
+
def _sync_nav(self, index: int) -> None:
|
|
122
|
+
self.btn_preprocess.setChecked(index == 0)
|
|
123
|
+
self.btn_analysis.setChecked(index == 1)
|
|
124
|
+
|
|
125
|
+
def _init_theme(self) -> None:
|
|
126
|
+
theme = str(self._settings.value("theme", "Light"))
|
|
127
|
+
idx = 0 if theme.lower().startswith("light") else 1
|
|
128
|
+
self.theme_switch.setCurrentIndex(idx)
|
|
129
|
+
self._apply_theme()
|
|
130
|
+
|
|
131
|
+
def _init_language(self) -> None:
|
|
132
|
+
lang = str(self._settings.value("lang", "en")).lower()
|
|
133
|
+
idx = 0 if lang.startswith("en") else 1
|
|
134
|
+
self.lang_switch.setCurrentIndex(idx)
|
|
135
|
+
self._apply_language()
|
|
136
|
+
|
|
137
|
+
def _apply_theme(self) -> None:
|
|
138
|
+
try:
|
|
139
|
+
theme = str(self.theme_switch.currentData() or "Light")
|
|
140
|
+
qss = load_theme_qss(theme)
|
|
141
|
+
QApplication.instance().setStyleSheet(qss)
|
|
142
|
+
self._settings.setValue("theme", theme)
|
|
143
|
+
self._init_icons(theme)
|
|
144
|
+
except Exception:
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
def _apply_language(self) -> None:
|
|
148
|
+
lang = self.lang_switch.currentData() or "en"
|
|
149
|
+
self._settings.setValue("lang", str(lang))
|
|
150
|
+
is_zh = str(lang).lower().startswith("zh")
|
|
151
|
+
self.btn_preprocess.setText("预处理" if is_zh else "Preprocess")
|
|
152
|
+
self.btn_analysis.setText("分析" if is_zh else "Analysis")
|
|
153
|
+
self.preprocess_page.apply_language(str(lang))
|
|
154
|
+
self.analysis_page.apply_language(str(lang))
|
|
155
|
+
|
|
156
|
+
def _init_icons(self, theme: str) -> None:
|
|
157
|
+
try:
|
|
158
|
+
import qtawesome as qta
|
|
159
|
+
|
|
160
|
+
color = "#34d399" if str(theme).lower().startswith("dark") else "#10b981"
|
|
161
|
+
self.btn_preprocess.setIcon(qta.icon("fa5s.sliders-h", color=color))
|
|
162
|
+
self.btn_analysis.setIcon(qta.icon("fa5s.chart-area", color=color))
|
|
163
|
+
except Exception:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
def _load_logo_pixmap(self, height: int):
|
|
167
|
+
from pathlib import Path
|
|
168
|
+
|
|
169
|
+
base = Path(__file__).resolve().parents[4] / "images"
|
|
170
|
+
logo_path = base / "logo.svg"
|
|
171
|
+
if not logo_path.exists():
|
|
172
|
+
return None
|
|
173
|
+
icon = QIcon(str(logo_path))
|
|
174
|
+
if icon.isNull():
|
|
175
|
+
return None
|
|
176
|
+
size = max(64, int(height) * 4)
|
|
177
|
+
pixmap = icon.pixmap(size, size)
|
|
178
|
+
return pixmap.scaledToHeight(int(height), Qt.SmoothTransformation)
|
|
179
|
+
|
|
180
|
+
def _open_homepage(self) -> None:
|
|
181
|
+
QDesktopServices.openUrl(QUrl("https://github.com/Routhleck/canns"))
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
ASAGuiApp = MainWindow
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Lightweight config helpers for ASA GUI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class AnalysisConfig:
|
|
11
|
+
"""Simple container for analysis parameters."""
|
|
12
|
+
|
|
13
|
+
mode: str = "tda"
|
|
14
|
+
params: dict[str, Any] = field(default_factory=dict)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Job specification and results for ASA GUI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class JobSpec:
|
|
12
|
+
"""Inputs and parameters for a single analysis run."""
|
|
13
|
+
|
|
14
|
+
input_mode: str # "asa" | "batch" | "neuron_traj"
|
|
15
|
+
preset: str # "grid" | "hd" | "none"
|
|
16
|
+
asa_file: Path | None = None
|
|
17
|
+
neuron_file: Path | None = None
|
|
18
|
+
traj_file: Path | None = None
|
|
19
|
+
out_dir: Path = Path("Results/asa_gui_job")
|
|
20
|
+
params: dict[str, Any] = field(default_factory=dict)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class JobResult:
|
|
25
|
+
"""Result container for pipeline execution."""
|
|
26
|
+
|
|
27
|
+
ok: bool
|
|
28
|
+
out_dir: Path
|
|
29
|
+
artifacts: dict[str, Path] = field(default_factory=dict)
|
|
30
|
+
summary: dict[str, Any] = field(default_factory=dict)
|
|
31
|
+
errors: list[str] = field(default_factory=list)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Preset parameter hints for ASA GUI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_preset_params(preset: str) -> dict[str, Any]:
|
|
9
|
+
"""Return default parameter overrides for a preset."""
|
|
10
|
+
preset = (preset or "none").lower()
|
|
11
|
+
if preset == "grid":
|
|
12
|
+
return {
|
|
13
|
+
"tda": {"maxdim": 2},
|
|
14
|
+
"decode": {"num_circ": 2},
|
|
15
|
+
}
|
|
16
|
+
if preset == "hd":
|
|
17
|
+
return {
|
|
18
|
+
"tda": {"maxdim": 1},
|
|
19
|
+
"decode": {"num_circ": 1},
|
|
20
|
+
}
|
|
21
|
+
return {}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Static resources for ASA GUI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def load_theme_qss(theme: str) -> str:
|
|
9
|
+
"""Load the QSS for a given theme name."""
|
|
10
|
+
name = (theme or "Light").strip().lower()
|
|
11
|
+
if name in {"dark", "dark mode", "darkmode"}:
|
|
12
|
+
fname = "dark.qss"
|
|
13
|
+
else:
|
|
14
|
+
fname = "light.qss"
|
|
15
|
+
path = Path(__file__).parent / fname
|
|
16
|
+
return path.read_text(encoding="utf-8")
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/* Dark theme */
|
|
2
|
+
QWidget {
|
|
3
|
+
font-family: "SF Pro Text", "Helvetica Neue", "Segoe UI", "Arial", sans-serif;
|
|
4
|
+
font-size: 11pt;
|
|
5
|
+
color: #e5e7eb;
|
|
6
|
+
background-color: #111827;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
QLabel {
|
|
10
|
+
background: transparent;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
QLabel#muted {
|
|
14
|
+
color: #9ca3af;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
QFrame#card, QGroupBox#card {
|
|
18
|
+
background-color: #1f2937;
|
|
19
|
+
border: 1px solid #374151;
|
|
20
|
+
border-radius: 12px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
QGroupBox {
|
|
24
|
+
border: 1px solid #374151;
|
|
25
|
+
border-radius: 10px;
|
|
26
|
+
margin-top: 18px;
|
|
27
|
+
background-color: #1f2937;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
QGroupBox::title {
|
|
31
|
+
subcontrol-origin: margin;
|
|
32
|
+
left: 10px;
|
|
33
|
+
padding: 0 6px;
|
|
34
|
+
color: #34d399;
|
|
35
|
+
font-weight: 600;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
QLineEdit, QSpinBox, QDoubleSpinBox, QComboBox {
|
|
39
|
+
background-color: #374151;
|
|
40
|
+
color: #e5e7eb;
|
|
41
|
+
border: 1px solid #4b5563;
|
|
42
|
+
border-radius: 6px;
|
|
43
|
+
padding: 4px 8px;
|
|
44
|
+
min-width: 120px;
|
|
45
|
+
selection-background-color: #34d399;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QComboBox:focus {
|
|
49
|
+
border: 1px solid #34d399;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
QPushButton {
|
|
54
|
+
background-color: #374151;
|
|
55
|
+
color: #e5e7eb;
|
|
56
|
+
border: 1px solid #4b5563;
|
|
57
|
+
border-radius: 6px;
|
|
58
|
+
padding: 6px 12px;
|
|
59
|
+
font-weight: 600;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
QPushButton:hover {
|
|
63
|
+
background-color: #4b5563;
|
|
64
|
+
border-color: #6b7280;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
QPushButton#btn_run {
|
|
68
|
+
background-color: #059669;
|
|
69
|
+
color: #ffffff;
|
|
70
|
+
border: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
QPushButton#btn_run:hover {
|
|
74
|
+
background-color: #10b981;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
QPushButton#btn_stop {
|
|
78
|
+
background-color: #991b1b;
|
|
79
|
+
color: #ffffff;
|
|
80
|
+
border: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
QPushButton#btn_stop:hover {
|
|
84
|
+
background-color: #b91c1c;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
QPushButton#navButton {
|
|
88
|
+
background-color: transparent;
|
|
89
|
+
border: none;
|
|
90
|
+
color: #9ca3af;
|
|
91
|
+
font-weight: 600;
|
|
92
|
+
padding: 6px 10px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
QPushButton#navButton:checked {
|
|
96
|
+
color: #34d399;
|
|
97
|
+
border-bottom: 2px solid #34d399;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
QTabWidget::pane {
|
|
101
|
+
border: 1px solid #374151;
|
|
102
|
+
background: #1f2937;
|
|
103
|
+
border-radius: 8px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
QTabBar::tab {
|
|
107
|
+
background: transparent;
|
|
108
|
+
border: none;
|
|
109
|
+
padding: 8px 14px;
|
|
110
|
+
margin-right: 4px;
|
|
111
|
+
color: #9ca3af;
|
|
112
|
+
font-weight: 600;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
QTabBar::tab:selected {
|
|
116
|
+
color: #34d399;
|
|
117
|
+
border-bottom: 2px solid #34d399;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
QProgressBar {
|
|
121
|
+
border: none;
|
|
122
|
+
background-color: #374151;
|
|
123
|
+
border-radius: 4px;
|
|
124
|
+
text-align: center;
|
|
125
|
+
color: #e5e7eb;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
QProgressBar::chunk {
|
|
129
|
+
background-color: #059669;
|
|
130
|
+
border-radius: 4px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
QTextEdit, QListWidget {
|
|
134
|
+
background-color: #0b0f14;
|
|
135
|
+
color: #a7f3d0;
|
|
136
|
+
border: 1px solid #374151;
|
|
137
|
+
border-radius: 8px;
|
|
138
|
+
font-family: "Consolas", "Menlo", "Monaco", monospace;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
QSplitter::handle {
|
|
142
|
+
background: #374151;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
QScrollArea {
|
|
146
|
+
background: transparent;
|
|
147
|
+
border: none;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
QGraphicsView {
|
|
151
|
+
background: #0f172a;
|
|
152
|
+
border: 1px solid #374151;
|
|
153
|
+
border-radius: 8px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
QFrame#DropZone {
|
|
157
|
+
border: 2px dashed #6b7280;
|
|
158
|
+
border-radius: 12px;
|
|
159
|
+
background-color: #111827;
|
|
160
|
+
color: #9ca3af;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
QFrame#DropZone[drag="true"] {
|
|
164
|
+
background-color: #0f172a;
|
|
165
|
+
border-color: #34d399;
|
|
166
|
+
color: #34d399;
|
|
167
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/* Light theme */
|
|
2
|
+
QWidget {
|
|
3
|
+
font-family: "SF Pro Text", "Helvetica Neue", "Segoe UI", "Arial", sans-serif;
|
|
4
|
+
font-size: 11pt;
|
|
5
|
+
color: #374151;
|
|
6
|
+
background-color: #f3f4f6;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
QLabel {
|
|
10
|
+
background: transparent;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
QLabel#muted {
|
|
14
|
+
color: #6b7280;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
QFrame#card, QGroupBox#card {
|
|
18
|
+
background-color: #ffffff;
|
|
19
|
+
border: 1px solid #e5e7eb;
|
|
20
|
+
border-radius: 12px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
QGroupBox {
|
|
24
|
+
border: 1px solid #e5e7eb;
|
|
25
|
+
border-radius: 10px;
|
|
26
|
+
margin-top: 18px;
|
|
27
|
+
background-color: #ffffff;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
QGroupBox::title {
|
|
31
|
+
subcontrol-origin: margin;
|
|
32
|
+
left: 10px;
|
|
33
|
+
padding: 0 6px;
|
|
34
|
+
color: #10b981;
|
|
35
|
+
font-weight: 600;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
QLineEdit, QSpinBox, QDoubleSpinBox, QComboBox {
|
|
39
|
+
background-color: #ffffff;
|
|
40
|
+
border: 1px solid #d1d5db;
|
|
41
|
+
border-radius: 6px;
|
|
42
|
+
padding: 4px 8px;
|
|
43
|
+
min-width: 120px;
|
|
44
|
+
selection-background-color: #10b981;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QComboBox:focus {
|
|
48
|
+
border: 1px solid #10b981;
|
|
49
|
+
background-color: #f0fdf4;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
QPushButton {
|
|
54
|
+
background-color: #ffffff;
|
|
55
|
+
border: 1px solid #d1d5db;
|
|
56
|
+
border-radius: 6px;
|
|
57
|
+
padding: 6px 12px;
|
|
58
|
+
font-weight: 600;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
QPushButton:hover {
|
|
62
|
+
background-color: #f9fafb;
|
|
63
|
+
border-color: #9ca3af;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
QPushButton#btn_run {
|
|
67
|
+
background-color: #10b981;
|
|
68
|
+
color: #ffffff;
|
|
69
|
+
border: none;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
QPushButton#btn_run:hover {
|
|
73
|
+
background-color: #059669;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
QPushButton#btn_stop {
|
|
77
|
+
background-color: #ef4444;
|
|
78
|
+
color: #ffffff;
|
|
79
|
+
border: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
QPushButton#btn_stop:hover {
|
|
83
|
+
background-color: #dc2626;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
QPushButton#navButton {
|
|
87
|
+
background-color: transparent;
|
|
88
|
+
border: none;
|
|
89
|
+
color: #6b7280;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
padding: 6px 10px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
QPushButton#navButton:checked {
|
|
95
|
+
color: #10b981;
|
|
96
|
+
border-bottom: 2px solid #10b981;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
QTabWidget::pane {
|
|
100
|
+
border: 1px solid #e5e7eb;
|
|
101
|
+
background: #ffffff;
|
|
102
|
+
border-radius: 8px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
QTabBar::tab {
|
|
106
|
+
background: transparent;
|
|
107
|
+
border: none;
|
|
108
|
+
padding: 8px 14px;
|
|
109
|
+
margin-right: 4px;
|
|
110
|
+
color: #6b7280;
|
|
111
|
+
font-weight: 600;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
QTabBar::tab:selected {
|
|
115
|
+
color: #10b981;
|
|
116
|
+
border-bottom: 2px solid #10b981;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
QProgressBar {
|
|
120
|
+
border: none;
|
|
121
|
+
background-color: #e5e7eb;
|
|
122
|
+
border-radius: 4px;
|
|
123
|
+
text-align: center;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
QProgressBar::chunk {
|
|
127
|
+
background-color: #10b981;
|
|
128
|
+
border-radius: 4px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
QTextEdit, QListWidget {
|
|
132
|
+
background-color: #ffffff;
|
|
133
|
+
border: 1px solid #e5e7eb;
|
|
134
|
+
border-radius: 8px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
QSplitter::handle {
|
|
138
|
+
background: #e5e7eb;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
QScrollArea {
|
|
142
|
+
background: transparent;
|
|
143
|
+
border: none;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
QGraphicsView {
|
|
147
|
+
background: #ffffff;
|
|
148
|
+
border: 1px solid #e5e7eb;
|
|
149
|
+
border-radius: 8px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
QFrame#DropZone {
|
|
153
|
+
border: 2px dashed #9ca3af;
|
|
154
|
+
border-radius: 12px;
|
|
155
|
+
background-color: #f9fafb;
|
|
156
|
+
color: #6b7280;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
QFrame#DropZone[drag="true"] {
|
|
160
|
+
background-color: #f0fdf4;
|
|
161
|
+
border-color: #10b981;
|
|
162
|
+
color: #10b981;
|
|
163
|
+
}
|