feat: status bar visual upgrade, unified release job

Status bar: top hairline separator, vertical divider between toggle
and status text, segmented-control container with border/separators
around view buttons, accent line 2->3px, proper sizeHint with
breathing room, default system font instead of monospace override.

CI: replace per-job release uploads with a single release job that
waits for both windows and linux, then publishes both artifacts to
one GitHub release.
This commit is contained in:
IChooseYou
2026-02-21 11:09:28 -07:00
parent d65b6c5a29
commit ac94855d6c
2 changed files with 110 additions and 64 deletions

View File

@@ -46,32 +46,7 @@ jobs:
export PATH="$IQTA_TOOLS/mingw1310_64/bin:$PATH" export PATH="$IQTA_TOOLS/mingw1310_64/bin:$PATH"
ctest --test-dir build --output-on-failure ctest --test-dir build --output-on-failure
- name: Upload artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: Reclass-win64-qt6
path: |
build/Reclass.exe
build/ReclassMcpBridge.exe
build/Plugins/*.dll
build/*.dll
build/platforms/
build/styles/
build/imageformats/
build/iconengines/
build/themes/
build/examples/
build/screenshot.png
- name: Get date tag
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
id: date
shell: bash
run: echo "tag=$(date +'%d-%m-%Y')" >> "$GITHUB_OUTPUT"
- name: Package release zip - name: Package release zip
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
shell: bash shell: bash
run: | run: |
mkdir -p release mkdir -p release
@@ -89,19 +64,11 @@ jobs:
cp build/screenshot.png release/ 2>/dev/null || true cp build/screenshot.png release/ 2>/dev/null || true
cd release && 7z a ../Reclass-win64-qt6.zip * cd release && 7z a ../Reclass-win64-qt6.zip *
- name: Upload release asset - name: Upload artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/main' uses: actions/upload-artifact@v4
uses: softprops/action-gh-release@v2
with: with:
tag_name: snapshot-${{ steps.date.outputs.tag }} name: Reclass-win64-qt6
name: Snapshot ${{ steps.date.outputs.tag }} path: Reclass-win64-qt6.zip
body: |
Automated snapshot from main branch.
Commit: ${{ github.sha }}
prerelease: false
files: Reclass-win64-qt6.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
linux: linux:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@@ -167,19 +134,26 @@ jobs:
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
if: always()
with: with:
name: Reclass-linux64-qt6 name: Reclass-linux64-qt6
path: Reclass-linux64-qt6.AppImage path: Reclass-linux64-qt6.AppImage
release:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [windows, linux]
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Get date tag - name: Get date tag
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
id: date id: date
shell: bash
run: echo "tag=$(date +'%d-%m-%Y')" >> "$GITHUB_OUTPUT" run: echo "tag=$(date +'%d-%m-%Y')" >> "$GITHUB_OUTPUT"
- name: Upload release asset - name: Create release
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
tag_name: snapshot-${{ steps.date.outputs.tag }} tag_name: snapshot-${{ steps.date.outputs.tag }}
@@ -188,6 +162,8 @@ jobs:
Automated snapshot from main branch. Automated snapshot from main branch.
Commit: ${{ github.sha }} Commit: ${{ github.sha }}
prerelease: false prerelease: false
files: Reclass-linux64-qt6.AppImage files: |
artifacts/Reclass-win64-qt6/Reclass-win64-qt6.zip
artifacts/Reclass-linux64-qt6/Reclass-linux64-qt6.AppImage
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -566,7 +566,7 @@ private:
// ── Custom-painted view tab button (no CSS) ── // ── Custom-painted view tab button (no CSS) ──
class ViewTabButton : public QPushButton { class ViewTabButton : public QPushButton {
public: public:
static constexpr int kAccentH = 2; // accent line height in pixels static constexpr int kAccentH = 3; // accent line height in pixels
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
@@ -614,6 +614,39 @@ protected:
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,
@@ -624,13 +657,33 @@ public:
QWidget* tabRow = nullptr; // set by createStatusBar QWidget* tabRow = nullptr; // set by createStatusBar
QLabel* label = nullptr; // set by createStatusBar QLabel* label = nullptr; // set by createStatusBar
void setDividerColor(const QColor& c) { m_div = c; update(); }
void setTopLineColor(const QColor& c) { m_top = c; update(); }
explicit FlatStatusBar(QWidget* parent = nullptr) : QStatusBar(parent) { explicit FlatStatusBar(QWidget* parent = nullptr) : QStatusBar(parent) {
setSizeGripEnabled(false); setSizeGripEnabled(false);
} }
QSize sizeHint() const override {
const int tabH = tabRow ? tabRow->sizeHint().height() : 0;
const int textH = fontMetrics().height();
const int h = qMax(tabH, textH + 6);
return { QStatusBar::sizeHint().width(), h };
}
QSize minimumSizeHint() const override { return sizeHint(); }
protected: protected:
void paintEvent(QPaintEvent*) override { void paintEvent(QPaintEvent*) override {
QPainter p(this); QPainter p(this);
p.fillRect(rect(), palette().window()); p.fillRect(rect(), palette().window());
// Top hairline separator
if (m_top.isValid())
p.fillRect(0, 0, width(), 1, m_top);
// Vertical divider between tabRow and label
if (m_div.isValid() && m_divX >= 0)
p.fillRect(m_divX, 4, 1, height() - 8, m_div);
} }
void resizeEvent(QResizeEvent* e) override { void resizeEvent(QResizeEvent* e) override {
QStatusBar::resizeEvent(e); QStatusBar::resizeEvent(e);
@@ -641,12 +694,18 @@ protected:
manualLayout(); manualLayout();
} }
private: private:
QColor m_div, m_top;
int m_divX = -1;
void manualLayout() { void manualLayout() {
if (!tabRow || !label) return; if (!tabRow || !label) return;
int h = height(); const int h = height();
int tw = tabRow->sizeHint().width(); const int tw = tabRow->sizeHint().width();
const int gutter = 6;
tabRow->setGeometry(0, 0, tw, h); tabRow->setGeometry(0, 0, tw, h);
label->setGeometry(tw, 0, width() - tw, h); m_divX = tw;
label->setGeometry(tw + 1 + gutter, 0,
qMax(0, width() - (tw + 1 + gutter)), h);
} }
}; };
@@ -657,7 +716,8 @@ void MainWindow::createStatusBar() {
setStatusBar(sb); setStatusBar(sb);
m_statusLabel = new QLabel("Ready", sb); m_statusLabel = new QLabel("Ready", sb);
m_statusLabel->setContentsMargins(10, 0, 0, 0); m_statusLabel->setContentsMargins(0, 0, 0, 0);
m_statusLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
// View toggle buttons (Reclass / C/C++) — custom painted, no CSS // View toggle buttons (Reclass / C/C++) — custom painted, no CSS
m_viewBtnGroup = new QButtonGroup(this); m_viewBtnGroup = new QButtonGroup(this);
@@ -671,8 +731,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 zero-margin container — direct child of status bar // Wrap buttons in a segmented-control container (border + separator)
auto* tabRow = new QWidget(sb); auto* tabRow = new SegmentedContainer(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);
@@ -682,6 +742,9 @@ void MainWindow::createStatusBar() {
sb->tabRow = tabRow; sb->tabRow = tabRow;
sb->label = m_statusLabel; sb->label = m_statusLabel;
sb->setMinimumHeight(qMax(m_btnReclass->sizeHint().height(),
sb->fontMetrics().height() + 6));
connect(m_viewBtnGroup, &QButtonGroup::idClicked, this, [this](int id) { connect(m_viewBtnGroup, &QButtonGroup::idClicked, this, [this](int id) {
setViewMode(id == 1 ? VM_Rendered : VM_Reclass); setViewMode(id == 1 ? VM_Rendered : VM_Reclass);
}); });
@@ -700,6 +763,9 @@ void MainWindow::createStatusBar() {
statusBar()->setPalette(sbPal); statusBar()->setPalette(sbPal);
statusBar()->setAutoFillBackground(true); statusBar()->setAutoFillBackground(true);
sb->setTopLineColor(t.border);
sb->setDividerColor(t.border);
auto applyViewTabColors = [&](ViewTabButton* btn) { auto applyViewTabColors = [&](ViewTabButton* btn) {
btn->colBg = t.background; btn->colBg = t.background;
btn->colBgChecked = t.backgroundAlt; btn->colBgChecked = t.backgroundAlt;
@@ -711,17 +777,13 @@ void MainWindow::createStatusBar() {
}; };
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;
} }
// Sync status bar font with editor font at startup
{
QString fontName = QSettings("Reclass", "Reclass").value("font", "JetBrains Mono").toString();
QFont f(fontName, 12);
f.setFixedPitch(true);
statusBar()->setFont(f);
m_btnReclass->setFont(f);
m_btnRendered->setFont(f);
}
} }
@@ -1276,7 +1338,7 @@ void MainWindow::applyTheme(const Theme& theme) {
sbPal.setColor(QPalette::WindowText, theme.textDim); sbPal.setColor(QPalette::WindowText, theme.textDim);
statusBar()->setPalette(sbPal); statusBar()->setPalette(sbPal);
} }
// View toggle buttons in status bar // View toggle buttons + status bar chrome
{ {
auto applyColors = [&](ViewTabButton* btn) { auto applyColors = [&](ViewTabButton* btn) {
btn->colBg = theme.background; btn->colBg = theme.background;
@@ -1290,6 +1352,18 @@ void MainWindow::applyTheme(const Theme& theme) {
}; };
applyColors(static_cast<ViewTabButton*>(m_btnReclass)); applyColors(static_cast<ViewTabButton*>(m_btnReclass));
applyColors(static_cast<ViewTabButton*>(m_btnRendered)); applyColors(static_cast<ViewTabButton*>(m_btnRendered));
{ auto* fsb = static_cast<FlatStatusBar*>(statusBar());
fsb->setTopLineColor(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)
if (auto* w = findChild<QWidget*>("resizeGrip")) if (auto* w = findChild<QWidget*>("resizeGrip"))
@@ -1427,10 +1501,6 @@ void MainWindow::setEditorFont(const QString& fontName) {
// Sync dock titlebar font // Sync dock titlebar font
if (m_dockTitleLabel) if (m_dockTitleLabel)
m_dockTitleLabel->setFont(f); m_dockTitleLabel->setFont(f);
// Sync status bar font
statusBar()->setFont(f);
m_btnReclass->setFont(f);
m_btnRendered->setFont(f);
} }
RcxController* MainWindow::activeController() const { RcxController* MainWindow::activeController() const {