From 3a76b03c857b3e347879fb57bb566e67d80bce0a Mon Sep 17 00:00:00 2001 From: IChooseYou Date: Sat, 21 Feb 2026 11:41:46 -0700 Subject: [PATCH] fix: continuous top border on status bar tabs, baseline alignment, 15% taller - ViewTabButton always paints 1px top border matching status bar hairline; selected tab's accent line paints over it - Remove SegmentedContainer (caused gap on unselected tab) - Shared baseline alignment between tab text and status label - Status bar height * 1.15 --- src/main.cpp | 89 +++++++++++++++++++++------------------------------- 1 file changed, 36 insertions(+), 53 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 85f60cd..f86695b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -570,8 +570,10 @@ public: static constexpr int kPadLR = 12; // horizontal padding static constexpr int kPadBot = 4; // extra bottom padding + int baselineY = -1; // set by FlatStatusBar for cross-widget text alignment + QColor colBg, colBgChecked, colBgHover, colBgPressed; - QColor colText, colTextMuted, colAccent; + QColor colText, colTextMuted, colAccent, colBorder; explicit ViewTabButton(const QString& text, QWidget* parent = nullptr) : QPushButton(text, parent) { @@ -599,54 +601,29 @@ protected: else if (isChecked()) bg = colBgChecked; p.fillRect(rect(), bg); - // Accent line at y=0 when checked + // Top border (continuous with status bar hairline) + if (colBorder.isValid()) + p.fillRect(0, 0, width(), 1, colBorder); + + // Accent line at y=0 when checked (paints over border) if (isChecked()) p.fillRect(0, 0, width(), kAccentH, colAccent); - // Text + // Text — use shared baseline if set, otherwise fall back to VCenter p.setPen(isChecked() || underMouse() || isDown() ? colText : colTextMuted); p.setFont(font()); - QRect textRect(kPadLR, kAccentH, width() - 2 * kPadLR, height() - kAccentH); - p.drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, text()); + if (baselineY >= 0) { + p.drawText(kPadLR, baselineY, text()); + } else { + QRect textRect(kPadLR, kAccentH, width() - 2 * kPadLR, height() - kAccentH); + p.drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, text()); + } } void enterEvent(QEnterEvent*) override { update(); } void leaveEvent(QEvent*) override { update(); } }; -// ── Segmented-control container for the view toggle ── -// Draws a 1px border + rounded rect around the buttons with a vertical -// separator between segments so it reads as a mode switcher, not text. -class SegmentedContainer : public QWidget { -public: - QColor colBorder, colSeparator, colBg; - - explicit SegmentedContainer(QWidget* parent = nullptr) : QWidget(parent) {} - -protected: - void paintEvent(QPaintEvent*) override { - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing, true); - - // Container background + rounded border - QRectF r = QRectF(rect()).adjusted(0.5, 0.5, -0.5, -0.5); - p.setPen(QPen(colBorder, 1)); - p.setBrush(colBg); - p.drawRoundedRect(r, 3, 3); - - // Vertical separator between each child - p.setRenderHint(QPainter::Antialiasing, false); - p.setPen(Qt::NoPen); - auto* lay = layout(); - if (!lay) return; - for (int i = 0; i < lay->count() - 1; i++) { - auto* item = lay->itemAt(i); - int x = item->geometry().right() + 1; - p.fillRect(x, 3, 1, height() - 6, colSeparator); - } - } -}; - // ── Borderless status bar with manual child layout ── // QStatusBarLayout hardcodes 2px margins that can't be overridden. // We bypass it entirely: children are placed manually in resizeEvent, @@ -667,7 +644,8 @@ public: QSize sizeHint() const override { const int tabH = tabRow ? tabRow->sizeHint().height() : 0; const int textH = fontMetrics().height(); - const int h = qMax(tabH, textH + 6); + const int base = qMax(tabH, textH + 6); + const int h = qRound(base * 1.15); return { QStatusBar::sizeHint().width(), h }; } QSize minimumSizeHint() const override { return sizeHint(); } @@ -706,6 +684,21 @@ private: m_divX = tw; label->setGeometry(tw + 1 + gutter, 0, qMax(0, width() - (tw + 1 + gutter)), h); + + // Shared baseline so tab text and status text align + QFontMetrics fm(font()); + int by = (h + fm.ascent()) / 2; + + // Push baseline to buttons + auto* lay = tabRow->layout(); + if (lay) { + for (int i = 0; i < lay->count(); i++) + static_cast(lay->itemAt(i)->widget())->baselineY = by; + } + // Align label: set top margin so text baseline matches + int labelTop = by - fm.ascent(); + label->setContentsMargins(0, labelTop, 0, 0); + label->setAlignment(Qt::AlignLeft | Qt::AlignTop); } }; @@ -731,8 +724,8 @@ void MainWindow::createStatusBar() { m_viewBtnGroup->addButton(m_btnReclass, 0); m_viewBtnGroup->addButton(m_btnRendered, 1); - // Wrap buttons in a segmented-control container (border + separator) - auto* tabRow = new SegmentedContainer(sb); + // Wrap buttons in a plain container — FlatStatusBar paints the chrome + auto* tabRow = new QWidget(sb); auto* tabLay = new QHBoxLayout(tabRow); tabLay->setContentsMargins(0, 0, 0, 0); tabLay->setSpacing(0); @@ -774,14 +767,10 @@ void MainWindow::createStatusBar() { btn->colText = t.text; btn->colTextMuted = t.textMuted; btn->colAccent = t.indHoverSpan; + btn->colBorder = t.border; }; applyViewTabColors(static_cast(m_btnReclass)); applyViewTabColors(static_cast(m_btnRendered)); - - auto* seg = static_cast(tabRow); - seg->colBorder = t.border; - seg->colSeparator = t.border; - seg->colBg = t.background; } } @@ -1348,6 +1337,7 @@ void MainWindow::applyTheme(const Theme& theme) { btn->colText = theme.text; btn->colTextMuted = theme.textMuted; btn->colAccent = theme.indHoverSpan; + btn->colBorder = theme.border; btn->update(); }; applyColors(static_cast(m_btnReclass)); @@ -1356,13 +1346,6 @@ void MainWindow::applyTheme(const Theme& theme) { { auto* fsb = static_cast(statusBar()); fsb->setTopLineColor(theme.border); fsb->setDividerColor(theme.border); - if (fsb->tabRow) { - auto* seg = static_cast(fsb->tabRow); - seg->colBorder = theme.border; - seg->colSeparator = theme.border; - seg->colBg = theme.background; - seg->update(); - } } } // Resize grip (direct child of main window, not in status bar)