Add mac hot-switch implementations
| | |
| | | Let `WidgetWindowAgent` know which widget the title bar is. |
| | | |
| | | ```c++ |
| | | agent->setTitleBarWidget(myTitleBar); |
| | | agent->setTitleBar(myTitleBar); |
| | | ``` |
| | | |
| | | Next, set system button hints to let `WidgetWindowAgent` know the role of the child widgets, which is important for the Snap Layout to work. |
New file |
| | |
| | | /* Window bar */ |
| | | |
| | | QWK--WindowBar[bar-active=true] { |
| | | background-color: #195ABE; |
| | | } |
| | | |
| | | QWK--WindowBar[bar-active=false] { |
| | | background-color: #195ABE; |
| | | } |
| | | |
| | | |
| | | /* Title label */ |
| | | |
| | | QWK--WindowBar>QLabel#win-title-label { |
| | | padding: 0; |
| | | border: none; |
| | | color: #ECECEC; |
| | | background-color: transparent; |
| | | min-height: 28px; |
| | | } |
| | | |
| | | |
| | | /* System buttons */ |
| | | |
| | | QWK--WindowBar>QAbstractButton[system-button=true] { |
| | | qproperty-iconSize: 12px 12px; |
| | | min-width: 50px; |
| | | border: none; |
| | | padding: 0; |
| | | background-color: transparent; |
| | | } |
| | | |
| | | QWK--WindowBar>QAbstractButton#min-button { |
| | | qproperty-iconNormal: url(":/window-bar/minimize.svg"); |
| | | qproperty-iconSize: 12px 12px; |
| | | } |
| | | |
| | | QWK--WindowBar>QAbstractButton#min-button:hover, |
| | | QWK--WindowBar>QAbstractButton#min-button:pressed { |
| | | background-color: rgba(0, 0, 0, 15%); |
| | | } |
| | | |
| | | QWK--WindowBar>QAbstractButton#max-button { |
| | | qproperty-iconNormal: url(":/window-bar/maximize.svg"); |
| | | qproperty-iconChecked: url(":/window-bar/restore.svg"); |
| | | } |
| | | |
| | | QWK--WindowBar>QAbstractButton#max-button:hover, |
| | | QWK--WindowBar>QAbstractButton#max-button:pressed { |
| | | background-color: rgba(0, 0, 0, 15%); |
| | | } |
| | | |
| | | QWK--WindowBar>QAbstractButton#close-button { |
| | | qproperty-iconNormal: url(":/window-bar/close.svg"); |
| | | } |
| | | |
| | | QWK--WindowBar>QAbstractButton#close-button:hover, |
| | | QWK--WindowBar>QAbstractButton#close-button:pressed { |
| | | background-color: #e81123; |
| | | } |
| | | |
| | | |
| | | /* Icon button */ |
| | | |
| | | QWK--WindowBar>QAbstractButton#icon-button { |
| | | qproperty-iconNormal: url(":/app/example.png"); |
| | | qproperty-iconSize: 18px 18px; |
| | | min-width: 40px; |
| | | border: none; |
| | | padding: 0; |
| | | background-color: transparent; |
| | | } |
| | | |
| | | |
| | | /* Menu Bar */ |
| | | |
| | | QMenuBar { |
| | | background-color: transparent; |
| | | border: none; |
| | | } |
| | | |
| | | QMenuBar>QToolButton#qt_menubar_ext_button { |
| | | qproperty-icon: url(":/window-bar/more-line.svg"); |
| | | } |
| | | |
| | | QMenuBar>QToolButton#qt_menubar_ext_button:hover, |
| | | QMenuBar>QToolButton#qt_menubar_ext_button:pressed { |
| | | background-color: rgba(255, 255, 255, 10%); |
| | | } |
| | | |
| | | QMenuBar::item { |
| | | color: #EEEEEE; |
| | | border: none; |
| | | padding: 8px 12px; |
| | | } |
| | | |
| | | QMenuBar::item:selected { |
| | | background-color: rgba(255, 255, 255, 10%); |
| | | } |
| | | |
| | | |
| | | /* Menu */ |
| | | |
| | | QMenu { |
| | | padding: 4px; |
| | | background: #303030; |
| | | border: 1.25px solid transparent; |
| | | } |
| | | |
| | | QMenu::indicator { |
| | | left: 6px; |
| | | width: 20px; |
| | | height: 20px; |
| | | } |
| | | |
| | | QMenu::icon { |
| | | left: 6px; |
| | | } |
| | | |
| | | QMenu::item { |
| | | background: transparent; |
| | | color: #CCCCCC; |
| | | padding: 6px 24px; |
| | | } |
| | | |
| | | QMenu::item:selected { |
| | | color: white; |
| | | background-color: #0060C0; |
| | | } |
| | | |
| | | QMenu::item:disabled { |
| | | color: #666666; |
| | | background-color: transparent; |
| | | } |
| | | |
| | | QMenu::separator { |
| | | height: 2px; |
| | | background-color: #5B5B5B; |
| | | margin: 6px 0; |
| | | } |
| | | |
| | | |
| | | /* Window */ |
| | | |
| | | MainWindow { |
| | | background-color: #F3F3F3; |
| | | } |
| | | |
| | | QWidget#clock-widget { |
| | | font-size: 75px; |
| | | color: #333333; |
| | | font-weight: bold; |
| | | background-color: transparent; |
| | | } |
| | |
| | | clockWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); |
| | | setCentralWidget(clockWidget); |
| | | |
| | | if (QFile qss(QStringLiteral(":/dark-style.qss")); |
| | | qss.open(QIODevice::ReadOnly | QIODevice::Text)) { |
| | | setStyleSheet(QString::fromUtf8(qss.readAll())); |
| | | } |
| | | loadStyleSheet(Dark); |
| | | |
| | | setWindowTitle(tr("Example MainWindow")); |
| | | resize(800, 600); |
| | |
| | | agent->setup(this); |
| | | |
| | | // 2. Construct your title bar |
| | | auto menuBar = []() { |
| | | auto menuBar = [this, agent]() { |
| | | auto menuBar = new QMenuBar(); |
| | | |
| | | // Virtual menu |
| | | auto file = new QMenu(tr("File(&F)"), menuBar); |
| | | file->addAction(new QAction(tr("New(&N)"), menuBar)); |
| | | file->addAction(new QAction(tr("Open(&O)"), menuBar)); |
| | | file->addSeparator(); |
| | | |
| | | auto edit = new QMenu(tr("Edit(&E)"), menuBar); |
| | | edit->addAction(new QAction(tr("Undo(&U)"), menuBar)); |
| | | edit->addAction(new QAction(tr("Redo(&R)"), menuBar)); |
| | | |
| | | // Theme action |
| | | auto darkAction = new QAction(tr("Dark Theme"), menuBar); |
| | | darkAction->setCheckable(true); |
| | | connect(darkAction, &QAction::triggered, this, [this](bool checked) { |
| | | loadStyleSheet(checked ? Dark : Light); // |
| | | }); |
| | | connect(this, &MainWindow::themeChanged, darkAction, [this, darkAction]() { |
| | | darkAction->setChecked(currentTheme == Dark); // |
| | | }); |
| | | |
| | | // Agent action |
| | | auto framelessOnAction = new QAction(tr("Enable Frameless"), menuBar); |
| | | framelessOnAction->setCheckable(true); |
| | | framelessOnAction->setChecked(true); |
| | | connect(framelessOnAction, &QAction::triggered, agent, &QWK::WindowAgentBase::setEnabled); |
| | | connect(agent, &QWK::WindowAgentBase::enabledChanged, framelessOnAction, |
| | | &QAction::setChecked); |
| | | |
| | | // Real menu |
| | | auto settings = new QMenu(tr("Settings(&S)"), menuBar); |
| | | settings->addAction(darkAction); |
| | | settings->addSeparator(); |
| | | settings->addAction(framelessOnAction); |
| | | |
| | | menuBar->addMenu(file); |
| | | menuBar->addMenu(edit); |
| | | menuBar->addMenu(settings); |
| | | return menuBar; |
| | | }(); |
| | | menuBar->setObjectName(QStringLiteral("win-menu-bar")); |
| | |
| | | connect(windowBar, &QWK::WindowBar::closeRequested, this, &QWidget::close); |
| | | #endif |
| | | } |
| | | |
| | | void MainWindow::loadStyleSheet(Theme theme) { |
| | | if (!styleSheet().isEmpty() && theme == currentTheme) |
| | | return; |
| | | currentTheme = theme; |
| | | if (QFile qss(theme == Dark ? QStringLiteral(":/dark-style.qss") |
| | | : QStringLiteral(":/light-style.qss")); |
| | | qss.open(QIODevice::ReadOnly | QIODevice::Text)) { |
| | | setStyleSheet(QString::fromUtf8(qss.readAll())); |
| | | Q_EMIT themeChanged(); |
| | | } |
| | | } |
| | |
| | | explicit MainWindow(QWidget *parent = nullptr); |
| | | ~MainWindow() override; |
| | | |
| | | enum Theme { |
| | | Dark, |
| | | Light, |
| | | }; |
| | | Q_ENUM(Theme) |
| | | |
| | | Q_SIGNALS: |
| | | void themeChanged(); |
| | | |
| | | protected: |
| | | bool event(QEvent *event) override; |
| | | |
| | | private: |
| | | void installWindowAgent(); |
| | | void loadStyleSheet(Theme theme); |
| | | |
| | | Theme currentTheme{}; |
| | | }; |
| | | |
| | | #endif // MAINWINDOW_H |
| | |
| | | <RCC> |
| | | <qresource prefix="/"> |
| | | <file>dark-style.qss</file> |
| | | <file>light-style.qss</file> |
| | | </qresource> |
| | | </RCC> |
| | |
| | | } |
| | | |
| | | void CocoaWindowContext::winIdChanged(QWindow *oldWindow, bool isDestroyed) { |
| | | releaseWindowProxy(windowId); |
| | | // If the original window id is valid, remove all resources related |
| | | if (windowId) { |
| | | releaseWindowProxy(windowId); |
| | | windowId = 0; |
| | | cocoaWindowEventFilter.reset(); |
| | | } |
| | | |
| | | if (!m_windowHandle) { |
| | | return; |
| | | } |
| | | |
| | | // Allocate new resources |
| | | windowId = m_windowHandle->winId(); |
| | | ensureWindowProxy(windowId)->setSystemTitleBarVisible(false); |
| | | cocoaWindowEventFilter = std::make_unique<CocoaWindowEventFilter>(this, this); |
| | |
| | | } |
| | | |
| | | void QtWindowContext::winIdChanged(QWindow *oldWindow, bool isDestroyed) { |
| | | Q_UNUSED(oldWindow) |
| | | Q_UNUSED(isDestroyed) |
| | | |
| | | // If the original window id is valid, remove all resources related |
| | | if (oldWindow) { |
| | | qtWindowEventFilter.reset(); |
| | | } |
| | | |
| | | if (!m_windowHandle) { |
| | | m_delegate->setWindowFlags(m_host, m_delegate->getWindowFlags(m_host) & |
| | | ~Qt::FramelessWindowHint); |
| | | return; |
| | | } |
| | | |
| | | // Allocate new resources |
| | | m_delegate->setWindowFlags(m_host, |
| | | m_delegate->getWindowFlags(m_host) | Qt::FramelessWindowHint); |
| | | qtWindowEventFilter = std::make_unique<QtWindowEventFilter>(this); |
| | |
| | | private: |
| | | DynamicApis() { |
| | | #define DYNAMIC_API_RESOLVE(DLL, NAME) \ |
| | | p##NAME = reinterpret_cast<decltype(p##NAME)>(DLL.resolve(#NAME)) |
| | | p##NAME = reinterpret_cast<decltype(p##NAME)>(DLL.resolve(#NAME)) |
| | | |
| | | QSystemLibrary user32(QStringLiteral("user32")); |
| | | DYNAMIC_API_RESOLVE(user32, GetDpiForWindow); |
| | |
| | | } |
| | | |
| | | void Win32WindowContext::winIdChanged(QWindow *oldWindow, bool isDestroyed) { |
| | | if (isDestroyed) { |
| | | removeManagedWindow(reinterpret_cast<HWND>(windowId)); |
| | | } else { |
| | | removeManagedWindow<false>(reinterpret_cast<HWND>(windowId)); |
| | | Q_UNUSED(isDestroyed) |
| | | |
| | | // If the original window id is valid, remove all resources related |
| | | if (windowId) { |
| | | if (isDestroyed) { |
| | | removeManagedWindow(reinterpret_cast<HWND>(windowId)); |
| | | } else { |
| | | removeManagedWindow<false>(reinterpret_cast<HWND>(windowId)); |
| | | } |
| | | windowId = 0; |
| | | } |
| | | |
| | | if (!m_windowHandle) { |
| | | return; |
| | | } |
| | |
| | | |
| | | void WindowAgentBase::setEnabled(bool enabled) { |
| | | Q_D(WindowAgentBase); |
| | | if (enabled == d->context->isEnabled()) { |
| | | return; |
| | | } |
| | | d->context->setEnabled(enabled); |
| | | Q_EMIT enabledChanged(enabled); |
| | | } |
| | | |
| | | void WindowAgentBase::showSystemMenu(const QPoint &pos) { |
| | |
| | | void centralize(); |
| | | void raise(); |
| | | |
| | | Q_SIGNALS: |
| | | void enabledChanged(bool enabled); |
| | | |
| | | protected: |
| | | explicit WindowAgentBase(WindowAgentBasePrivate &d, QObject *parent = nullptr); |
| | | |