diff --git a/src/main.cpp b/src/main.cpp index 89db02b..3260885 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -251,6 +251,8 @@ public: s.setHeight(s.height() + qRound(s.height() * 0.5)); if (type == CT_MenuItem) s = QSize(s.width() + 24, s.height() + 4); + if (type == CT_ItemViewItem) + s.setHeight(s.height() + 4); return s; } int pixelMetric(PixelMetric metric, const QStyleOption* opt, @@ -268,8 +270,9 @@ public: } void drawPrimitive(PrimitiveElement elem, const QStyleOption* opt, QPainter* p, const QWidget* w) const override { - // Clean 1px border on QMenu (replaces Fusion's 3D bevel + OS shadow) + // Opaque background + clean 1px border on QMenu if (elem == PE_FrameMenu) { + p->fillRect(opt->rect, opt->palette.color(QPalette::Window)); p->setPen(opt->palette.color(QPalette::Dark)); p->setBrush(Qt::NoBrush); p->drawRect(opt->rect.adjusted(0, 0, -1, -1)); @@ -309,9 +312,9 @@ public: // Only fill background for hover/pressed — non-hovered stays // transparent so the parent's border line shows through. if (sunken) - p->fillRect(area, mi->palette.color(QPalette::Mid).darker(130)); + p->fillRect(area, mi->palette.color(QPalette::Highlight).darker(130)); else if (selected) - p->fillRect(area, mi->palette.color(QPalette::Mid)); + p->fillRect(area, mi->palette.color(QPalette::Highlight)); QColor fg = (selected || sunken) ? mi->palette.color(QPalette::Link) @@ -321,15 +324,23 @@ public: return; // never delegate to Fusion } } - // Popup menu items — palette patch then delegate to Fusion + // Popup menu items if (element == CE_MenuItem) { if (auto* mi = qstyleoption_cast(opt)) { - if ((mi->state & State_Selected) - && mi->menuItemType != QStyleOptionMenuItem::Separator) { + // Subtle separator — single line using surface color + if (mi->menuItemType == QStyleOptionMenuItem::Separator) { + int y = mi->rect.center().y(); + p->setPen(mi->palette.color(QPalette::AlternateBase)); + p->drawLine(mi->rect.left() + 4, y, mi->rect.right() - 4, y); + return; + } + // Hover highlight — flat fill (no Fusion border) then delegate + // for text/icon/arrow with Selected cleared + if ((mi->state & State_Selected)) { + p->fillRect(mi->rect, mi->palette.color(QPalette::Highlight)); QStyleOptionMenuItem patched = *mi; - patched.palette.setColor(QPalette::Highlight, - mi->palette.color(QPalette::Mid)); // theme.hover - patched.palette.setColor(QPalette::HighlightedText, + patched.state &= ~State_Selected; + patched.palette.setColor(QPalette::Text, mi->palette.color(QPalette::Link)); // theme.indHoverSpan QProxyStyle::drawControl(element, &patched, p, w); return; @@ -365,7 +376,7 @@ static void applyGlobalTheme(const rcx::Theme& theme) { pal.setColor(QPalette::Text, theme.text); pal.setColor(QPalette::Button, theme.button); pal.setColor(QPalette::ButtonText, theme.text); - pal.setColor(QPalette::Highlight, theme.hover); + pal.setColor(QPalette::Highlight, theme.selected); pal.setColor(QPalette::HighlightedText, theme.text); pal.setColor(QPalette::ToolTipBase, theme.backgroundAlt); pal.setColor(QPalette::ToolTipText, theme.text); @@ -770,6 +781,34 @@ private: QColor m_color; }; +// ── Dock title-bar grip (VS2022-style dot pattern) ── +class DockGripWidget : public QWidget { +public: + explicit DockGripWidget(QWidget* parent) : QWidget(parent) { + setFixedWidth(6); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + m_color = rcx::ThemeManager::instance().current().textFaint; + } + void setGripColor(const QColor& c) { m_color = c; update(); } +protected: + void paintEvent(QPaintEvent*) override { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + p.setPen(Qt::NoPen); + p.setBrush(m_color); + const double r = 0.75, s = 3.0; + double cx = width() / 2.0; + double cy = height() / 2.0; + // 2 columns x 3 rows, centered + for (int row = -1; row <= 1; row++) { + p.drawEllipse(QPointF(cx - s * 0.5, cy + row * s), r, r); + p.drawEllipse(QPointF(cx + s * 0.5, cy + row * s), r, r); + } + } +private: + QColor m_color; +}; + // ── Custom-painted view tab button (no CSS) ── class ViewTabButton : public QPushButton { public: @@ -1882,6 +1921,8 @@ void MainWindow::applyTheme(const Theme& theme) { "QToolButton { color: %1; border: none; padding: 0px 4px 2px 4px; font-size: 12px; }" "QToolButton:hover { color: %2; }") .arg(theme.textDim.name(), theme.indHoverSpan.name())); + if (m_dockGrip) + m_dockGrip->setGripColor(theme.textFaint); // Scanner dock if (m_scannerPanel) @@ -1901,6 +1942,8 @@ void MainWindow::applyTheme(const Theme& theme) { "QToolButton { color: %1; border: none; padding: 0px 4px 2px 4px; font-size: 12px; }" "QToolButton:hover { color: %2; }") .arg(theme.textDim.name(), theme.indHoverSpan.name())); + if (m_scanDockGrip) + m_scanDockGrip->setGripColor(theme.textFaint); // Rendered C/C++ views: update lexer colors, paper, margins for (auto& tab : m_tabs) { @@ -1956,7 +1999,6 @@ void MainWindow::showOptionsDialog() { current.showIcon = m_titleBar ? QSettings("Reclass", "Reclass").value("showIcon", false).toBool() : false; - current.safeMode = QSettings("Reclass", "Reclass").value("safeMode", false).toBool(); current.autoStartMcp = QSettings("Reclass", "Reclass").value("autoStartMcp", true).toBool(); current.refreshMs = QSettings("Reclass", "Reclass").value("refreshMs", 660).toInt(); current.generatorAsserts = QSettings("Reclass", "Reclass").value("generatorAsserts", false).toBool(); @@ -1983,9 +2025,6 @@ void MainWindow::showOptionsDialog() { QSettings("Reclass", "Reclass").setValue("showIcon", r.showIcon); } - if (r.safeMode != current.safeMode) - QSettings("Reclass", "Reclass").setValue("safeMode", r.safeMode); - if (r.autoStartMcp != current.autoStartMcp) QSettings("Reclass", "Reclass").setValue("autoStartMcp", r.autoStartMcp); @@ -2683,8 +2722,11 @@ void MainWindow::createWorkspaceDock() { titleBar->setPalette(tbPal); } auto* layout = new QHBoxLayout(titleBar); - layout->setContentsMargins(6, 2, 2, 2); - layout->setSpacing(0); + layout->setContentsMargins(4, 2, 2, 2); + layout->setSpacing(4); + + m_dockGrip = new DockGripWidget(titleBar); + layout->addWidget(m_dockGrip); m_dockTitleLabel = new QLabel("Project", titleBar); { @@ -2955,8 +2997,11 @@ void MainWindow::createScannerDock() { titleBar->setPalette(tbPal); } auto* layout = new QHBoxLayout(titleBar); - layout->setContentsMargins(6, 2, 2, 2); - layout->setSpacing(0); + layout->setContentsMargins(4, 2, 2, 2); + layout->setSpacing(4); + + m_scanDockGrip = new DockGripWidget(titleBar); + layout->addWidget(m_scanDockGrip); m_scanDockTitle = new QLabel("Scanner", titleBar); { diff --git a/src/mainwindow.h b/src/mainwindow.h index bbb8370..4472fae 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -24,6 +24,7 @@ namespace rcx { class McpBridge; class ShimmerLabel; +class DockGripWidget; class MainWindow : public QMainWindow { Q_OBJECT @@ -158,6 +159,7 @@ private: QLineEdit* m_workspaceSearch = nullptr; QLabel* m_dockTitleLabel = nullptr; QToolButton* m_dockCloseBtn = nullptr; + DockGripWidget* m_dockGrip = nullptr; void createWorkspaceDock(); void rebuildWorkspaceModel(); void updateBorderColor(const QColor& color); @@ -167,6 +169,7 @@ private: ScannerPanel* m_scannerPanel = nullptr; QLabel* m_scanDockTitle = nullptr; QToolButton* m_scanDockCloseBtn = nullptr; + DockGripWidget* m_scanDockGrip = nullptr; void createScannerDock(); protected: diff --git a/src/optionsdialog.cpp b/src/optionsdialog.cpp index 40dc931..0af5d76 100644 --- a/src/optionsdialog.cpp +++ b/src/optionsdialog.cpp @@ -40,9 +40,21 @@ OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent) m_tree->setHeaderHidden(true); m_tree->setRootIsDecorated(true); m_tree->setFixedWidth(200); + m_tree->setMouseTracking(true); + m_tree->setIconSize(QSize(16, 16)); + { + const auto& t = ThemeManager::instance().current(); + QPalette tp = m_tree->palette(); + tp.setColor(QPalette::Text, t.textDim); + tp.setColor(QPalette::Highlight, t.hover); + tp.setColor(QPalette::HighlightedText, t.text); + m_tree->setPalette(tp); + } auto* envItem = new QTreeWidgetItem(m_tree, {"Environment"}); + envItem->setIcon(0, QIcon(":/vsicons/folder.svg")); auto* generalItem = new QTreeWidgetItem(envItem, {"General"}); + generalItem->setIcon(0, QIcon(":/vsicons/settings-gear.svg")); m_tree->expandAll(); m_tree->setCurrentItem(generalItem); leftColumn->addWidget(m_tree, 1); @@ -102,7 +114,7 @@ OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent) m_fontCombo->setObjectName("fontCombo"); visualLayout->addRow("Editor Font:", m_fontCombo); - m_titleCaseCheck = new QCheckBox("Apply title case styling to menu bar"); + m_titleCaseCheck = new QCheckBox("Uppercase menu items"); m_titleCaseCheck->setChecked(current.menuBarTitleCase); visualLayout->addRow(m_titleCaseCheck); @@ -111,24 +123,6 @@ OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent) visualLayout->addRow(m_showIconCheck); generalLayout->addWidget(visualGroup); - - // Safe Mode group box - auto* safeModeGroup = new QGroupBox("Preview Features"); - auto* safeModeLayout = new QVBoxLayout(safeModeGroup); - safeModeLayout->setSpacing(4); - - m_safeModeCheck = new QCheckBox("Safe Mode"); - m_safeModeCheck->setChecked(current.safeMode); - safeModeLayout->addWidget(m_safeModeCheck); - - auto* safeModeDesc = new QLabel( - "Enable to use the default OS icon for this application and " - "create the window with the name of the executable file."); - safeModeDesc->setWordWrap(true); - safeModeDesc->setContentsMargins(20, 0, 0, 0); // indent under checkbox - safeModeLayout->addWidget(safeModeDesc); - - generalLayout->addWidget(safeModeGroup); generalLayout->addStretch(); m_pages->addWidget(generalPage); // index 0 @@ -136,6 +130,7 @@ OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent) // -- AI Features page -- auto* aiItem = new QTreeWidgetItem(envItem, {"AI Features"}); + aiItem->setIcon(0, QIcon(":/vsicons/remote.svg")); auto* aiPage = new QWidget; auto* aiLayout = new QVBoxLayout(aiPage); @@ -165,6 +160,7 @@ OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent) // -- Generator page -- auto* generatorItem = new QTreeWidgetItem(envItem, {"Generator"}); + generatorItem->setIcon(0, QIcon(":/vsicons/code.svg")); auto* generatorPage = new QWidget; auto* generatorLayout = new QVBoxLayout(generatorPage); @@ -213,7 +209,6 @@ OptionsResult OptionsDialog::result() const { r.fontName = m_fontCombo->currentText(); r.menuBarTitleCase = m_titleCaseCheck->isChecked(); r.showIcon = m_showIconCheck->isChecked(); - r.safeMode = m_safeModeCheck->isChecked(); r.autoStartMcp = m_autoMcpCheck->isChecked(); r.refreshMs = m_refreshSpin->value(); r.generatorAsserts = m_assertCheck->isChecked(); diff --git a/src/optionsdialog.h b/src/optionsdialog.h index 314c797..aaa4d92 100644 --- a/src/optionsdialog.h +++ b/src/optionsdialog.h @@ -15,7 +15,6 @@ struct OptionsResult { QString fontName; bool menuBarTitleCase = true; bool showIcon = false; - bool safeMode = false; bool autoStartMcp = true; int refreshMs = 660; bool generatorAsserts = false; @@ -39,7 +38,6 @@ private: QComboBox* m_fontCombo = nullptr; QCheckBox* m_titleCaseCheck = nullptr; QCheckBox* m_showIconCheck = nullptr; - QCheckBox* m_safeModeCheck = nullptr; QCheckBox* m_autoMcpCheck = nullptr; QSpinBox* m_refreshSpin = nullptr; QCheckBox* m_assertCheck = nullptr; diff --git a/src/themes/defaults/tw.json b/src/themes/defaults/tw.json index a5a211c..bb3de96 100644 --- a/src/themes/defaults/tw.json +++ b/src/themes/defaults/tw.json @@ -8,8 +8,8 @@ "button": "#ccccd0", "text": "#1b1b22", "textDim": "#5c5c68", - "textMuted": "#84848e", - "textFaint": "#a8a8b0", + "textMuted": "#6a6a78", + "textFaint": "#8a8a94", "hover": "#d8d8de", "selected": "#d0d0d8", "selection": "#b4c8e8", diff --git a/src/titlebar.cpp b/src/titlebar.cpp index edd3b93..d390af6 100644 --- a/src/titlebar.cpp +++ b/src/titlebar.cpp @@ -107,12 +107,14 @@ void TitleBarWidget::setShowIcon(bool show) { if (show) { m_appLabel->setText(QString()); m_appLabel->setPixmap(QIcon(":/icons/class.png").pixmap(24, 24)); + setFixedHeight(34); } else { m_appLabel->setPixmap(QPixmap()); m_appLabel->setText(QStringLiteral("Reclass")); m_appLabel->setStyleSheet( QStringLiteral("QLabel { color: %1; font-size: 12px; font-weight: bold; }") .arg(m_theme.text.name())); + setFixedHeight(32); } } diff --git a/tests/test_options_dialog.cpp b/tests/test_options_dialog.cpp index 370df5f..630e2e1 100644 --- a/tests/test_options_dialog.cpp +++ b/tests/test_options_dialog.cpp @@ -59,7 +59,6 @@ private slots: defaults.themeIndex = 0; defaults.fontName = "JetBrains Mono"; defaults.menuBarTitleCase = true; - defaults.safeMode = false; defaults.autoStartMcp = false; OptionsDialog dlg(defaults); @@ -93,7 +92,6 @@ private slots: input.themeIndex = 1; input.fontName = "Consolas"; input.menuBarTitleCase = false; - input.safeMode = true; input.autoStartMcp = true; OptionsDialog dlg(input); @@ -102,7 +100,6 @@ private slots: QCOMPARE(r.themeIndex, 1); QCOMPARE(r.fontName, QString("Consolas")); QCOMPARE(r.menuBarTitleCase, false); - QCOMPARE(r.safeMode, true); QCOMPARE(r.autoStartMcp, true); }