mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Added custom title bar & border color when focused
This commit is contained in:
@@ -60,6 +60,8 @@ add_executable(Reclass
|
|||||||
src/themes/themeeditor.h
|
src/themes/themeeditor.h
|
||||||
src/themes/themeeditor.cpp
|
src/themes/themeeditor.cpp
|
||||||
src/mainwindow.h
|
src/mainwindow.h
|
||||||
|
src/titlebar.h
|
||||||
|
src/titlebar.cpp
|
||||||
src/mcp/mcp_bridge.h
|
src/mcp/mcp_bridge.h
|
||||||
src/mcp/mcp_bridge.cpp
|
src/mcp/mcp_bridge.cpp
|
||||||
$<$<PLATFORM_ID:Windows>:src/app.rc>
|
$<$<PLATFORM_ID:Windows>:src/app.rc>
|
||||||
|
|||||||
90
src/main.cpp
90
src/main.cpp
@@ -46,6 +46,7 @@
|
|||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <windowsx.h>
|
||||||
#include <dwmapi.h>
|
#include <dwmapi.h>
|
||||||
#include <dbghelp.h>
|
#include <dbghelp.h>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@@ -230,6 +231,21 @@ static void applyGlobalTheme(const rcx::Theme& theme) {
|
|||||||
qApp->setStyleSheet(QString());
|
qApp->setStyleSheet(QString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BorderOverlay : public QWidget {
|
||||||
|
public:
|
||||||
|
QColor color;
|
||||||
|
explicit BorderOverlay(QWidget* parent) : QWidget(parent) {
|
||||||
|
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
setAttribute(Qt::WA_NoSystemBackground);
|
||||||
|
setFocusPolicy(Qt::NoFocus);
|
||||||
|
}
|
||||||
|
void paintEvent(QPaintEvent*) override {
|
||||||
|
QPainter p(this);
|
||||||
|
p.setPen(color);
|
||||||
|
p.drawRect(0, 0, width() - 1, height() - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
namespace rcx {
|
namespace rcx {
|
||||||
|
|
||||||
// MainWindow class declaration is in mainwindow.h
|
// MainWindow class declaration is in mainwindow.h
|
||||||
@@ -238,6 +254,32 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
|||||||
setWindowTitle("Reclass");
|
setWindowTitle("Reclass");
|
||||||
resize(1200, 800);
|
resize(1200, 800);
|
||||||
|
|
||||||
|
// Frameless window with system menu (Alt+Space) and min/max/close support
|
||||||
|
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint
|
||||||
|
| Qt::WindowMinMaxButtonsHint);
|
||||||
|
|
||||||
|
// Custom title bar (replaces native menu bar area in QMainWindow)
|
||||||
|
m_titleBar = new TitleBarWidget(this);
|
||||||
|
m_titleBar->applyTheme(ThemeManager::instance().current());
|
||||||
|
setMenuWidget(m_titleBar);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// 1px top margin preserves DWM drop shadow on the frameless window
|
||||||
|
{
|
||||||
|
auto hwnd = reinterpret_cast<HWND>(winId());
|
||||||
|
MARGINS margins = {0, 0, 1, 0};
|
||||||
|
DwmExtendFrameIntoClientArea(hwnd, &margins);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Border overlay — draws a 1px colored border on top of everything
|
||||||
|
auto* overlay = new BorderOverlay(this);
|
||||||
|
m_borderOverlay = overlay;
|
||||||
|
overlay->color = ThemeManager::instance().current().borderFocused;
|
||||||
|
overlay->setGeometry(rect());
|
||||||
|
overlay->raise();
|
||||||
|
overlay->show();
|
||||||
|
|
||||||
m_mdiArea = new QMdiArea(this);
|
m_mdiArea = new QMdiArea(this);
|
||||||
m_mdiArea->setViewMode(QMdiArea::TabbedView);
|
m_mdiArea->setViewMode(QMdiArea::TabbedView);
|
||||||
m_mdiArea->setTabsClosable(true);
|
m_mdiArea->setTabsClosable(true);
|
||||||
@@ -306,7 +348,7 @@ QIcon MainWindow::makeIcon(const QString& svgPath) {
|
|||||||
|
|
||||||
void MainWindow::createMenus() {
|
void MainWindow::createMenus() {
|
||||||
// File
|
// File
|
||||||
auto* file = menuBar()->addMenu("&File");
|
auto* file = m_titleBar->menuBar()->addMenu("&File");
|
||||||
file->addAction("&New", this, &MainWindow::newDocument, QKeySequence::New);
|
file->addAction("&New", this, &MainWindow::newDocument, QKeySequence::New);
|
||||||
file->addAction("New &Tab", this, &MainWindow::newFile, QKeySequence(Qt::CTRL | Qt::Key_T));
|
file->addAction("New &Tab", this, &MainWindow::newFile, QKeySequence(Qt::CTRL | Qt::Key_T));
|
||||||
file->addAction(makeIcon(":/vsicons/folder-opened.svg"), "&Open...", this, &MainWindow::openFile, QKeySequence::Open);
|
file->addAction(makeIcon(":/vsicons/folder-opened.svg"), "&Open...", this, &MainWindow::openFile, QKeySequence::Open);
|
||||||
@@ -321,14 +363,14 @@ void MainWindow::createMenus() {
|
|||||||
file->addAction(makeIcon(":/vsicons/close.svg"), "E&xit", this, &QMainWindow::close, QKeySequence(Qt::Key_Close));
|
file->addAction(makeIcon(":/vsicons/close.svg"), "E&xit", this, &QMainWindow::close, QKeySequence(Qt::Key_Close));
|
||||||
|
|
||||||
// Edit
|
// Edit
|
||||||
auto* edit = menuBar()->addMenu("&Edit");
|
auto* edit = m_titleBar->menuBar()->addMenu("&Edit");
|
||||||
edit->addAction(makeIcon(":/vsicons/arrow-left.svg"), "&Undo", this, &MainWindow::undo, QKeySequence::Undo);
|
edit->addAction(makeIcon(":/vsicons/arrow-left.svg"), "&Undo", this, &MainWindow::undo, QKeySequence::Undo);
|
||||||
edit->addAction(makeIcon(":/vsicons/arrow-right.svg"), "&Redo", this, &MainWindow::redo, QKeySequence::Redo);
|
edit->addAction(makeIcon(":/vsicons/arrow-right.svg"), "&Redo", this, &MainWindow::redo, QKeySequence::Redo);
|
||||||
edit->addSeparator();
|
edit->addSeparator();
|
||||||
edit->addAction("&Type Aliases...", this, &MainWindow::showTypeAliasesDialog);
|
edit->addAction("&Type Aliases...", this, &MainWindow::showTypeAliasesDialog);
|
||||||
|
|
||||||
// View
|
// View
|
||||||
auto* view = menuBar()->addMenu("&View");
|
auto* view = m_titleBar->menuBar()->addMenu("&View");
|
||||||
view->addAction(makeIcon(":/vsicons/split-horizontal.svg"), "Split &Horizontal", this, &MainWindow::splitView);
|
view->addAction(makeIcon(":/vsicons/split-horizontal.svg"), "Split &Horizontal", this, &MainWindow::splitView);
|
||||||
view->addAction(makeIcon(":/vsicons/chrome-close.svg"), "&Unsplit", this, &MainWindow::unsplitView);
|
view->addAction(makeIcon(":/vsicons/chrome-close.svg"), "&Unsplit", this, &MainWindow::unsplitView);
|
||||||
view->addSeparator();
|
view->addSeparator();
|
||||||
@@ -371,7 +413,7 @@ void MainWindow::createMenus() {
|
|||||||
view->addAction(m_workspaceDock->toggleViewAction());
|
view->addAction(m_workspaceDock->toggleViewAction());
|
||||||
|
|
||||||
// Node
|
// Node
|
||||||
auto* node = menuBar()->addMenu("&Node");
|
auto* node = m_titleBar->menuBar()->addMenu("&Node");
|
||||||
node->addAction(makeIcon(":/vsicons/add.svg"), "&Add Field", this, &MainWindow::addNode, QKeySequence(Qt::Key_Insert));
|
node->addAction(makeIcon(":/vsicons/add.svg"), "&Add Field", this, &MainWindow::addNode, QKeySequence(Qt::Key_Insert));
|
||||||
node->addAction(makeIcon(":/vsicons/remove.svg"), "&Remove Field", this, &MainWindow::removeNode, QKeySequence::Delete);
|
node->addAction(makeIcon(":/vsicons/remove.svg"), "&Remove Field", this, &MainWindow::removeNode, QKeySequence::Delete);
|
||||||
node->addAction(makeIcon(":/vsicons/symbol-structure.svg"), "Change &Type", this, &MainWindow::changeNodeType, QKeySequence(Qt::Key_T));
|
node->addAction(makeIcon(":/vsicons/symbol-structure.svg"), "Change &Type", this, &MainWindow::changeNodeType, QKeySequence(Qt::Key_T));
|
||||||
@@ -379,11 +421,11 @@ void MainWindow::createMenus() {
|
|||||||
node->addAction(makeIcon(":/vsicons/files.svg"), "D&uplicate", this, &MainWindow::duplicateNodeAction)->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_D));
|
node->addAction(makeIcon(":/vsicons/files.svg"), "D&uplicate", this, &MainWindow::duplicateNodeAction)->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_D));
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
auto* plugins = menuBar()->addMenu("&Plugins");
|
auto* plugins = m_titleBar->menuBar()->addMenu("&Plugins");
|
||||||
plugins->addAction("&Manage Plugins...", this, &MainWindow::showPluginsDialog);
|
plugins->addAction("&Manage Plugins...", this, &MainWindow::showPluginsDialog);
|
||||||
|
|
||||||
// Help
|
// Help
|
||||||
auto* help = menuBar()->addMenu("&Help");
|
auto* help = m_titleBar->menuBar()->addMenu("&Help");
|
||||||
help->addAction(makeIcon(":/vsicons/question.svg"), "&About Reclass", this, &MainWindow::about);
|
help->addAction(makeIcon(":/vsicons/question.svg"), "&About Reclass", this, &MainWindow::about);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -879,6 +921,12 @@ void MainWindow::toggleMcp() {
|
|||||||
void MainWindow::applyTheme(const Theme& theme) {
|
void MainWindow::applyTheme(const Theme& theme) {
|
||||||
applyGlobalTheme(theme);
|
applyGlobalTheme(theme);
|
||||||
|
|
||||||
|
// Custom title bar
|
||||||
|
m_titleBar->applyTheme(theme);
|
||||||
|
|
||||||
|
// Update border overlay color
|
||||||
|
updateBorderColor(isActiveWindow() ? theme.borderFocused : theme.border);
|
||||||
|
|
||||||
// MDI area tabs
|
// MDI area tabs
|
||||||
m_mdiArea->setStyleSheet(QStringLiteral(
|
m_mdiArea->setStyleSheet(QStringLiteral(
|
||||||
"QTabBar::tab {"
|
"QTabBar::tab {"
|
||||||
@@ -984,6 +1032,7 @@ MainWindow::TabState* MainWindow::tabByIndex(int index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::updateWindowTitle() {
|
void MainWindow::updateWindowTitle() {
|
||||||
|
QString title;
|
||||||
auto* sub = m_mdiArea->activeSubWindow();
|
auto* sub = m_mdiArea->activeSubWindow();
|
||||||
if (sub && m_tabs.contains(sub)) {
|
if (sub && m_tabs.contains(sub)) {
|
||||||
auto& tab = m_tabs[sub];
|
auto& tab = m_tabs[sub];
|
||||||
@@ -991,10 +1040,12 @@ void MainWindow::updateWindowTitle() {
|
|||||||
? rootName(tab.doc->tree, tab.ctrl->viewRootId())
|
? rootName(tab.doc->tree, tab.ctrl->viewRootId())
|
||||||
: QFileInfo(tab.doc->filePath).fileName();
|
: QFileInfo(tab.doc->filePath).fileName();
|
||||||
if (tab.doc->modified) name += " *";
|
if (tab.doc->modified) name += " *";
|
||||||
setWindowTitle(name + " - Reclass");
|
title = name + " - Reclass";
|
||||||
} else {
|
} else {
|
||||||
setWindowTitle("Reclass");
|
title = "Reclass";
|
||||||
}
|
}
|
||||||
|
setWindowTitle(title);
|
||||||
|
m_titleBar->setTitle(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Rendered view setup ──
|
// ── Rendered view setup ──
|
||||||
@@ -1474,6 +1525,29 @@ void MainWindow::showPluginsDialog() {
|
|||||||
dialog.exec();
|
dialog.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::changeEvent(QEvent* event) {
|
||||||
|
QMainWindow::changeEvent(event);
|
||||||
|
if (event->type() == QEvent::ActivationChange) {
|
||||||
|
const auto& t = ThemeManager::instance().current();
|
||||||
|
updateBorderColor(isActiveWindow() ? t.borderFocused : t.border);
|
||||||
|
}
|
||||||
|
if (event->type() == QEvent::WindowStateChange)
|
||||||
|
m_titleBar->updateMaximizeIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::resizeEvent(QResizeEvent* event) {
|
||||||
|
QMainWindow::resizeEvent(event);
|
||||||
|
if (m_borderOverlay) {
|
||||||
|
m_borderOverlay->setGeometry(rect());
|
||||||
|
m_borderOverlay->raise();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::updateBorderColor(const QColor& color) {
|
||||||
|
static_cast<BorderOverlay*>(m_borderOverlay)->color = color;
|
||||||
|
m_borderOverlay->update();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace rcx
|
} // namespace rcx
|
||||||
|
|
||||||
// ── Entry point ──
|
// ── Entry point ──
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
#include "titlebar.h"
|
||||||
#include "pluginmanager.h"
|
#include "pluginmanager.h"
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QMdiArea>
|
#include <QMdiArea>
|
||||||
@@ -61,6 +62,8 @@ private:
|
|||||||
|
|
||||||
QMdiArea* m_mdiArea;
|
QMdiArea* m_mdiArea;
|
||||||
QLabel* m_statusLabel;
|
QLabel* m_statusLabel;
|
||||||
|
TitleBarWidget* m_titleBar = nullptr;
|
||||||
|
QWidget* m_borderOverlay = nullptr;
|
||||||
PluginManager m_pluginManager;
|
PluginManager m_pluginManager;
|
||||||
McpBridge* m_mcp = nullptr;
|
McpBridge* m_mcp = nullptr;
|
||||||
QAction* m_mcpAction = nullptr;
|
QAction* m_mcpAction = nullptr;
|
||||||
@@ -114,6 +117,11 @@ private:
|
|||||||
QStandardItemModel* m_workspaceModel = nullptr;
|
QStandardItemModel* m_workspaceModel = nullptr;
|
||||||
void createWorkspaceDock();
|
void createWorkspaceDock();
|
||||||
void rebuildWorkspaceModel();
|
void rebuildWorkspaceModel();
|
||||||
|
void updateBorderColor(const QColor& color);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void changeEvent(QEvent* event) override;
|
||||||
|
void resizeEvent(QResizeEvent* event) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rcx
|
} // namespace rcx
|
||||||
|
|||||||
@@ -20,6 +20,9 @@
|
|||||||
<file alias="arrow-right.svg">vsicons/arrow-right.svg</file>
|
<file alias="arrow-right.svg">vsicons/arrow-right.svg</file>
|
||||||
<file alias="split-horizontal.svg">vsicons/split-horizontal.svg</file>
|
<file alias="split-horizontal.svg">vsicons/split-horizontal.svg</file>
|
||||||
<file alias="chrome-close.svg">vsicons/chrome-close.svg</file>
|
<file alias="chrome-close.svg">vsicons/chrome-close.svg</file>
|
||||||
|
<file alias="chrome-minimize.svg">vsicons/chrome-minimize.svg</file>
|
||||||
|
<file alias="chrome-maximize.svg">vsicons/chrome-maximize.svg</file>
|
||||||
|
<file alias="chrome-restore.svg">vsicons/chrome-restore.svg</file>
|
||||||
<file alias="text-size.svg">vsicons/text-size.svg</file>
|
<file alias="text-size.svg">vsicons/text-size.svg</file>
|
||||||
<file alias="add.svg">vsicons/add.svg</file>
|
<file alias="add.svg">vsicons/add.svg</file>
|
||||||
<file alias="remove.svg">vsicons/remove.svg</file>
|
<file alias="remove.svg">vsicons/remove.svg</file>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ static const ColorField kFields[] = {
|
|||||||
{"backgroundAlt", &Theme::backgroundAlt},
|
{"backgroundAlt", &Theme::backgroundAlt},
|
||||||
{"surface", &Theme::surface},
|
{"surface", &Theme::surface},
|
||||||
{"border", &Theme::border},
|
{"border", &Theme::border},
|
||||||
|
{"borderFocused", &Theme::borderFocused},
|
||||||
{"button", &Theme::button},
|
{"button", &Theme::button},
|
||||||
{"text", &Theme::text},
|
{"text", &Theme::text},
|
||||||
{"textDim", &Theme::textDim},
|
{"textDim", &Theme::textDim},
|
||||||
@@ -61,6 +62,7 @@ Theme Theme::reclassDark() {
|
|||||||
t.backgroundAlt = QColor("#252526");
|
t.backgroundAlt = QColor("#252526");
|
||||||
t.surface = QColor("#2a2d2e");
|
t.surface = QColor("#2a2d2e");
|
||||||
t.border = QColor("#3c3c3c");
|
t.border = QColor("#3c3c3c");
|
||||||
|
t.borderFocused = QColor("#64e6b450"); // indHoverSpan at ~40% alpha
|
||||||
t.button = QColor("#333333");
|
t.button = QColor("#333333");
|
||||||
t.text = QColor("#d4d4d4");
|
t.text = QColor("#d4d4d4");
|
||||||
t.textDim = QColor("#858585");
|
t.textDim = QColor("#858585");
|
||||||
@@ -92,6 +94,7 @@ Theme Theme::warm() {
|
|||||||
t.backgroundAlt = QColor("#2a2a2a");
|
t.backgroundAlt = QColor("#2a2a2a");
|
||||||
t.surface = QColor("#2a2a2a");
|
t.surface = QColor("#2a2a2a");
|
||||||
t.border = QColor("#373737");
|
t.border = QColor("#373737");
|
||||||
|
t.borderFocused = QColor("#64aa9565"); // indHoverSpan at ~40% alpha
|
||||||
t.button = QColor("#373737");
|
t.button = QColor("#373737");
|
||||||
t.text = QColor("#AAA99F");
|
t.text = QColor("#AAA99F");
|
||||||
t.textDim = QColor("#7a7a6e");
|
t.textDim = QColor("#7a7a6e");
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ struct Theme {
|
|||||||
QColor backgroundAlt; // panels, tab selected, tooltips
|
QColor backgroundAlt; // panels, tab selected, tooltips
|
||||||
QColor surface; // alternateBase
|
QColor surface; // alternateBase
|
||||||
QColor border; // separators, menu borders
|
QColor border; // separators, menu borders
|
||||||
|
QColor borderFocused; // window border when focused
|
||||||
QColor button; // button bg
|
QColor button; // button bg
|
||||||
|
|
||||||
// ── Text ──
|
// ── Text ──
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ ThemeEditor::ThemeEditor(int themeIndex, QWidget* parent)
|
|||||||
{"Background Alt", &Theme::backgroundAlt},
|
{"Background Alt", &Theme::backgroundAlt},
|
||||||
{"Surface", &Theme::surface},
|
{"Surface", &Theme::surface},
|
||||||
{"Border", &Theme::border},
|
{"Border", &Theme::border},
|
||||||
|
{"Border Focused", &Theme::borderFocused},
|
||||||
{"Button", &Theme::button},
|
{"Button", &Theme::button},
|
||||||
});
|
});
|
||||||
addGroup("Text", {
|
addGroup("Text", {
|
||||||
|
|||||||
158
src/titlebar.cpp
Normal file
158
src/titlebar.cpp
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
#include "titlebar.h"
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QStyle>
|
||||||
|
#include <QWindow>
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
TitleBarWidget::TitleBarWidget(QWidget* parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, m_theme(Theme::reclassDark())
|
||||||
|
{
|
||||||
|
setFixedHeight(32);
|
||||||
|
|
||||||
|
auto* layout = new QHBoxLayout(this);
|
||||||
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
layout->setSpacing(0);
|
||||||
|
|
||||||
|
// App icon
|
||||||
|
auto* iconLabel = new QLabel(this);
|
||||||
|
iconLabel->setPixmap(QPixmap(":/icons/class.png").scaled(24, 24, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||||
|
iconLabel->setFixedSize(32, 32);
|
||||||
|
iconLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
iconLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
layout->addWidget(iconLabel);
|
||||||
|
|
||||||
|
// Menu bar
|
||||||
|
m_menuBar = new QMenuBar(this);
|
||||||
|
m_menuBar->setNativeMenuBar(false);
|
||||||
|
m_menuBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
|
||||||
|
layout->addWidget(m_menuBar);
|
||||||
|
|
||||||
|
layout->addStretch();
|
||||||
|
|
||||||
|
// Title label (centered, transparent to mouse so drag works through it)
|
||||||
|
m_titleLabel = new QLabel(this);
|
||||||
|
m_titleLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
m_titleLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
layout->addWidget(m_titleLabel);
|
||||||
|
|
||||||
|
layout->addStretch();
|
||||||
|
|
||||||
|
// Chrome buttons
|
||||||
|
m_btnMin = makeChromeButton(":/vsicons/chrome-minimize.svg");
|
||||||
|
m_btnMax = makeChromeButton(":/vsicons/chrome-maximize.svg");
|
||||||
|
m_btnClose = makeChromeButton(":/vsicons/chrome-close.svg");
|
||||||
|
|
||||||
|
layout->addWidget(m_btnMin);
|
||||||
|
layout->addWidget(m_btnMax);
|
||||||
|
layout->addWidget(m_btnClose);
|
||||||
|
|
||||||
|
connect(m_btnMin, &QToolButton::clicked, this, [this]() {
|
||||||
|
window()->showMinimized();
|
||||||
|
});
|
||||||
|
connect(m_btnMax, &QToolButton::clicked, this, [this]() {
|
||||||
|
toggleMaximize();
|
||||||
|
});
|
||||||
|
connect(m_btnClose, &QToolButton::clicked, this, [this]() {
|
||||||
|
window()->close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QToolButton* TitleBarWidget::makeChromeButton(const QString& iconPath) {
|
||||||
|
auto* btn = new QToolButton(this);
|
||||||
|
btn->setIcon(QIcon(iconPath));
|
||||||
|
btn->setIconSize(QSize(16, 16));
|
||||||
|
btn->setFixedSize(46, 32);
|
||||||
|
btn->setAutoRaise(true);
|
||||||
|
btn->setFocusPolicy(Qt::NoFocus);
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitleBarWidget::setTitle(const QString& title) {
|
||||||
|
m_titleLabel->setText(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitleBarWidget::applyTheme(const Theme& theme) {
|
||||||
|
m_theme = theme;
|
||||||
|
|
||||||
|
// Title bar background
|
||||||
|
setAutoFillBackground(true);
|
||||||
|
QPalette pal = palette();
|
||||||
|
pal.setColor(QPalette::Window, theme.background);
|
||||||
|
setPalette(pal);
|
||||||
|
|
||||||
|
// Title text
|
||||||
|
m_titleLabel->setStyleSheet(
|
||||||
|
QStringLiteral("QLabel { color: %1; font-size: 12px; }")
|
||||||
|
.arg(theme.textDim.name()));
|
||||||
|
|
||||||
|
// Menu bar styling — transparent background, themed text
|
||||||
|
m_menuBar->setStyleSheet(
|
||||||
|
QStringLiteral(
|
||||||
|
"QMenuBar { background: transparent; border: none; }"
|
||||||
|
"QMenuBar::item { background: transparent; color: %1; padding: 8px 8px 4px 8px; }"
|
||||||
|
"QMenuBar::item:selected { background: %2; }"
|
||||||
|
"QMenuBar::item:pressed { background: %2; }")
|
||||||
|
.arg(theme.textDim.name(), theme.hover.name()));
|
||||||
|
|
||||||
|
// Chrome buttons
|
||||||
|
QString btnStyle = QStringLiteral(
|
||||||
|
"QToolButton { background: transparent; border: none; }"
|
||||||
|
"QToolButton:hover { background: %1; }")
|
||||||
|
.arg(theme.hover.name());
|
||||||
|
m_btnMin->setStyleSheet(btnStyle);
|
||||||
|
m_btnMax->setStyleSheet(btnStyle);
|
||||||
|
|
||||||
|
// Close button: red hover
|
||||||
|
m_btnClose->setStyleSheet(QStringLiteral(
|
||||||
|
"QToolButton { background: transparent; border: none; }"
|
||||||
|
"QToolButton:hover { background: #c42b1c; }"));
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitleBarWidget::updateMaximizeIcon() {
|
||||||
|
if (window()->isMaximized())
|
||||||
|
m_btnMax->setIcon(QIcon(":/vsicons/chrome-restore.svg"));
|
||||||
|
else
|
||||||
|
m_btnMax->setIcon(QIcon(":/vsicons/chrome-maximize.svg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitleBarWidget::toggleMaximize() {
|
||||||
|
if (window()->isMaximized())
|
||||||
|
window()->showNormal();
|
||||||
|
else
|
||||||
|
window()->showMaximized();
|
||||||
|
updateMaximizeIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitleBarWidget::mousePressEvent(QMouseEvent* event) {
|
||||||
|
if (event->button() == Qt::LeftButton) {
|
||||||
|
window()->windowHandle()->startSystemMove();
|
||||||
|
event->accept();
|
||||||
|
} else {
|
||||||
|
QWidget::mousePressEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitleBarWidget::mouseDoubleClickEvent(QMouseEvent* event) {
|
||||||
|
if (event->button() == Qt::LeftButton) {
|
||||||
|
toggleMaximize();
|
||||||
|
event->accept();
|
||||||
|
} else {
|
||||||
|
QWidget::mouseDoubleClickEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitleBarWidget::paintEvent(QPaintEvent* event) {
|
||||||
|
QWidget::paintEvent(event);
|
||||||
|
|
||||||
|
// 1px bottom border
|
||||||
|
QPainter p(this);
|
||||||
|
p.setPen(m_theme.border);
|
||||||
|
p.drawLine(0, height() - 1, width() - 1, height() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
40
src/titlebar.h
Normal file
40
src/titlebar.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "themes/theme.h"
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QMenuBar>
|
||||||
|
#include <QToolButton>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
class TitleBarWidget : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit TitleBarWidget(QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
QMenuBar* menuBar() const { return m_menuBar; }
|
||||||
|
void setTitle(const QString& title);
|
||||||
|
void applyTheme(const Theme& theme);
|
||||||
|
|
||||||
|
void updateMaximizeIcon();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void mousePressEvent(QMouseEvent* event) override;
|
||||||
|
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||||
|
void paintEvent(QPaintEvent* event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QMenuBar* m_menuBar = nullptr;
|
||||||
|
QLabel* m_titleLabel = nullptr;
|
||||||
|
QToolButton* m_btnMin = nullptr;
|
||||||
|
QToolButton* m_btnMax = nullptr;
|
||||||
|
QToolButton* m_btnClose = nullptr;
|
||||||
|
|
||||||
|
Theme m_theme;
|
||||||
|
|
||||||
|
QToolButton* makeChromeButton(const QString& iconPath);
|
||||||
|
void toggleMaximize();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
Reference in New Issue
Block a user