summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Kolset <christian.kolset@gmail.com>2026-09-21 00:24:25 -0600
committerChristian Kolset <christian.kolset@gmail.com>2026-09-21 00:24:25 -0600
commitbe6e8ec356166c2df0fba443a086afc971aaefbd (patch)
treea7a732022dcb2f3159bf7d58bb71b319efdcf08a
parent4ab2eea9af5338485303d421476fa874e5af6841 (diff)
Fix white native-chrome artifacts on dark thememain
QSS alone doesn't reach everything Qt draws — splitter/dock separator grip-dot decorations and a checkable QMenu item's tick come from the app's QPalette, which was never set (stylesheet-only theming), so they stayed Qt's default light palette under the dark stylesheet: - MainWindow._apply_theme() now also sets a matching dark QPalette (restored to the style's standard palette for light theme). - QMainWindow::separator and a generic QMenu rule (only #profileMenu was styled before — the toolbar's View menu had no objectName and so no styling at all) added to style_dark.qss, with QMenu::indicator styled as a filled/outline square so checkmarks are visible regardless of palette. - QTabWidget/QTabBar (the bare containers, not ::pane/::tab) had no background set, so the strip beside the tab buttons fell back to the light palette — fixed globally, covers both the Devices/Channels/Plot config tabs and the Settings window since both share the same QTabWidget styling. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-rw-r--r--ui/main_window.py33
-rw-r--r--ui/style_dark.qss42
2 files changed, 73 insertions, 2 deletions
diff --git a/ui/main_window.py b/ui/main_window.py
index 07ab0b2..d7c297f 100644
--- a/ui/main_window.py
+++ b/ui/main_window.py
@@ -13,7 +13,7 @@ from PyQt6.QtWidgets import (
QAbstractSpinBox, QComboBox, QMenu,
)
from PyQt6.QtCore import Qt, QTimer, QObject, QEvent, QRect, QByteArray, pyqtSignal, pyqtSlot
-from PyQt6.QtGui import QPainter, QColor, QFont, QAction
+from PyQt6.QtGui import QPainter, QColor, QFont, QAction, QPalette
import logging
import base64
import os
@@ -822,8 +822,39 @@ class MainWindow(QMainWindow):
if os.path.exists(qss_file):
with open(qss_file) as f:
QApplication.instance().setStyleSheet(f.read())
+ self._apply_palette(theme)
self._chart.set_theme(theme)
+ def _apply_palette(self, theme: str):
+ """QSS alone doesn't reach every Qt-drawn pixel — a splitter/dock
+ separator's grip-dot decoration, a QMenu's checkmark tick, etc. are
+ drawn from the app's QPalette, not stylesheet background-color.
+ Without ever setting one, those stay Qt's default light palette
+ regardless of the dark stylesheet, showing up as stray white dots/
+ slivers on top of otherwise-correctly-dark widgets."""
+ app = QApplication.instance()
+ if theme == "dark":
+ pal = QPalette()
+ pal.setColor(QPalette.ColorRole.Window, QColor("#0b0e13"))
+ pal.setColor(QPalette.ColorRole.WindowText, QColor("#c7d2fe"))
+ pal.setColor(QPalette.ColorRole.Base, QColor("#111620"))
+ pal.setColor(QPalette.ColorRole.AlternateBase, QColor("#161d2e"))
+ pal.setColor(QPalette.ColorRole.Text, QColor("#c7d2fe"))
+ pal.setColor(QPalette.ColorRole.Button, QColor("#161d2e"))
+ pal.setColor(QPalette.ColorRole.ButtonText, QColor("#c7d2fe"))
+ pal.setColor(QPalette.ColorRole.Highlight, QColor("#3b82f6"))
+ pal.setColor(QPalette.ColorRole.HighlightedText, QColor("#0b0e13"))
+ pal.setColor(QPalette.ColorRole.ToolTipBase, QColor("#161d2e"))
+ pal.setColor(QPalette.ColorRole.ToolTipText, QColor("#c7d2fe"))
+ pal.setColor(QPalette.ColorRole.Light, QColor("#2a3558"))
+ pal.setColor(QPalette.ColorRole.Midlight, QColor("#2a3558"))
+ pal.setColor(QPalette.ColorRole.Dark, QColor("#060810"))
+ pal.setColor(QPalette.ColorRole.Mid, QColor("#1c2540"))
+ pal.setColor(QPalette.ColorRole.Shadow, QColor("#000000"))
+ app.setPalette(pal)
+ else:
+ app.setPalette(app.style().standardPalette())
+
def _apply_settings_on_startup(self):
self._apply_theme(self._settings.get("theme", "dark"))
self.engine._interval = self._settings.get("poll_ms", 100) / 1000.0
diff --git a/ui/style_dark.qss b/ui/style_dark.qss
index 3a20cba..ce43d74 100644
--- a/ui/style_dark.qss
+++ b/ui/style_dark.qss
@@ -348,6 +348,11 @@ QComboBox#channelPickerCb { font-family: "IBM Plex Mono", monospace; font-size:
QComboBox#channelPickerCb:focus { border-color: #3b82f6; }
/* ── Tabs ────────────────────────────────────────────────────────── */
+/* The bare QTabWidget/QTabBar containers (not ::pane or ::tab) have no
+ background of their own otherwise, so the strip beside/above the tab
+ buttons falls back to the default light palette. */
+QTabWidget { background-color: transparent; }
+QTabBar { background-color: transparent; }
QTabWidget::pane { background-color: #111620; border: 1px solid #2a3558; border-radius: 4px; }
QTabBar::tab {
background-color: #0b0e13; color: #8b9dc3;
@@ -385,10 +390,18 @@ QScrollBar::handle:horizontal { background: #2a3558; border-radius: 4px; min-wid
QScrollBar::handle:horizontal:hover { background: #3b82f6; }
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0; }
-/* ── Splitter ─────────────────────────────────────────────────────── */
+/* ── Splitter / dock separator ───────────────────────────────────────
+ Qt draws the little grip-dot decoration on a splitter/dock separator
+ from the app's QPalette (Light/Midlight roles), not from QSS
+ background-color — see MainWindow._apply_palette() in ui/main_window.py,
+ which sets those roles dark too. background-color here still matters
+ for the flat part of the handle. */
QSplitter::handle { background-color: #2a3558; }
QSplitter::handle:hover { background-color: #3b82f6; }
+QMainWindow::separator { background-color: #2a3558; width: 4px; height: 4px; }
+QMainWindow::separator:hover { background-color: #3b82f6; }
+
/* ── Status bar ───────────────────────────────────────────────────── */
QStatusBar {
background-color: #0b0e13; border-top: 1px solid #2a3558;
@@ -554,6 +567,33 @@ QFileDialog QComboBox {
color: #e2e8f0;
}
+/* ── Menus (generic — covers the View menu and any other unnamed QMenu,
+ not just the named ones below) ───────────────────────────────────── */
+QMenu {
+ background-color: #111620;
+ border: 1px solid #2a3558;
+ border-radius: 4px;
+ padding: 4px 0;
+ color: #e2e8f0;
+ font-size: 13px;
+}
+QMenu::item { padding: 7px 20px 7px 28px; color: #c7d2fe; }
+QMenu::item:selected { background-color: #1e3a5f; color: #e0f2fe; }
+QMenu::item:disabled { color: #475569; }
+QMenu::separator { height: 1px; background: #2a3558; margin: 3px 8px; }
+/* Checkable QAction indicator — drawn as a small filled/outline square
+ rather than relying on the native tick glyph, so it's guaranteed
+ visible regardless of palette. */
+QMenu::indicator {
+ width: 13px; height: 13px; margin-left: 6px;
+}
+QMenu::indicator:checked {
+ background-color: #3b82f6; border: 1px solid #3b82f6; border-radius: 3px;
+}
+QMenu::indicator:unchecked {
+ background-color: transparent; border: 1px solid #2a3558; border-radius: 3px;
+}
+
/* ── Profile / File menu ─────────────────────────────────────────── */
QMenu#profileMenu {
background-color: #111620;