diff options
Diffstat (limited to 'src/plugins/decorations')
-rw-r--r-- | src/plugins/decorations/CMakeLists.txt | 9 | ||||
-rw-r--r-- | src/plugins/decorations/adwaita/CMakeLists.txt | 25 | ||||
-rw-r--r-- | src/plugins/decorations/adwaita/adwaita.json | 3 | ||||
-rw-r--r-- | src/plugins/decorations/adwaita/main.cpp | 36 | ||||
-rw-r--r-- | src/plugins/decorations/adwaita/qwaylandadwaitadecoration.cpp | 731 | ||||
-rw-r--r-- | src/plugins/decorations/adwaita/qwaylandadwaitadecoration_p.h | 155 | ||||
-rw-r--r-- | src/plugins/decorations/bradient/CMakeLists.txt | 23 | ||||
-rw-r--r-- | src/plugins/decorations/bradient/bradient.pro | 12 | ||||
-rw-r--r-- | src/plugins/decorations/bradient/main.cpp | 249 | ||||
-rw-r--r-- | src/plugins/decorations/decorations.pro | 3 |
10 files changed, 1111 insertions, 135 deletions
diff --git a/src/plugins/decorations/CMakeLists.txt b/src/plugins/decorations/CMakeLists.txt new file mode 100644 index 000000000..abe3c375b --- /dev/null +++ b/src/plugins/decorations/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from decorations.pro. +if (QT_FEATURE_wayland_decoration_adwaita) + add_subdirectory(adwaita) +endif() + +add_subdirectory(bradient) diff --git a/src/plugins/decorations/adwaita/CMakeLists.txt b/src/plugins/decorations/adwaita/CMakeLists.txt new file mode 100644 index 000000000..b318c2b8b --- /dev/null +++ b/src/plugins/decorations/adwaita/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## QWaylandAdwaitaDecorationPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(QWaylandAdwaitaDecorationPlugin + OUTPUT_NAME adwaita + PLUGIN_TYPE wayland-decoration-client + SOURCES + main.cpp + qwaylandadwaitadecoration.cpp + LIBRARIES + Qt::Core + Qt::DBus + Qt::Gui + Qt::Svg + Qt::WaylandClientPrivate + Wayland::Client +) + +#### Keys ignored in scope 1:.:.:bradient.pro:<TRUE>: +# OTHER_FILES = "bradient.json" + diff --git a/src/plugins/decorations/adwaita/adwaita.json b/src/plugins/decorations/adwaita/adwaita.json new file mode 100644 index 000000000..69ec79e9b --- /dev/null +++ b/src/plugins/decorations/adwaita/adwaita.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "adwaita", "gnome" ] +} diff --git a/src/plugins/decorations/adwaita/main.cpp b/src/plugins/decorations/adwaita/main.cpp new file mode 100644 index 000000000..e5b1be830 --- /dev/null +++ b/src/plugins/decorations/adwaita/main.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2023 Jan Grulich <jgrulich@redhat.com> +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QtWaylandClient/private/qwaylanddecorationplugin_p.h> + +#include "qwaylandadwaitadecoration_p.h" + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace QtWaylandClient { + +class QWaylandAdwaitaDecorationPlugin : public QWaylandDecorationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QWaylandDecorationFactoryInterface_iid FILE "adwaita.json") +public: + QWaylandAbstractDecoration *create(const QString &key, const QStringList ¶ms) override; +}; + +QWaylandAbstractDecoration *QWaylandAdwaitaDecorationPlugin::create(const QString &key, const QStringList ¶ms) +{ + Q_UNUSED(params); + if (!key.compare("adwaita"_L1, Qt::CaseInsensitive) || + !key.compare("gnome"_L1, Qt::CaseInsensitive)) + return new QWaylandAdwaitaDecoration(); + return nullptr; +} + +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/decorations/adwaita/qwaylandadwaitadecoration.cpp b/src/plugins/decorations/adwaita/qwaylandadwaitadecoration.cpp new file mode 100644 index 000000000..2d3575bce --- /dev/null +++ b/src/plugins/decorations/adwaita/qwaylandadwaitadecoration.cpp @@ -0,0 +1,731 @@ +// Copyright (C) 2023 Jan Grulich <jgrulich@redhat.com> +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandadwaitadecoration_p.h" + +// QtCore +#include <QtCore/QLoggingCategory> +#include <QScopeGuard> + +// QtDBus +#include <QtDBus/QDBusArgument> +#include <QtDBus/QDBusConnection> +#include <QtDBus/QDBusMessage> +#include <QtDBus/QDBusPendingCall> +#include <QtDBus/QDBusPendingCallWatcher> +#include <QtDBus/QDBusPendingReply> +#include <QtDBus/QDBusVariant> +#include <QtDBus/QtDBus> + +// QtGui +#include <QtGui/QColor> +#include <QtGui/QPainter> +#include <QtGui/QPainterPath> + +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/qpa/qplatformtheme.h> + +// QtSvg +#include <QtSvg/QSvgRenderer> + +// QtWayland +#include <QtWaylandClient/private/qwaylandshmbackingstore_p.h> +#include <QtWaylandClient/private/qwaylandwindow_p.h> + + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace QtWaylandClient { + +static constexpr int ceButtonSpacing = 12; +static constexpr int ceButtonWidth = 24; +static constexpr int ceCornerRadius = 12; +static constexpr int ceShadowsWidth = 10; +static constexpr int ceTitlebarHeight = 38; +static constexpr int ceWindowBorderWidth = 1; +static constexpr qreal ceTitlebarSeperatorWidth = 0.5; + +static QMap<QWaylandAdwaitaDecoration::ButtonIcon, QString> buttonMap = { + { QWaylandAdwaitaDecoration::CloseIcon, "window-close-symbolic"_L1 }, + { QWaylandAdwaitaDecoration::MinimizeIcon, "window-minimize-symbolic"_L1 }, + { QWaylandAdwaitaDecoration::MaximizeIcon, "window-maximize-symbolic"_L1 }, + { QWaylandAdwaitaDecoration::RestoreIcon, "window-restore-symbolic"_L1 } +}; + +const QDBusArgument &operator>>(const QDBusArgument &argument, QMap<QString, QVariantMap> &map) +{ + argument.beginMap(); + map.clear(); + + while (!argument.atEnd()) { + QString key; + QVariantMap value; + argument.beginMapEntry(); + argument >> key >> value; + argument.endMapEntry(); + map.insert(key, value); + } + + argument.endMap(); + return argument; +} + +Q_LOGGING_CATEGORY(lcQWaylandAdwaitaDecorationLog, "qt.qpa.qwaylandadwaitadecoration", QtWarningMsg) + +QWaylandAdwaitaDecoration::QWaylandAdwaitaDecoration() + : QWaylandAbstractDecoration() +{ + m_lastButtonClick = QDateTime::currentDateTime(); + + QTextOption option(Qt::AlignHCenter | Qt::AlignVCenter); + option.setWrapMode(QTextOption::NoWrap); + m_windowTitle.setTextOption(option); + m_windowTitle.setTextFormat(Qt::PlainText); + + const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme(); + if (const QFont *font = theme->font(QPlatformTheme::TitleBarFont)) + m_font = std::make_unique<QFont>(*font); + if (!m_font) // Fallback to GNOME's default font + m_font = std::make_unique<QFont>("Cantarell"_L1, 10); + + QTimer::singleShot(0, this, &QWaylandAdwaitaDecoration::loadConfiguration); +} + +QMargins QWaylandAdwaitaDecoration::margins(QWaylandAbstractDecoration::MarginsType marginsType) const +{ + const bool onlyShadows = marginsType == QWaylandAbstractDecoration::ShadowsOnly; + const bool shadowsExcluded = marginsType == ShadowsExcluded; + + if (waylandWindow()->windowStates() & Qt::WindowMaximized) { + // Maximized windows don't have anything around, no shadows, border, + // etc. Only report titlebar height in case we are not asking for shadow + // margins. + return QMargins(0, onlyShadows ? 0 : ceTitlebarHeight, 0, 0); + } + + const QWaylandWindow::ToplevelWindowTilingStates tilingStates = waylandWindow()->toplevelWindowTilingStates(); + + // Since all sides (left, right, bottom) are going to be same + const int marginsBase = shadowsExcluded ? ceWindowBorderWidth : ceShadowsWidth + ceWindowBorderWidth; + const int sideMargins = onlyShadows ? ceShadowsWidth : marginsBase; + const int topMargins = onlyShadows ? ceShadowsWidth : ceTitlebarHeight + marginsBase; + + return QMargins(tilingStates & QWaylandWindow::WindowTiledLeft ? 0 : sideMargins, + tilingStates & QWaylandWindow::WindowTiledTop ? onlyShadows ? 0 : ceTitlebarHeight : topMargins, + tilingStates & QWaylandWindow::WindowTiledRight ? 0 : sideMargins, + tilingStates & QWaylandWindow::WindowTiledBottom ? 0 : sideMargins); +} + +void QWaylandAdwaitaDecoration::paint(QPaintDevice *device) +{ + const QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(ShadowsOnly); + + QPainter p(device); + p.setRenderHint(QPainter::Antialiasing); + + /* + * Titlebar and window border + */ + const int titleBarWidth = surfaceRect.width() - margins().left() - margins().right(); + QPainterPath path; + + // Maximized or tiled won't have rounded corners + if (waylandWindow()->windowStates() & Qt::WindowMaximized + || waylandWindow()->toplevelWindowTilingStates() != QWaylandWindow::WindowNoState) + path.addRect(margins().left(), margins().bottom(), titleBarWidth, margins().top()); + else + path.addRoundedRect(margins().left(), margins().bottom(), titleBarWidth, + margins().top() + ceCornerRadius, ceCornerRadius, ceCornerRadius); + + p.save(); + p.setPen(color(Border)); + p.fillPath(path.simplified(), color(Background)); + p.drawPath(path); + p.drawRect(margins().left(), margins().top(), titleBarWidth, surfaceRect.height() - margins().top() - margins().bottom()); + p.restore(); + + + /* + * Titlebar separator + */ + p.save(); + p.setPen(color(Border)); + p.drawLine(QLineF(margins().left(), margins().top() - ceTitlebarSeperatorWidth, + surfaceRect.width() - margins().right(), + margins().top() - ceTitlebarSeperatorWidth)); + p.restore(); + + + /* + * Window title + */ + const QRect top = QRect(margins().left(), margins().bottom(), surfaceRect.width(), + margins().top() - margins().bottom()); + const QString windowTitleText = waylandWindow()->windowTitle(); + if (!windowTitleText.isEmpty()) { + if (m_windowTitle.text() != windowTitleText) { + m_windowTitle.setText(windowTitleText); + m_windowTitle.prepare(); + } + + QRect titleBar = top; + if (m_placement == Right) { + titleBar.setLeft(margins().left()); + titleBar.setRight(static_cast<int>(buttonRect(Minimize).left()) - 8); + } else { + titleBar.setLeft(static_cast<int>(buttonRect(Minimize).right()) + 8); + titleBar.setRight(surfaceRect.width() - margins().right()); + } + + p.save(); + p.setClipRect(titleBar); + p.setPen(color(Foreground)); + QSize size = m_windowTitle.size().toSize(); + int dx = (top.width() - size.width()) / 2; + int dy = (top.height() - size.height()) / 2; + p.setFont(*m_font); + QPoint windowTitlePoint(top.topLeft().x() + dx, top.topLeft().y() + dy); + p.drawStaticText(windowTitlePoint, m_windowTitle); + p.restore(); + } + + + /* + * Buttons + */ + if (m_buttons.contains(Close)) + drawButton(Close, &p); + + if (m_buttons.contains(Maximize)) + drawButton(Maximize, &p); + + if (m_buttons.contains(Minimize)) + drawButton(Minimize, &p); +} + +bool QWaylandAdwaitaDecoration::handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local, + const QPointF &global, Qt::MouseButtons b, + Qt::KeyboardModifiers mods) +{ + Q_UNUSED(global) + + if (local.y() > margins().top()) + updateButtonHoverState(Button::None); + + // Figure out what area mouse is in + QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(ShadowsOnly); + if (local.y() <= surfaceRect.top() + margins().top()) + processMouseTop(inputDevice, local, b, mods); + else if (local.y() > surfaceRect.bottom() - margins().bottom()) + processMouseBottom(inputDevice, local, b, mods); + else if (local.x() <= surfaceRect.left() + margins().left()) + processMouseLeft(inputDevice, local, b, mods); + else if (local.x() > surfaceRect.right() - margins().right()) + processMouseRight(inputDevice, local, b, mods); + else { +#if QT_CONFIG(cursor) + waylandWindow()->restoreMouseCursor(inputDevice); +#endif + } + + // Reset clicking state in case a button press is released outside + // the button area + if (isLeftReleased(b)) { + m_clicking = None; + requestRepaint(); + } + + setMouseButtons(b); + return false; +} + +bool QWaylandAdwaitaDecoration::handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, + const QPointF &global, QEventPoint::State state, + Qt::KeyboardModifiers mods) +{ + Q_UNUSED(inputDevice) + Q_UNUSED(global) + Q_UNUSED(mods) + + bool handled = state == QEventPoint::Pressed; + + if (handled) { + if (buttonRect(Close).contains(local)) + QWindowSystemInterface::handleCloseEvent(window()); + else if (m_buttons.contains(Maximize) && buttonRect(Maximize).contains(local)) + window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized); + else if (m_buttons.contains(Minimize) && buttonRect(Minimize).contains(local)) + window()->setWindowState(Qt::WindowMinimized); + else if (local.y() <= margins().top()) + waylandWindow()->shellSurface()->move(inputDevice); + else + handled = false; + } + + return handled; +} + +QString getIconSvg(const QString &iconName) +{ + const QStringList themeNames = { QIcon::themeName(), QIcon::fallbackThemeName(), "Adwaita"_L1 }; + + qCDebug(lcQWaylandAdwaitaDecorationLog) << "Searched icon themes: " << themeNames; + + for (const QString &themeName : themeNames) { + if (themeName.isEmpty()) + continue; + + for (const QString &path : QIcon::themeSearchPaths()) { + if (path.startsWith(QLatin1Char(':'))) + continue; + + const QString fullPath = QString("%1/%2").arg(path).arg(themeName); + QDirIterator dirIt(fullPath, {"*.svg"}, QDir::Files, QDirIterator::Subdirectories); + while (dirIt.hasNext()) { + const QString fileName = dirIt.next(); + const QFileInfo fileInfo(fileName); + + if (fileInfo.fileName() == iconName) { + qCDebug(lcQWaylandAdwaitaDecorationLog) << "Using " << iconName << " from " << themeName << " theme"; + QFile readFile(fileInfo.filePath()); + readFile.open(QFile::ReadOnly); + return readFile.readAll(); + } + } + } + } + + qCWarning(lcQWaylandAdwaitaDecorationLog) << "Failed to find an svg icon for " << iconName; + + return QString(); +} + +void QWaylandAdwaitaDecoration::loadConfiguration() +{ + qRegisterMetaType<QDBusVariant>(); + qDBusRegisterMetaType<QMap<QString, QVariantMap>>(); + + QDBusConnection connection = QDBusConnection::sessionBus(); + + QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1, + "/org/freedesktop/portal/desktop"_L1, + "org.freedesktop.portal.Settings"_L1, + "ReadAll"_L1); + message << QStringList{ { "org.gnome.desktop.wm.preferences"_L1 }, + { "org.freedesktop.appearance"_L1 } }; + + QDBusPendingCall pendingCall = connection.asyncCall(message); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) { + QDBusPendingReply<QMap<QString, QVariantMap>> reply = *watcher; + if (reply.isValid()) { + QMap<QString, QVariantMap> settings = reply.value(); + if (!settings.isEmpty()) { + const uint colorScheme = settings.value("org.freedesktop.appearance"_L1).value("color-scheme"_L1).toUInt(); + updateColors(colorScheme == 1); // 1 == Prefer Dark + const QString buttonLayout = settings.value("org.gnome.desktop.wm.preferences"_L1).value("button-layout"_L1).toString(); + if (!buttonLayout.isEmpty()) + updateTitlebarLayout(buttonLayout); + // Workaround for QGtkStyle not having correct titlebar font + const QString titlebarFont = + settings.value("org.gnome.desktop.wm.preferences"_L1).value("titlebar-font"_L1).toString(); + if (titlebarFont.contains("bold"_L1, Qt::CaseInsensitive)) { + m_font->setBold(true); + } + } + } + watcher->deleteLater(); + }); + + QDBusConnection::sessionBus().connect(QString(), "/org/freedesktop/portal/desktop"_L1, + "org.freedesktop.portal.Settings"_L1, "SettingChanged"_L1, this, + SLOT(settingChanged(QString, QString, QDBusVariant))); + + // Load SVG icons + for (auto mapIt = buttonMap.constBegin(); mapIt != buttonMap.constEnd(); mapIt++) { + const QString fullName = mapIt.value() + QStringLiteral(".svg"); + m_icons[mapIt.key()] = getIconSvg(fullName); + } + + updateColors(false); +} + +void QWaylandAdwaitaDecoration::updateColors(bool isDark) +{ + qCDebug(lcQWaylandAdwaitaDecorationLog) << "Color scheme changed to: " << (isDark ? "dark" : "light"); + + m_colors = { { Background, isDark ? QColor(0x303030) : QColor(0xffffff) }, + { BackgroundInactive, isDark ? QColor(0x242424) : QColor(0xfafafa) }, + { Foreground, isDark ? QColor(0xffffff) : QColor(0x2e2e2e) }, + { ForegroundInactive, isDark ? QColor(0x919191) : QColor(0x949494) }, + { Border, isDark ? QColor(0x3b3b3b) : QColor(0xdbdbdb) }, + { BorderInactive, isDark ? QColor(0x303030) : QColor(0xdbdbdb) }, + { ButtonBackground, isDark ? QColor(0x444444) : QColor(0xebebeb) }, + { ButtonBackgroundInactive, isDark ? QColor(0x2e2e2e) : QColor(0xf0f0f0) }, + { HoveredButtonBackground, isDark ? QColor(0x4f4f4f) : QColor(0xe0e0e0) }, + { PressedButtonBackground, isDark ? QColor(0x6e6e6e) : QColor(0xc2c2c2) } }; + requestRepaint(); +} + +void QWaylandAdwaitaDecoration::updateTitlebarLayout(const QString &layout) +{ + const QStringList layouts = layout.split(QLatin1Char(':')); + if (layouts.count() != 2) + return; + + // Remove previous configuration + m_buttons.clear(); + + const QString &leftLayout = layouts.at(0); + const QString &rightLayout = layouts.at(1); + m_placement = leftLayout.contains("close"_L1) ? Left : Right; + + int pos = 1; + const QString &buttonLayout = m_placement == Right ? rightLayout : leftLayout; + + QStringList buttonList = buttonLayout.split(QLatin1Char(',')); + if (m_placement == Right) + std::reverse(buttonList.begin(), buttonList.end()); + + for (const QString &button : buttonList) { + if (button == "close"_L1) + m_buttons.insert(Close, pos); + else if (button == "maximize"_L1) + m_buttons.insert(Maximize, pos); + else if (button == "minimize"_L1) + m_buttons.insert(Minimize, pos); + + pos++; + } + + qCDebug(lcQWaylandAdwaitaDecorationLog) << "Button layout changed to: " << layout; + + requestRepaint(); +} + +void QWaylandAdwaitaDecoration::settingChanged(const QString &group, const QString &key, + const QDBusVariant &value) +{ + if (group == "org.gnome.desktop.wm.preferences"_L1 && key == "button-layout"_L1) { + const QString layout = value.variant().toString(); + updateTitlebarLayout(layout); + } else if (group == "org.freedesktop.appearance"_L1 && key == "color-scheme"_L1) { + const uint colorScheme = value.variant().toUInt(); + updateColors(colorScheme == 1); // 1 == Prefer Dark + } +} + +QRectF QWaylandAdwaitaDecoration::buttonRect(Button button) const +{ + int xPos; + int yPos; + const int btnPos = m_buttons.value(button); + + const QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(QWaylandAbstractDecoration::ShadowsOnly); + if (m_placement == Right) { + xPos = surfaceRect.width(); + xPos -= ceButtonWidth * btnPos; + xPos -= ceButtonSpacing * btnPos; + xPos -= margins(ShadowsOnly).right(); + } else { + xPos = 0; + xPos += ceButtonWidth * btnPos; + xPos += ceButtonSpacing * btnPos; + xPos += margins(ShadowsOnly).left(); + // We are painting from the left to the right so the real + // position doesn't need to by moved by the size of the button. + xPos -= ceButtonWidth; + } + + yPos = margins().top(); + yPos += margins().bottom(); + yPos -= ceButtonWidth; + yPos /= 2; + + return QRectF(xPos, yPos, ceButtonWidth, ceButtonWidth); +} + +static void renderFlatRoundedButtonFrame(QPainter *painter, const QRect &rect, const QColor &color) +{ + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setPen(Qt::NoPen); + painter->setBrush(color); + painter->drawEllipse(rect); + painter->restore(); +} + +static void renderButtonIcon(const QString &svgIcon, QPainter *painter, const QRect &rect, const QColor &color) +{ + painter->save(); + painter->setRenderHints(QPainter::Antialiasing, true); + + QString icon = svgIcon; + QRegularExpression regexp("fill=[\"']#[0-9A-F]{6}[\"']", QRegularExpression::CaseInsensitiveOption); + QRegularExpression regexpAlt("fill:#[0-9A-F]{6}", QRegularExpression::CaseInsensitiveOption); + QRegularExpression regexpCurrentColor("fill=[\"']currentColor[\"']"); + icon.replace(regexp, QString("fill=\"%1\"").arg(color.name())); + icon.replace(regexpAlt, QString("fill:%1").arg(color.name())); + icon.replace(regexpCurrentColor, QString("fill=\"%1\"").arg(color.name())); + QSvgRenderer svgRenderer(icon.toLocal8Bit()); + svgRenderer.render(painter, rect); + + painter->restore(); +} + +static void renderButtonIcon(QWaylandAdwaitaDecoration::ButtonIcon buttonIcon, QPainter *painter, const QRect &rect) +{ + QString iconName = buttonMap[buttonIcon]; + + painter->save(); + painter->setRenderHints(QPainter::Antialiasing, true); + painter->drawPixmap(rect, QIcon::fromTheme(iconName).pixmap(ceButtonWidth, ceButtonWidth)); + painter->restore(); +} + +static QWaylandAdwaitaDecoration::ButtonIcon iconFromButtonAndState(QWaylandAdwaitaDecoration::Button button, bool maximized) +{ + if (button == QWaylandAdwaitaDecoration::Close) + return QWaylandAdwaitaDecoration::CloseIcon; + else if (button == QWaylandAdwaitaDecoration::Minimize) + return QWaylandAdwaitaDecoration::MinimizeIcon; + else if (button == QWaylandAdwaitaDecoration::Maximize && maximized) + return QWaylandAdwaitaDecoration::RestoreIcon; + else + return QWaylandAdwaitaDecoration::MaximizeIcon; +} + +void QWaylandAdwaitaDecoration::drawButton(Button button, QPainter *painter) +{ + const Qt::WindowStates windowStates = waylandWindow()->windowStates(); + const bool maximized = windowStates & Qt::WindowMaximized; + + const QRect btnRect = buttonRect(button).toRect(); + renderFlatRoundedButtonFrame(painter, btnRect, color(ButtonBackground, button)); + + QRect adjustedBtnRect = btnRect; + adjustedBtnRect.setSize(QSize(16, 16)); + adjustedBtnRect.translate(4, 4); + const QString svgIcon = m_icons[iconFromButtonAndState(button, maximized)]; + if (!svgIcon.isEmpty()) + renderButtonIcon(svgIcon, painter, adjustedBtnRect, color(Foreground)); + else // Fallback to use QIcon + renderButtonIcon(iconFromButtonAndState(button, maximized), painter, adjustedBtnRect); +} + +QColor QWaylandAdwaitaDecoration::color(ColorType type, Button button) +{ + const bool active = waylandWindow()->windowStates() & Qt::WindowActive; + + switch (type) { + case Background: + case BackgroundInactive: + return active ? m_colors[Background] : m_colors[BackgroundInactive]; + case Foreground: + case ForegroundInactive: + return active ? m_colors[Foreground] : m_colors[ForegroundInactive]; + case Border: + case BorderInactive: + return active ? m_colors[Border] : m_colors[BorderInactive]; + case ButtonBackground: + case ButtonBackgroundInactive: + case HoveredButtonBackground: { + if (m_clicking == button) { + return m_colors[PressedButtonBackground]; + } else if (m_hoveredButtons.testFlag(button)) { + return m_colors[HoveredButtonBackground]; + } + return active ? m_colors[ButtonBackground] : m_colors[ButtonBackgroundInactive]; + } + default: + return m_colors[Background]; + } +} + +bool QWaylandAdwaitaDecoration::clickButton(Qt::MouseButtons b, Button btn) +{ + auto repaint = qScopeGuard([this] { requestRepaint(); }); + + if (isLeftClicked(b)) { + m_clicking = btn; + return false; + } else if (isLeftReleased(b)) { + if (m_clicking == btn) { + m_clicking = None; + return true; + } else { + m_clicking = None; + } + } + return false; +} + +bool QWaylandAdwaitaDecoration::doubleClickButton(Qt::MouseButtons b, const QPointF &local, + const QDateTime ¤tTime) +{ + if (isLeftClicked(b)) { + const qint64 clickInterval = m_lastButtonClick.msecsTo(currentTime); + m_lastButtonClick = currentTime; + const int doubleClickDistance = 5; + const QPointF posDiff = m_lastButtonClickPosition - local; + if ((clickInterval <= 500) + && ((posDiff.x() <= doubleClickDistance && posDiff.x() >= -doubleClickDistance) + && ((posDiff.y() <= doubleClickDistance && posDiff.y() >= -doubleClickDistance)))) { + return true; + } + + m_lastButtonClickPosition = local; + } + + return false; +} + +void QWaylandAdwaitaDecoration::updateButtonHoverState(Button hoveredButton) +{ + bool currentCloseButtonState = m_hoveredButtons.testFlag(Close); + bool currentMaximizeButtonState = m_hoveredButtons.testFlag(Maximize); + bool currentMinimizeButtonState = m_hoveredButtons.testFlag(Minimize); + + m_hoveredButtons.setFlag(Close, hoveredButton == Button::Close); + m_hoveredButtons.setFlag(Maximize, hoveredButton == Button::Maximize); + m_hoveredButtons.setFlag(Minimize, hoveredButton == Button::Minimize); + + if (m_hoveredButtons.testFlag(Close) != currentCloseButtonState + || m_hoveredButtons.testFlag(Maximize) != currentMaximizeButtonState + || m_hoveredButtons.testFlag(Minimize) != currentMinimizeButtonState) { + requestRepaint(); + } +} + +void QWaylandAdwaitaDecoration::processMouseTop(QWaylandInputDevice *inputDevice, const QPointF &local, + Qt::MouseButtons b, Qt::KeyboardModifiers mods) +{ + Q_UNUSED(mods) + + QDateTime currentDateTime = QDateTime::currentDateTime(); + QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(ShadowsOnly); + + if (!buttonRect(Close).contains(local) && !buttonRect(Maximize).contains(local) + && !buttonRect(Minimize).contains(local)) + updateButtonHoverState(Button::None); + + if (local.y() <= surfaceRect.top() + margins().bottom()) { + if (local.x() <= margins().left()) { + // top left bit +#if QT_CONFIG(cursor) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeFDiagCursor); +#endif + startResize(inputDevice, Qt::TopEdge | Qt::LeftEdge, b); + } else if (local.x() > surfaceRect.right() - margins().left()) { + // top right bit +#if QT_CONFIG(cursor) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeBDiagCursor); +#endif + startResize(inputDevice, Qt::TopEdge | Qt::RightEdge, b); + } else { + // top resize bit +#if QT_CONFIG(cursor) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeVerCursor); +#endif + startResize(inputDevice, Qt::TopEdge, b); + } + } else if (local.x() <= surfaceRect.left() + margins().left()) { + processMouseLeft(inputDevice, local, b, mods); + } else if (local.x() > surfaceRect.right() - margins().right()) { + processMouseRight(inputDevice, local, b, mods); + } else if (buttonRect(Close).contains(local)) { + if (clickButton(b, Close)) { + QWindowSystemInterface::handleCloseEvent(window()); + m_hoveredButtons.setFlag(Close, false); + } + updateButtonHoverState(Close); + } else if (m_buttons.contains(Maximize) && buttonRect(Maximize).contains(local)) { + updateButtonHoverState(Maximize); + if (clickButton(b, Maximize)) { + window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized); + m_hoveredButtons.setFlag(Maximize, false); + } + } else if (m_buttons.contains(Minimize) && buttonRect(Minimize).contains(local)) { + updateButtonHoverState(Minimize); + if (clickButton(b, Minimize)) { + window()->setWindowState(Qt::WindowMinimized); + m_hoveredButtons.setFlag(Minimize, false); + } + } else if (doubleClickButton(b, local, currentDateTime)) { + window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized); + } else { + // Show window menu + if (b == Qt::MouseButton::RightButton) + waylandWindow()->shellSurface()->showWindowMenu(inputDevice); +#if QT_CONFIG(cursor) + waylandWindow()->restoreMouseCursor(inputDevice); +#endif + startMove(inputDevice, b); + } +} + +void QWaylandAdwaitaDecoration::processMouseBottom(QWaylandInputDevice *inputDevice, const QPointF &local, + Qt::MouseButtons b, Qt::KeyboardModifiers mods) +{ + Q_UNUSED(mods) + if (local.x() <= margins().left()) { + // bottom left bit +#if QT_CONFIG(cursor) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeBDiagCursor); +#endif + startResize(inputDevice, Qt::BottomEdge | Qt::LeftEdge, b); + } else if (local.x() > window()->width() + margins().right()) { + // bottom right bit +#if QT_CONFIG(cursor) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeFDiagCursor); +#endif + startResize(inputDevice, Qt::BottomEdge | Qt::RightEdge, b); + } else { + // bottom bit +#if QT_CONFIG(cursor) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeVerCursor); +#endif + startResize(inputDevice, Qt::BottomEdge, b); + } +} + +void QWaylandAdwaitaDecoration::processMouseLeft(QWaylandInputDevice *inputDevice, const QPointF &local, + Qt::MouseButtons b, Qt::KeyboardModifiers mods) +{ + Q_UNUSED(local) + Q_UNUSED(mods) +#if QT_CONFIG(cursor) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeHorCursor); +#endif + startResize(inputDevice, Qt::LeftEdge, b); +} + +void QWaylandAdwaitaDecoration::processMouseRight(QWaylandInputDevice *inputDevice, const QPointF &local, + Qt::MouseButtons b, Qt::KeyboardModifiers mods) +{ + Q_UNUSED(local) + Q_UNUSED(mods) +#if QT_CONFIG(cursor) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeHorCursor); +#endif + startResize(inputDevice, Qt::RightEdge, b); +} + +void QWaylandAdwaitaDecoration::requestRepaint() const +{ + // Set dirty flag + if (waylandWindow()->decoration()) + waylandWindow()->decoration()->update(); + + // Request re-paint + waylandWindow()->window()->requestUpdate(); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#include "moc_qwaylandadwaitadecoration_p.cpp" diff --git a/src/plugins/decorations/adwaita/qwaylandadwaitadecoration_p.h b/src/plugins/decorations/adwaita/qwaylandadwaitadecoration_p.h new file mode 100644 index 000000000..34874e088 --- /dev/null +++ b/src/plugins/decorations/adwaita/qwaylandadwaitadecoration_p.h @@ -0,0 +1,155 @@ +// Copyright (C) 2023 Jan Grulich <jgrulich@redhat.com> +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDADWAITADECORATION_P_H +#define QWAYLANDADWAITADECORATION_P_H + +#include <QtWaylandClient/private/qwaylandabstractdecoration_p.h> + +#include <QtCore/QDateTime> + +QT_BEGIN_NAMESPACE + +class QDBusVariant; +class QPainter; + +namespace QtWaylandClient { + +// +// INFO +// ------------- +// +// This is a Qt decoration plugin implementing Adwaita-like (GNOME) client-side +// window decorations. It uses xdg-desktop-portal to get the user configuration. +// This plugin was originally part of QGnomePlatform and later made a separate +// project named QAdwaitaDecorations. +// +// INFO: How SVG icons are used here? +// We try to find an SVG icon for a particular button from the current icon theme. +// This icon is then opened as a file, it's content saved and later loaded to be +// painted with QSvgRenderer, but before it's painted, we try to find following +// patterns: +// 1) fill=[\"']#[0-9A-F]{6}[\"'] +// 2) fill:#[0-9A-F]{6} +// 3) fill=[\"']currentColor[\"'] +// The color in this case doesn't match the theme and is replaced by Foreground color. +// +// FIXME/TODO: +// This plugin currently have all the colors for the decorations hardcoded. +// There might be a way to get these from GTK/libadwaita (not sure), but problem is +// we want Gtk4 version and using Gtk4 together with QGtk3Theme from QtBase that links +// to Gtk3 will not work out. Possibly in future we can make a QGtk4Theme providing us +// what we need to paint the decorations without having to deal with the colors ourself. +// +// TODO: Implement shadows + + +class QWaylandAdwaitaDecoration : public QWaylandAbstractDecoration +{ + Q_OBJECT +public: + enum ColorType { + Background, + BackgroundInactive, + Foreground, + ForegroundInactive, + Border, + BorderInactive, + ButtonBackground, + ButtonBackgroundInactive, + HoveredButtonBackground, + PressedButtonBackground + }; + + enum Placement { + Left = 0, + Right = 1 + }; + + enum Button { + None = 0x0, + Close = 0x1, + Minimize = 0x02, + Maximize = 0x04 + }; + Q_DECLARE_FLAGS(Buttons, Button); + + enum ButtonIcon { + CloseIcon, + MinimizeIcon, + MaximizeIcon, + RestoreIcon + }; + + QWaylandAdwaitaDecoration(); + virtual ~QWaylandAdwaitaDecoration() = default; + +protected: + QMargins margins(MarginsType marginsType = Full) const override; + void paint(QPaintDevice *device) override; + bool handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, + Qt::MouseButtons b, Qt::KeyboardModifiers mods) override; + bool handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, + QEventPoint::State state, Qt::KeyboardModifiers mods) override; + +private Q_SLOTS: + void settingChanged(const QString &group, const QString &key, const QDBusVariant &value); + +private: + // Makes a call to xdg-desktop-portal (Settings) to load initial configuration + void loadConfiguration(); + // Updates color scheme from light to dark and vice-versa + void updateColors(bool isDark); + // Updates titlebar layout with position and button order + void updateTitlebarLayout(const QString &layout); + + // Returns a bounding rect for a given button type + QRectF buttonRect(Button button) const; + // Draw given button type using SVG icon (when found) or fallback to QPixmap icon + void drawButton(Button button, QPainter *painter); + + // Returns color for given type and button + QColor color(ColorType type, Button button = None); + + // Returns whether the left button was clicked i.e. pressed and released + bool clickButton(Qt::MouseButtons b, Button btn); + // Returns whether the left button was double-clicked + bool doubleClickButton(Qt::MouseButtons b, const QPointF &local, const QDateTime ¤tTime); + // Updates button hover state + void updateButtonHoverState(Button hoveredButton); + + void processMouseTop(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b, + Qt::KeyboardModifiers mods); + void processMouseBottom(QWaylandInputDevice *inputDevice, const QPointF &local, + Qt::MouseButtons b, Qt::KeyboardModifiers mods); + void processMouseLeft(QWaylandInputDevice *inputDevice, const QPointF &local, + Qt::MouseButtons b, Qt::KeyboardModifiers mods); + void processMouseRight(QWaylandInputDevice *inputDevice, const QPointF &local, + Qt::MouseButtons b, Qt::KeyboardModifiers mods); + // Request to repaint the decorations. This will be invoked when button hover changes or + // when there is a setting change (e.g. layout change). + void requestRepaint() const; + + // Button states + Button m_clicking = None; + Buttons m_hoveredButtons = None; + QDateTime m_lastButtonClick; + QPointF m_lastButtonClickPosition; + + // Configuration + QMap<Button, uint> m_buttons; + QMap<ColorType, QColor> m_colors; + QMap<ButtonIcon, QString> m_icons; + std::unique_ptr<QFont> m_font; + Placement m_placement = Right; + + QStaticText m_windowTitle; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(QWaylandAdwaitaDecoration::Buttons) + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDADWAITADECORATION_P_H diff --git a/src/plugins/decorations/bradient/CMakeLists.txt b/src/plugins/decorations/bradient/CMakeLists.txt new file mode 100644 index 000000000..065d0f18c --- /dev/null +++ b/src/plugins/decorations/bradient/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from bradient.pro. + +##################################################################### +## QWaylandBradientDecorationPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(QWaylandBradientDecorationPlugin + OUTPUT_NAME bradient + PLUGIN_TYPE wayland-decoration-client + SOURCES + main.cpp + LIBRARIES + Qt::Core + Qt::Gui + Qt::WaylandClientPrivate + Wayland::Client +) + +#### Keys ignored in scope 1:.:.:bradient.pro:<TRUE>: +# OTHER_FILES = "bradient.json" diff --git a/src/plugins/decorations/bradient/bradient.pro b/src/plugins/decorations/bradient/bradient.pro deleted file mode 100644 index e31576783..000000000 --- a/src/plugins/decorations/bradient/bradient.pro +++ /dev/null @@ -1,12 +0,0 @@ -QT += waylandclient-private - -OTHER_FILES += \ - bradient.json - -SOURCES += main.cpp - -QMAKE_USE += wayland-client - -PLUGIN_TYPE = wayland-decoration-client -PLUGIN_CLASS_NAME = QWaylandBradientDecorationPlugin -load(qt_plugin) diff --git a/src/plugins/decorations/bradient/main.cpp b/src/plugins/decorations/bradient/main.cpp index e8e35775e..32f2d8db8 100644 --- a/src/plugins/decorations/bradient/main.cpp +++ b/src/plugins/decorations/bradient/main.cpp @@ -1,47 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Robin Burchell <robin.burchell@viroteck.net> -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 Robin Burchell <robin.burchell@viroteck.net> +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <QtGui/QCursor> #include <QtGui/QPainter> +#include <QtGui/QPainterPath> #include <QtGui/QPalette> #include <QtGui/QLinearGradient> +#include <QtGui/QPainterPath> #include <qpa/qwindowsysteminterface.h> @@ -66,29 +32,31 @@ enum Button Minimize }; -class Q_WAYLAND_CLIENT_EXPORT QWaylandBradientDecoration : public QWaylandAbstractDecoration +class Q_WAYLANDCLIENT_EXPORT QWaylandBradientDecoration : public QWaylandAbstractDecoration { public: QWaylandBradientDecoration(); protected: - QMargins margins() const override; + QMargins margins(MarginsType marginsType = Full) const override; void paint(QPaintDevice *device) override; bool handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global,Qt::MouseButtons b,Qt::KeyboardModifiers mods) override; - bool handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, Qt::TouchPointState state, Qt::KeyboardModifiers mods) override; + bool handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, QEventPoint::State state, Qt::KeyboardModifiers mods) override; private: - void processMouseTop(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods); - void processMouseBottom(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods); - void processMouseLeft(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods); - void processMouseRight(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods); + enum class PointerType { + Mouse, + Touch + }; + + void processPointerTop(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods, PointerType type); + void processPointerBottom(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods, PointerType type); + void processPointerLeft(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods, PointerType type); + void processPointerRight(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods, PointerType type); bool clickButton(Qt::MouseButtons b, Button btn); QRectF closeButtonRect() const; QRectF maximizeButtonRect() const; QRectF minimizeButtonRect() const; - QColor m_foregroundColor; - QColor m_foregroundInactiveColor; - QColor m_backgroundColor; QStaticText m_windowTitle; Button m_clicking = None; }; @@ -97,56 +65,61 @@ private: QWaylandBradientDecoration::QWaylandBradientDecoration() { - QPalette palette; - m_foregroundColor = palette.color(QPalette::Active, QPalette::WindowText); - m_backgroundColor = palette.color(QPalette::Active, QPalette::Window); - m_foregroundInactiveColor = palette.color(QPalette::Disabled, QPalette::WindowText); - QTextOption option(Qt::AlignHCenter | Qt::AlignVCenter); option.setWrapMode(QTextOption::NoWrap); m_windowTitle.setTextOption(option); + m_windowTitle.setTextFormat(Qt::PlainText); } QRectF QWaylandBradientDecoration::closeButtonRect() const { - const int windowRight = waylandWindow()->windowContentGeometry().right() + 1; + const int windowRight = waylandWindow()->surfaceSize().width() - margins(ShadowsOnly).right(); return QRectF(windowRight - BUTTON_WIDTH - BUTTON_SPACING * 0 - BUTTONS_RIGHT_MARGIN, (margins().top() - BUTTON_WIDTH) / 2, BUTTON_WIDTH, BUTTON_WIDTH); } QRectF QWaylandBradientDecoration::maximizeButtonRect() const { - const int windowRight = waylandWindow()->windowContentGeometry().right() + 1; + const int windowRight = waylandWindow()->surfaceSize().width() - margins(ShadowsOnly).right(); return QRectF(windowRight - BUTTON_WIDTH * 2 - BUTTON_SPACING * 1 - BUTTONS_RIGHT_MARGIN, (margins().top() - BUTTON_WIDTH) / 2, BUTTON_WIDTH, BUTTON_WIDTH); } QRectF QWaylandBradientDecoration::minimizeButtonRect() const { - const int windowRight = waylandWindow()->windowContentGeometry().right() + 1; + const int windowRight = waylandWindow()->surfaceSize().width() - margins(ShadowsOnly).right(); return QRectF(windowRight - BUTTON_WIDTH * 3 - BUTTON_SPACING * 2 - BUTTONS_RIGHT_MARGIN, (margins().top() - BUTTON_WIDTH) / 2, BUTTON_WIDTH, BUTTON_WIDTH); } -QMargins QWaylandBradientDecoration::margins() const +QMargins QWaylandBradientDecoration::margins(MarginsType marginsType) const { + if (marginsType == ShadowsOnly) + return QMargins(); + return QMargins(3, 30, 3, 3); } void QWaylandBradientDecoration::paint(QPaintDevice *device) { bool active = window()->handle()->isActive(); - QRect wg = waylandWindow()->windowContentGeometry(); + QRect wg = QRect(QPoint(), waylandWindow()->surfaceSize()).marginsRemoved(margins(ShadowsOnly)); + QRect cg = wg.marginsRemoved(margins(ShadowsExcluded)); QRect clips[] = { - QRect(wg.left(), wg.top(), wg.width(), margins().top()), - QRect(wg.left(), (wg.bottom() + 1) - margins().bottom(), wg.width(), margins().bottom()), - QRect(wg.left(), margins().top(), margins().left(), wg.height() - margins().top() - margins().bottom()), - QRect((wg.right() + 1) - margins().right(), wg.top() + margins().top(), margins().right(), wg.height() - margins().top() - margins().bottom()) + QRect(wg.left(), wg.top(), wg.width(), margins(ShadowsExcluded).top()), + QRect(wg.left(), cg.bottom() + 1, wg.width(), margins(ShadowsExcluded).bottom()), + QRect(wg.left(), cg.top(), margins(ShadowsExcluded).left(), cg.height()), + QRect(cg.right() + 1, cg.top(), margins(ShadowsExcluded).right(), cg.height()) }; QRect top = clips[0]; + QPalette palette; + const QColor foregroundColor = palette.color(QPalette::Active, QPalette::WindowText); + const QColor backgroundColor = palette.color(QPalette::Active, QPalette::Window); + const QColor foregroundInactiveColor = palette.color(QPalette::Disabled, QPalette::WindowText); + QPainter p(device); p.setRenderHint(QPainter::Antialiasing); @@ -156,24 +129,21 @@ void QWaylandBradientDecoration::paint(QPaintDevice *device) for (int i = 0; i < 4; ++i) { p.save(); p.setClipRect(clips[i]); - p.fillPath(roundedRect, m_backgroundColor); + p.fillPath(roundedRect, backgroundColor); p.restore(); } // Window icon QIcon icon = waylandWindow()->windowIcon(); if (!icon.isNull()) { - QPixmap pixmap = icon.pixmap(QSize(128, 128)); - QPixmap scaled = pixmap.scaled(22, 22, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - QRectF iconRect(0, 0, 22, 22); - p.drawPixmap(iconRect.adjusted(margins().left() + BUTTON_SPACING, 4, - margins().left() + BUTTON_SPACING, 4), - scaled, iconRect); + iconRect.adjust(margins().left() + BUTTON_SPACING, 4, + margins().left() + BUTTON_SPACING, 4), + icon.paint(&p, iconRect.toRect()); } // Window title - QString windowTitleText = window()->title(); + QString windowTitleText = waylandWindow()->windowTitle(); if (!windowTitleText.isEmpty()) { if (m_windowTitle.text() != windowTitleText) { m_windowTitle.setText(windowTitleText); @@ -187,7 +157,7 @@ void QWaylandBradientDecoration::paint(QPaintDevice *device) p.save(); p.setClipRect(titleBar); - p.setPen(active ? m_foregroundColor : m_foregroundInactiveColor); + p.setPen(active ? foregroundColor : foregroundInactiveColor); QSizeF size = m_windowTitle.size(); int dx = (top.width() - size.width()) /2; int dy = (top.height()- size.height()) /2; @@ -203,7 +173,7 @@ void QWaylandBradientDecoration::paint(QPaintDevice *device) QRectF rect; // Default pen - QPen pen(active ? m_foregroundColor : m_foregroundInactiveColor); + QPen pen(active ? foregroundColor : foregroundInactiveColor); p.setPen(pen); // Close button @@ -227,7 +197,7 @@ void QWaylandBradientDecoration::paint(QPaintDevice *device) QRectF rect1 = rect.adjusted(inset, 0, 0, -inset); QRectF rect2 = rect.adjusted(0, inset, -inset, 0); p.drawRect(rect1); - p.setBrush(m_backgroundColor); // need to cover up some lines from the other rect + p.setBrush(backgroundColor); // need to cover up some lines from the other rect p.drawRect(rect2); } else { p.drawRect(rect); @@ -267,15 +237,15 @@ bool QWaylandBradientDecoration::handleMouse(QWaylandInputDevice *inputDevice, c Q_UNUSED(global); // Figure out what area mouse is in - QRect wg = waylandWindow()->windowContentGeometry(); - if (local.y() <= wg.top() + margins().top()) { - processMouseTop(inputDevice,local,b,mods); - } else if (local.y() > wg.bottom() - margins().bottom()) { - processMouseBottom(inputDevice,local,b,mods); - } else if (local.x() <= wg.left() + margins().left()) { - processMouseLeft(inputDevice,local,b,mods); - } else if (local.x() > wg.right() - margins().right()) { - processMouseRight(inputDevice,local,b,mods); + QSize ss = waylandWindow()->surfaceSize(); + if (local.y() <= margins().top()) { + processPointerTop(inputDevice, local, b, mods, PointerType::Mouse); + } else if (local.y() >= ss.height() - margins().bottom()) { + processPointerBottom(inputDevice, local, b, mods, PointerType::Mouse); + } else if (local.x() <= margins().left()) { + processPointerLeft(inputDevice, local, b, mods, PointerType::Mouse); + } else if (local.x() >= ss.width() - margins().right()) { + processPointerRight(inputDevice, local, b, mods, PointerType::Mouse); } else { #if QT_CONFIG(cursor) waylandWindow()->restoreMouseCursor(inputDevice); @@ -288,115 +258,154 @@ bool QWaylandBradientDecoration::handleMouse(QWaylandInputDevice *inputDevice, c return true; } -bool QWaylandBradientDecoration::handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, Qt::TouchPointState state, Qt::KeyboardModifiers mods) +bool QWaylandBradientDecoration::handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, QEventPoint::State state, Qt::KeyboardModifiers mods) { - Q_UNUSED(inputDevice); Q_UNUSED(global); - Q_UNUSED(mods); - bool handled = state == Qt::TouchPointPressed; + QSize ss = waylandWindow()->surfaceSize(); + + bool handled = state == QEventPoint::Pressed; if (handled) { - if (closeButtonRect().contains(local)) - QWindowSystemInterface::handleCloseEvent(window()); - else if (maximizeButtonRect().contains(local)) - window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized); - else if (minimizeButtonRect().contains(local)) - window()->setWindowState(Qt::WindowMinimized); - else if (local.y() <= margins().top()) - waylandWindow()->shellSurface()->move(inputDevice); - else + if (local.y() <= margins().top()) { + processPointerTop(inputDevice, local, Qt::LeftButton, mods, PointerType::Touch); + } else if (local.y() >= ss.height() - margins().bottom()) { + processPointerBottom(inputDevice, local, Qt::LeftButton, mods, PointerType::Touch); + } else if (local.x() <= margins().left()) { + processPointerLeft(inputDevice, local, Qt::LeftButton, mods, PointerType::Touch); + } else if (local.x() >= ss.width() - margins().right()) { + processPointerRight(inputDevice, local, Qt::LeftButton, mods, PointerType::Touch); + } else { handled = false; + } } return handled; } -void QWaylandBradientDecoration::processMouseTop(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b, Qt::KeyboardModifiers mods) +void QWaylandBradientDecoration::processPointerTop(QWaylandInputDevice *inputDevice, + const QPointF &local, + Qt::MouseButtons b, + Qt::KeyboardModifiers mods, + PointerType type) { - QRect wg = waylandWindow()->windowContentGeometry(); +#if !QT_CONFIG(cursor) + Q_UNUSED(type); +#endif + + QSize ss = waylandWindow()->surfaceSize(); Q_UNUSED(mods); - if (local.y() <= wg.top() + margins().bottom()) { + if (local.y() <= margins().bottom()) { if (local.x() <= margins().left()) { //top left bit #if QT_CONFIG(cursor) - waylandWindow()->setMouseCursor(inputDevice, Qt::SizeFDiagCursor); + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeFDiagCursor); #endif startResize(inputDevice, Qt::TopEdge | Qt::LeftEdge, b); - } else if (local.x() > wg.right() - margins().right()) { + } else if (local.x() >= ss.width() - margins().right()) { //top right bit #if QT_CONFIG(cursor) - waylandWindow()->setMouseCursor(inputDevice, Qt::SizeBDiagCursor); + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeBDiagCursor); #endif startResize(inputDevice, Qt::TopEdge | Qt::RightEdge, b); } else { //top resize bit #if QT_CONFIG(cursor) - waylandWindow()->setMouseCursor(inputDevice, Qt::SplitVCursor); + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SplitVCursor); #endif startResize(inputDevice, Qt::TopEdge, b); } - } else if (local.x() <= wg.left() + margins().left()) { - processMouseLeft(inputDevice, local, b, mods); - } else if (local.x() > wg.right() - margins().right()) { - processMouseRight(inputDevice, local, b, mods); + } else if (local.x() <= margins().left()) { + processPointerLeft(inputDevice, local, b, mods, type); + } else if (local.x() >= ss.width() - margins().right()) { + processPointerRight(inputDevice, local, b, mods, type); } else if (isRightClicked(b)) { showWindowMenu(inputDevice); } else if (closeButtonRect().contains(local)) { - if (clickButton(b, Close)) + if (type == PointerType::Touch || clickButton(b, Close)) QWindowSystemInterface::handleCloseEvent(window()); } else if (maximizeButtonRect().contains(local)) { - if (clickButton(b, Maximize)) + if (type == PointerType::Touch || clickButton(b, Maximize)) window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized); } else if (minimizeButtonRect().contains(local)) { - if (clickButton(b, Minimize)) + if (type == PointerType::Touch || clickButton(b, Minimize)) window()->setWindowState(Qt::WindowMinimized); } else { #if QT_CONFIG(cursor) - waylandWindow()->restoreMouseCursor(inputDevice); + if (type == PointerType::Mouse) + waylandWindow()->restoreMouseCursor(inputDevice); #endif startMove(inputDevice,b); } } -void QWaylandBradientDecoration::processMouseBottom(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b, Qt::KeyboardModifiers mods) +void QWaylandBradientDecoration::processPointerBottom(QWaylandInputDevice *inputDevice, + const QPointF &local, + Qt::MouseButtons b, + Qt::KeyboardModifiers mods, + PointerType type) { Q_UNUSED(mods); +#if !QT_CONFIG(cursor) + Q_UNUSED(type); +#endif + + QSize ss = waylandWindow()->surfaceSize(); if (local.x() <= margins().left()) { //bottom left bit #if QT_CONFIG(cursor) - waylandWindow()->setMouseCursor(inputDevice, Qt::SizeBDiagCursor); + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeBDiagCursor); #endif startResize(inputDevice, Qt::BottomEdge | Qt::LeftEdge, b); - } else if (local.x() > window()->width() + margins().left()) { + } else if (local.x() >= ss.width() - margins().right()) { //bottom right bit #if QT_CONFIG(cursor) - waylandWindow()->setMouseCursor(inputDevice, Qt::SizeFDiagCursor); + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeFDiagCursor); #endif startResize(inputDevice, Qt::BottomEdge | Qt::RightEdge, b); } else { //bottom bit #if QT_CONFIG(cursor) - waylandWindow()->setMouseCursor(inputDevice, Qt::SplitVCursor); + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeVerCursor); #endif startResize(inputDevice, Qt::BottomEdge, b); } } -void QWaylandBradientDecoration::processMouseLeft(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b, Qt::KeyboardModifiers mods) +void QWaylandBradientDecoration::processPointerLeft(QWaylandInputDevice *inputDevice, + const QPointF &local, + Qt::MouseButtons b, + Qt::KeyboardModifiers mods, + PointerType type) { Q_UNUSED(local); Q_UNUSED(mods); #if QT_CONFIG(cursor) - waylandWindow()->setMouseCursor(inputDevice, Qt::SplitHCursor); + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeHorCursor); +#else + Q_UNUSED(type); #endif startResize(inputDevice, Qt::LeftEdge, b); } -void QWaylandBradientDecoration::processMouseRight(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b, Qt::KeyboardModifiers mods) +void QWaylandBradientDecoration::processPointerRight(QWaylandInputDevice *inputDevice, + const QPointF &local, + Qt::MouseButtons b, + Qt::KeyboardModifiers mods, + PointerType type) { Q_UNUSED(local); Q_UNUSED(mods); #if QT_CONFIG(cursor) - waylandWindow()->setMouseCursor(inputDevice, Qt::SplitHCursor); + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeHorCursor); +#else + Q_UNUSED(type); #endif startResize(inputDevice, Qt::RightEdge, b); } diff --git a/src/plugins/decorations/decorations.pro b/src/plugins/decorations/decorations.pro deleted file mode 100644 index 6d51a450f..000000000 --- a/src/plugins/decorations/decorations.pro +++ /dev/null @@ -1,3 +0,0 @@ -TEMPLATE = subdirs -SUBDIRS += \ - bradient |