diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5da5521..5a747dc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,32 +46,7 @@ jobs: export PATH="$IQTA_TOOLS/mingw1310_64/bin:$PATH" 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 - if: github.event_name == 'push' && github.ref == 'refs/heads/main' shell: bash run: | mkdir -p release @@ -89,19 +64,11 @@ jobs: cp build/screenshot.png release/ 2>/dev/null || true cd release && 7z a ../Reclass-win64-qt6.zip * - - name: Upload release asset - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: softprops/action-gh-release@v2 + - name: Upload artifact + uses: actions/upload-artifact@v4 with: - tag_name: snapshot-${{ steps.date.outputs.tag }} - name: Snapshot ${{ steps.date.outputs.tag }} - body: | - Automated snapshot from main branch. - Commit: ${{ github.sha }} - prerelease: false - files: Reclass-win64-qt6.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: Reclass-win64-qt6 + path: Reclass-win64-qt6.zip linux: runs-on: ubuntu-22.04 @@ -167,19 +134,26 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v4 - if: always() with: name: Reclass-linux64-qt6 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 - if: github.event_name == 'push' && github.ref == 'refs/heads/main' id: date - shell: bash run: echo "tag=$(date +'%d-%m-%Y')" >> "$GITHUB_OUTPUT" - - name: Upload release asset - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + - name: Create release uses: softprops/action-gh-release@v2 with: tag_name: snapshot-${{ steps.date.outputs.tag }} @@ -188,6 +162,8 @@ jobs: Automated snapshot from main branch. Commit: ${{ github.sha }} 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: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/src/main.cpp b/src/main.cpp index bb10add..85f60cd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -566,7 +566,7 @@ private: // ── Custom-painted view tab button (no CSS) ── class ViewTabButton : public QPushButton { 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 kPadBot = 4; // extra bottom padding @@ -614,6 +614,39 @@ protected: 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, @@ -624,13 +657,33 @@ public: QWidget* tabRow = 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) { 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: void paintEvent(QPaintEvent*) override { QPainter p(this); 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 { QStatusBar::resizeEvent(e); @@ -641,12 +694,18 @@ protected: manualLayout(); } private: + QColor m_div, m_top; + int m_divX = -1; + void manualLayout() { if (!tabRow || !label) return; - int h = height(); - int tw = tabRow->sizeHint().width(); + const int h = height(); + const int tw = tabRow->sizeHint().width(); + const int gutter = 6; 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); 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 m_viewBtnGroup = new QButtonGroup(this); @@ -671,8 +731,8 @@ void MainWindow::createStatusBar() { m_viewBtnGroup->addButton(m_btnReclass, 0); m_viewBtnGroup->addButton(m_btnRendered, 1); - // Wrap buttons in a zero-margin container — direct child of status bar - auto* tabRow = new QWidget(sb); + // Wrap buttons in a segmented-control container (border + separator) + auto* tabRow = new SegmentedContainer(sb); auto* tabLay = new QHBoxLayout(tabRow); tabLay->setContentsMargins(0, 0, 0, 0); tabLay->setSpacing(0); @@ -682,6 +742,9 @@ void MainWindow::createStatusBar() { sb->tabRow = tabRow; sb->label = m_statusLabel; + sb->setMinimumHeight(qMax(m_btnReclass->sizeHint().height(), + sb->fontMetrics().height() + 6)); + connect(m_viewBtnGroup, &QButtonGroup::idClicked, this, [this](int id) { setViewMode(id == 1 ? VM_Rendered : VM_Reclass); }); @@ -700,6 +763,9 @@ void MainWindow::createStatusBar() { statusBar()->setPalette(sbPal); statusBar()->setAutoFillBackground(true); + sb->setTopLineColor(t.border); + sb->setDividerColor(t.border); + auto applyViewTabColors = [&](ViewTabButton* btn) { btn->colBg = t.background; btn->colBgChecked = t.backgroundAlt; @@ -711,17 +777,13 @@ void MainWindow::createStatusBar() { }; 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; } - // 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); statusBar()->setPalette(sbPal); } - // View toggle buttons in status bar + // View toggle buttons + status bar chrome { auto applyColors = [&](ViewTabButton* btn) { btn->colBg = theme.background; @@ -1290,6 +1352,18 @@ void MainWindow::applyTheme(const Theme& theme) { }; applyColors(static_cast(m_btnReclass)); applyColors(static_cast(m_btnRendered)); + + { 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) if (auto* w = findChild("resizeGrip")) @@ -1427,10 +1501,6 @@ void MainWindow::setEditorFont(const QString& fontName) { // Sync dock titlebar font if (m_dockTitleLabel) m_dockTitleLabel->setFont(f); - // Sync status bar font - statusBar()->setFont(f); - m_btnReclass->setFont(f); - m_btnRendered->setFont(f); } RcxController* MainWindow::activeController() const {