mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
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
This commit is contained in:
89
src/main.cpp
89
src/main.cpp
@@ -570,8 +570,10 @@ public:
|
|||||||
static constexpr int kPadLR = 12; // horizontal padding
|
static constexpr int kPadLR = 12; // horizontal padding
|
||||||
static constexpr int kPadBot = 4; // extra bottom 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 colBg, colBgChecked, colBgHover, colBgPressed;
|
||||||
QColor colText, colTextMuted, colAccent;
|
QColor colText, colTextMuted, colAccent, colBorder;
|
||||||
|
|
||||||
explicit ViewTabButton(const QString& text, QWidget* parent = nullptr)
|
explicit ViewTabButton(const QString& text, QWidget* parent = nullptr)
|
||||||
: QPushButton(text, parent) {
|
: QPushButton(text, parent) {
|
||||||
@@ -599,54 +601,29 @@ protected:
|
|||||||
else if (isChecked()) bg = colBgChecked;
|
else if (isChecked()) bg = colBgChecked;
|
||||||
p.fillRect(rect(), bg);
|
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())
|
if (isChecked())
|
||||||
p.fillRect(0, 0, width(), kAccentH, colAccent);
|
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.setPen(isChecked() || underMouse() || isDown() ? colText : colTextMuted);
|
||||||
p.setFont(font());
|
p.setFont(font());
|
||||||
QRect textRect(kPadLR, kAccentH, width() - 2 * kPadLR, height() - kAccentH);
|
if (baselineY >= 0) {
|
||||||
p.drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, text());
|
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 enterEvent(QEnterEvent*) override { update(); }
|
||||||
void leaveEvent(QEvent*) 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 ──
|
// ── Borderless status bar with manual child layout ──
|
||||||
// QStatusBarLayout hardcodes 2px margins that can't be overridden.
|
// QStatusBarLayout hardcodes 2px margins that can't be overridden.
|
||||||
// We bypass it entirely: children are placed manually in resizeEvent,
|
// We bypass it entirely: children are placed manually in resizeEvent,
|
||||||
@@ -667,7 +644,8 @@ public:
|
|||||||
QSize sizeHint() const override {
|
QSize sizeHint() const override {
|
||||||
const int tabH = tabRow ? tabRow->sizeHint().height() : 0;
|
const int tabH = tabRow ? tabRow->sizeHint().height() : 0;
|
||||||
const int textH = fontMetrics().height();
|
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 };
|
return { QStatusBar::sizeHint().width(), h };
|
||||||
}
|
}
|
||||||
QSize minimumSizeHint() const override { return sizeHint(); }
|
QSize minimumSizeHint() const override { return sizeHint(); }
|
||||||
@@ -706,6 +684,21 @@ private:
|
|||||||
m_divX = tw;
|
m_divX = tw;
|
||||||
label->setGeometry(tw + 1 + gutter, 0,
|
label->setGeometry(tw + 1 + gutter, 0,
|
||||||
qMax(0, width() - (tw + 1 + gutter)), h);
|
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<ViewTabButton*>(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_btnReclass, 0);
|
||||||
m_viewBtnGroup->addButton(m_btnRendered, 1);
|
m_viewBtnGroup->addButton(m_btnRendered, 1);
|
||||||
|
|
||||||
// Wrap buttons in a segmented-control container (border + separator)
|
// Wrap buttons in a plain container — FlatStatusBar paints the chrome
|
||||||
auto* tabRow = new SegmentedContainer(sb);
|
auto* tabRow = new QWidget(sb);
|
||||||
auto* tabLay = new QHBoxLayout(tabRow);
|
auto* tabLay = new QHBoxLayout(tabRow);
|
||||||
tabLay->setContentsMargins(0, 0, 0, 0);
|
tabLay->setContentsMargins(0, 0, 0, 0);
|
||||||
tabLay->setSpacing(0);
|
tabLay->setSpacing(0);
|
||||||
@@ -774,14 +767,10 @@ void MainWindow::createStatusBar() {
|
|||||||
btn->colText = t.text;
|
btn->colText = t.text;
|
||||||
btn->colTextMuted = t.textMuted;
|
btn->colTextMuted = t.textMuted;
|
||||||
btn->colAccent = t.indHoverSpan;
|
btn->colAccent = t.indHoverSpan;
|
||||||
|
btn->colBorder = t.border;
|
||||||
};
|
};
|
||||||
applyViewTabColors(static_cast<ViewTabButton*>(m_btnReclass));
|
applyViewTabColors(static_cast<ViewTabButton*>(m_btnReclass));
|
||||||
applyViewTabColors(static_cast<ViewTabButton*>(m_btnRendered));
|
applyViewTabColors(static_cast<ViewTabButton*>(m_btnRendered));
|
||||||
|
|
||||||
auto* seg = static_cast<SegmentedContainer*>(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->colText = theme.text;
|
||||||
btn->colTextMuted = theme.textMuted;
|
btn->colTextMuted = theme.textMuted;
|
||||||
btn->colAccent = theme.indHoverSpan;
|
btn->colAccent = theme.indHoverSpan;
|
||||||
|
btn->colBorder = theme.border;
|
||||||
btn->update();
|
btn->update();
|
||||||
};
|
};
|
||||||
applyColors(static_cast<ViewTabButton*>(m_btnReclass));
|
applyColors(static_cast<ViewTabButton*>(m_btnReclass));
|
||||||
@@ -1356,13 +1346,6 @@ void MainWindow::applyTheme(const Theme& theme) {
|
|||||||
{ auto* fsb = static_cast<FlatStatusBar*>(statusBar());
|
{ auto* fsb = static_cast<FlatStatusBar*>(statusBar());
|
||||||
fsb->setTopLineColor(theme.border);
|
fsb->setTopLineColor(theme.border);
|
||||||
fsb->setDividerColor(theme.border);
|
fsb->setDividerColor(theme.border);
|
||||||
if (fsb->tabRow) {
|
|
||||||
auto* seg = static_cast<SegmentedContainer*>(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)
|
// Resize grip (direct child of main window, not in status bar)
|
||||||
|
|||||||
Reference in New Issue
Block a user