Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwaylandadwaitadecoration.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 Jan Grulich <jgrulich@redhat.com>
2// Copyright (C) 2023 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
6
7// QtCore
8#include <QtCore/QLoggingCategory>
9#include <QScopeGuard>
10
11// QtDBus
12#include <QtDBus/QDBusArgument>
13#include <QtDBus/QDBusConnection>
14#include <QtDBus/QDBusMessage>
15#include <QtDBus/QDBusPendingCall>
16#include <QtDBus/QDBusPendingCallWatcher>
17#include <QtDBus/QDBusPendingReply>
18#include <QtDBus/QDBusVariant>
19#include <QtDBus/QtDBus>
20
21// QtGui
22#include <QtGui/QColor>
23#include <QtGui/QPainter>
24#include <QtGui/QPainterPath>
25
26#include <QtGui/private/qguiapplication_p.h>
27#include <QtGui/qpa/qplatformtheme.h>
28
29// QtSvg
30#include <QtSvg/QSvgRenderer>
31
32// QtWayland
33#include <QtWaylandClient/private/qwaylandshmbackingstore_p.h>
34#include <QtWaylandClient/private/qwaylandwindow_p.h>
35
37
38using namespace Qt::StringLiterals;
39
40namespace QtWaylandClient {
41
42static constexpr int ceButtonSpacing = 12;
43static constexpr int ceButtonWidth = 24;
44static constexpr int ceCornerRadius = 12;
45static constexpr int ceShadowsWidth = 10;
46static constexpr int ceTitlebarHeight = 38;
47static constexpr int ceWindowBorderWidth = 1;
48
50 { QWaylandAdwaitaDecoration::CloseIcon, "window-close-symbolic"_L1 },
51 { QWaylandAdwaitaDecoration::MinimizeIcon, "window-minimize-symbolic"_L1 },
52 { QWaylandAdwaitaDecoration::MaximizeIcon, "window-maximize-symbolic"_L1 },
53 { QWaylandAdwaitaDecoration::RestoreIcon, "window-restore-symbolic"_L1 }
54};
55
56const QDBusArgument &operator>>(const QDBusArgument &argument, QMap<QString, QVariantMap> &map)
57{
58 argument.beginMap();
59 map.clear();
60
61 while (!argument.atEnd()) {
62 QString key;
63 QVariantMap value;
64 argument.beginMapEntry();
65 argument >> key >> value;
66 argument.endMapEntry();
67 map.insert(key, value);
68 }
69
70 argument.endMap();
71 return argument;
72}
73
74Q_LOGGING_CATEGORY(lcQWaylandAdwaitaDecorationLog, "qt.qpa.qwaylandadwaitadecoration", QtWarningMsg)
75
94
95QMargins QWaylandAdwaitaDecoration::margins(QWaylandAbstractDecoration::MarginsType marginsType) const
96{
97 const bool onlyShadows = marginsType == QWaylandAbstractDecoration::ShadowsOnly;
98 const bool shadowsExcluded = marginsType == ShadowsExcluded;
99
100 if (waylandWindow()->windowStates() & Qt::WindowMaximized) {
101 // Maximized windows don't have anything around, no shadows, border,
102 // etc. Only report titlebar height in case we are not asking for shadow
103 // margins.
104 return QMargins(0, onlyShadows ? 0 : ceTitlebarHeight, 0, 0);
105 }
106
107 const QWaylandWindow::ToplevelWindowTilingStates tilingStates = waylandWindow()->toplevelWindowTilingStates();
108
109 // Since all sides (left, right, bottom) are going to be same
110 const int marginsBase = shadowsExcluded ? ceWindowBorderWidth : ceShadowsWidth + ceWindowBorderWidth;
111 const int sideMargins = onlyShadows ? ceShadowsWidth : marginsBase;
112 const int topMargins = onlyShadows ? ceShadowsWidth : ceTitlebarHeight + marginsBase;
113
114 return QMargins(tilingStates & QWaylandWindow::WindowTiledLeft ? 0 : sideMargins,
115 tilingStates & QWaylandWindow::WindowTiledTop ? onlyShadows ? 0 : ceTitlebarHeight : topMargins,
116 tilingStates & QWaylandWindow::WindowTiledRight ? 0 : sideMargins,
117 tilingStates & QWaylandWindow::WindowTiledBottom ? 0 : sideMargins);
118}
119
120void QWaylandAdwaitaDecoration::paint(QPaintDevice *device)
121{
122 const QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(ShadowsOnly);
123
124 QPainter p(device);
125 p.setRenderHint(QPainter::Antialiasing);
126
127 /*
128 * Titlebar and window border
129 */
130 QPainterPath path;
131 const QPointF topLeft = { margins(ShadowsOnly).left() + 0.5,
132 margins(ShadowsOnly).top() - 0.5 };
133 const int titleBarWidth = surfaceRect.width() - margins(ShadowsOnly).left()
134 - margins(ShadowsOnly).right() - 0.5;
135 const int borderRectHeight =
136 surfaceRect.height() - margins().top() - margins().bottom() + 0.5;
137
138 // Maximized or tiled won't have rounded corners
139 if (waylandWindow()->windowStates() & Qt::WindowMaximized
140 || waylandWindow()->toplevelWindowTilingStates() != QWaylandWindow::WindowNoState) {
141 path.addRect(QRectF(topLeft, QSizeF(titleBarWidth, margins().top())));
142 } else {
143 path.addRoundedRect(QRectF(topLeft, QSizeF(titleBarWidth, margins().top() + ceCornerRadius)),
144 ceCornerRadius, ceCornerRadius);
145 }
146
147 p.save();
148 p.setPen(color(Border));
149 p.fillPath(path.simplified(), color(Background));
150 p.drawPath(path);
151 p.drawRect(QRectF(topLeft.x(), margins().top(), titleBarWidth, borderRectHeight));
152 p.restore();
153
154 /*
155 * Window title
156 */
157 const QRect top = QRect(margins().left(), margins().bottom(), surfaceRect.width(),
158 margins().top() - margins().bottom());
159 const QString windowTitleText = waylandWindow()->windowTitle();
160 if (!windowTitleText.isEmpty()) {
161 if (m_windowTitle.text() != windowTitleText) {
162 m_windowTitle.setText(windowTitleText);
163 m_windowTitle.prepare();
164 }
165
166 QRect titleBar = top;
167 if (m_placement == Right) {
168 titleBar.setLeft(margins().left());
169 titleBar.setRight(static_cast<int>(buttonRect(Minimize).left()) - 8);
170 } else {
171 titleBar.setLeft(static_cast<int>(buttonRect(Minimize).right()) + 8);
172 titleBar.setRight(surfaceRect.width() - margins().right());
173 }
174
175 p.save();
176 p.setClipRect(titleBar);
177 p.setPen(color(Foreground));
178 QSize size = m_windowTitle.size().toSize();
179 int dx = (top.width() - size.width()) / 2;
180 int dy = (top.height() - size.height()) / 2;
181 p.setFont(*m_font);
182 QPoint windowTitlePoint(top.topLeft().x() + dx, top.topLeft().y() + dy);
183 p.drawStaticText(windowTitlePoint, m_windowTitle);
184 p.restore();
185 }
186
187
188 /*
189 * Buttons
190 */
191 if (m_buttons.contains(Close))
192 drawButton(Close, &p);
193
194 if (m_buttons.contains(Maximize))
195 drawButton(Maximize, &p);
196
197 if (m_buttons.contains(Minimize))
198 drawButton(Minimize, &p);
199}
200
201bool QWaylandAdwaitaDecoration::handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local,
202 const QPointF &global, Qt::MouseButtons b,
203 Qt::KeyboardModifiers mods)
204{
205 Q_UNUSED(global)
206
207 if (local.y() > margins().top())
208 updateButtonHoverState(Button::None);
209
210 // Figure out what area mouse is in
211 QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(ShadowsOnly);
212 if (local.y() <= surfaceRect.top() + margins().top())
213 processMouseTop(inputDevice, local, b, mods);
214 else if (local.y() > surfaceRect.bottom() - margins().bottom())
215 processMouseBottom(inputDevice, local, b, mods);
216 else if (local.x() <= surfaceRect.left() + margins().left())
217 processMouseLeft(inputDevice, local, b, mods);
218 else if (local.x() > surfaceRect.right() - margins().right())
219 processMouseRight(inputDevice, local, b, mods);
220 else {
221#if QT_CONFIG(cursor)
222 waylandWindow()->restoreMouseCursor(inputDevice);
223#endif
224 }
225
226 // Reset clicking state in case a button press is released outside
227 // the button area
228 if (isLeftReleased(b)) {
229 m_clicking = None;
230 requestRepaint();
231 }
232
233 setMouseButtons(b);
234 return false;
235}
236
237bool QWaylandAdwaitaDecoration::handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local,
238 const QPointF &global, QEventPoint::State state,
239 Qt::KeyboardModifiers mods)
240{
241 Q_UNUSED(inputDevice)
242 Q_UNUSED(global)
243 Q_UNUSED(mods)
244
245 bool handled = state == QEventPoint::Pressed;
246
247 if (handled) {
248 if (buttonRect(Close).contains(local))
249 QWindowSystemInterface::handleCloseEvent(window());
250 else if (m_buttons.contains(Maximize) && buttonRect(Maximize).contains(local))
251 window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized);
252 else if (m_buttons.contains(Minimize) && buttonRect(Minimize).contains(local))
253 window()->setWindowState(Qt::WindowMinimized);
254 else if (local.y() <= margins().top())
255 waylandWindow()->shellSurface()->move(inputDevice);
256 else
257 handled = false;
258 }
259
260 return handled;
261}
262
263QString getIconSvg(const QString &iconName)
264{
265 const QStringList themeNames = { QIcon::themeName(), QIcon::fallbackThemeName(), "Adwaita"_L1 };
266
267 qCDebug(lcQWaylandAdwaitaDecorationLog) << "Searched icon themes: " << themeNames;
268
269 for (const QString &themeName : themeNames) {
270 if (themeName.isEmpty())
271 continue;
272
273 for (const QString &path : QIcon::themeSearchPaths()) {
274 if (path.startsWith(QLatin1Char(':')))
275 continue;
276
277 const QString fullPath = QString("%1/%2").arg(path).arg(themeName);
278 QDirIterator dirIt(fullPath, {"*.svg"}, QDir::Files, QDirIterator::Subdirectories);
279 while (dirIt.hasNext()) {
280 const QString fileName = dirIt.next();
281 const QFileInfo fileInfo(fileName);
282
283 if (fileInfo.fileName() == iconName) {
284 qCDebug(lcQWaylandAdwaitaDecorationLog) << "Using " << iconName << " from " << themeName << " theme";
285 QFile readFile(fileInfo.filePath());
286 if (!readFile.open(QFile::ReadOnly)) {
287 qCWarning(lcQWaylandAdwaitaDecorationLog, "Failed to open %ls: %ls",
288 qUtf16Printable(readFile.fileName()),
289 qUtf16Printable(readFile.errorString()));
290 }
291 return readFile.readAll();
292 }
293 }
294 }
295 }
296
297 qCWarning(lcQWaylandAdwaitaDecorationLog) << "Failed to find an svg icon for " << iconName;
298
299 return QString();
300}
301
302void QWaylandAdwaitaDecoration::loadConfiguration()
303{
304 qRegisterMetaType<QDBusVariant>();
305 qDBusRegisterMetaType<QMap<QString, QVariantMap>>();
306
307 QDBusConnection connection = QDBusConnection::sessionBus();
308
309 QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1,
310 "/org/freedesktop/portal/desktop"_L1,
311 "org.freedesktop.portal.Settings"_L1,
312 "ReadAll"_L1);
313 message << QStringList{ { "org.gnome.desktop.wm.preferences"_L1 },
314 { "org.freedesktop.appearance"_L1 } };
315
316 QDBusPendingCall pendingCall = connection.asyncCall(message);
317 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
318 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
319 QDBusPendingReply<QMap<QString, QVariantMap>> reply = *watcher;
320 if (reply.isValid()) {
321 QMap<QString, QVariantMap> settings = reply.value();
322 if (!settings.isEmpty()) {
323 const uint colorScheme = settings.value("org.freedesktop.appearance"_L1).value("color-scheme"_L1).toUInt();
324 updateColors(colorScheme == 1); // 1 == Prefer Dark
325 const QString buttonLayout = settings.value("org.gnome.desktop.wm.preferences"_L1).value("button-layout"_L1).toString();
326 if (!buttonLayout.isEmpty())
327 updateTitlebarLayout(buttonLayout);
328 // Do not rely on titlebar-font in case titlebar-uses-desktop-font is set to true
329 // https://gitlab.gnome.org/GNOME/gsettings-desktop-schemas/-/blob/main/schemas/org.gnome.desktop.wm.preferences.gschema.xml.in
330 const bool titlebarUseDesktopFont = settings.value("org.gnome.desktop.wm.preferences"_L1).value("titlebar-uses-desktop-font"_L1).toBool();
331 if (!titlebarUseDesktopFont) {
332 // Workaround for QGtkStyle not having correct titlebar font
333 const QString titlebarFont =
334 settings.value("org.gnome.desktop.wm.preferences"_L1).value("titlebar-font"_L1).toString();
335 if (titlebarFont.contains("bold"_L1, Qt::CaseInsensitive)) {
336 m_font->setBold(true);
337 }
338 }
339 }
340 }
341 watcher->deleteLater();
342 });
343
344 QDBusConnection::sessionBus().connect(QString(), "/org/freedesktop/portal/desktop"_L1,
345 "org.freedesktop.portal.Settings"_L1, "SettingChanged"_L1, this,
346 SLOT(settingChanged(QString,QString,QDBusVariant)));
347
348 // Load SVG icons
349 for (auto mapIt = buttonMap.constBegin(); mapIt != buttonMap.constEnd(); mapIt++) {
350 const QString fullName = mapIt.value() + QStringLiteral(".svg");
351 m_icons[mapIt.key()] = getIconSvg(fullName);
352 }
353
354 updateColors(false);
355}
356
357void QWaylandAdwaitaDecoration::updateColors(bool isDark)
358{
359 qCDebug(lcQWaylandAdwaitaDecorationLog) << "Color scheme changed to: " << (isDark ? "dark" : "light");
360
361 m_colors = { { Background, isDark ? QColor(0x2e2e32) : QColor(0xffffff) },
362 { BackgroundInactive, isDark ? QColor(0x222226) : QColor(0xfafafb) },
363 { Foreground, isDark ? QColor(0xffffff) : QColor(0x333338) },
364 { ForegroundInactive, isDark ? QColor(0x919193) : QColor(0x969699) },
365 { Border, isDark ? QColor(0x2e2e32) : QColor(0xffffff) },
366 { BorderInactive, isDark ? QColor(0x2e2e32) : QColor(0xffffff) },
367 { ButtonBackground, isDark ? QColor(0x434347) : QColor(0xebebeb) },
368 { ButtonBackgroundInactive, isDark ? QColor(0x2d2d31) : QColor(0xf0f0f1) },
369 { HoveredButtonBackground, isDark ? QColor(0x4d4d51) : QColor(0xe0e0e1) },
370 { PressedButtonBackground, isDark ? QColor(0x6c6c6f) : QColor(0xc2c2c3) } };
371 requestRepaint();
372}
373
374void QWaylandAdwaitaDecoration::updateTitlebarLayout(const QString &layout)
375{
376 const QStringList layouts = layout.split(QLatin1Char(':'));
377 if (layouts.count() != 2)
378 return;
379
380 // Remove previous configuration
381 m_buttons.clear();
382
383 const QString &leftLayout = layouts.at(0);
384 const QString &rightLayout = layouts.at(1);
385 m_placement = leftLayout.contains("close"_L1) ? Left : Right;
386
387 int pos = 1;
388 const QString &buttonLayout = m_placement == Right ? rightLayout : leftLayout;
389
390 QStringList buttonList = buttonLayout.split(QLatin1Char(','));
391 if (m_placement == Right)
392 std::reverse(buttonList.begin(), buttonList.end());
393
394 for (const QString &button : buttonList) {
395 if (button == "close"_L1)
396 m_buttons.insert(Close, pos);
397 else if (button == "maximize"_L1)
398 m_buttons.insert(Maximize, pos);
399 else if (button == "minimize"_L1)
400 m_buttons.insert(Minimize, pos);
401
402 pos++;
403 }
404
405 qCDebug(lcQWaylandAdwaitaDecorationLog) << "Button layout changed to: " << layout;
406
407 requestRepaint();
408}
409
410void QWaylandAdwaitaDecoration::settingChanged(const QString &group, const QString &key,
411 const QDBusVariant &value)
412{
413 if (group == "org.gnome.desktop.wm.preferences"_L1 && key == "button-layout"_L1) {
414 const QString layout = value.variant().toString();
415 updateTitlebarLayout(layout);
416 } else if (group == "org.freedesktop.appearance"_L1 && key == "color-scheme"_L1) {
417 const uint colorScheme = value.variant().toUInt();
418 updateColors(colorScheme == 1); // 1 == Prefer Dark
419 }
420}
421
422QRectF QWaylandAdwaitaDecoration::buttonRect(Button button) const
423{
424 int xPos;
425 int yPos;
426 const int btnPos = m_buttons.value(button);
427
428 const QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(QWaylandAbstractDecoration::ShadowsOnly);
429 if (m_placement == Right) {
430 xPos = surfaceRect.width();
431 xPos -= ceButtonWidth * btnPos;
432 xPos -= ceButtonSpacing * btnPos;
433 xPos -= margins(ShadowsOnly).right();
434 } else {
435 xPos = 0;
436 xPos += ceButtonWidth * btnPos;
437 xPos += ceButtonSpacing * btnPos;
438 xPos += margins(ShadowsOnly).left();
439 // We are painting from the left to the right so the real
440 // position doesn't need to by moved by the size of the button.
441 xPos -= ceButtonWidth;
442 }
443
444 yPos = margins().top();
445 yPos += margins().bottom();
446 yPos -= ceButtonWidth;
447 yPos /= 2;
448
449 return QRectF(xPos, yPos, ceButtonWidth, ceButtonWidth);
450}
451
452static void renderFlatRoundedButtonFrame(QPainter *painter, const QRect &rect, const QColor &color)
453{
454 painter->save();
455 painter->setRenderHint(QPainter::Antialiasing, true);
456 painter->setPen(Qt::NoPen);
457 painter->setBrush(color);
458 painter->drawEllipse(rect);
459 painter->restore();
460}
461
462static void renderButtonIcon(const QString &svgIcon, QPainter *painter, const QRect &rect, const QColor &color)
463{
464 painter->save();
465 painter->setRenderHints(QPainter::Antialiasing, true);
466
467 QString icon = svgIcon;
468 QRegularExpression regexp("fill=[\"']#[0-9A-F]{6}[\"']", QRegularExpression::CaseInsensitiveOption);
469 QRegularExpression regexpAlt("fill:#[0-9A-F]{6}", QRegularExpression::CaseInsensitiveOption);
470 QRegularExpression regexpCurrentColor("fill=[\"']currentColor[\"']");
471 icon.replace(regexp, QString("fill=\"%1\"").arg(color.name()));
472 icon.replace(regexpAlt, QString("fill:%1").arg(color.name()));
473 icon.replace(regexpCurrentColor, QString("fill=\"%1\"").arg(color.name()));
474 QSvgRenderer svgRenderer(icon.toLocal8Bit());
475 svgRenderer.render(painter, rect);
476
477 painter->restore();
478}
479
480static void renderButtonIcon(QWaylandAdwaitaDecoration::ButtonIcon buttonIcon, QPainter *painter, const QRect &rect)
481{
482 QString iconName = buttonMap[buttonIcon];
483
484 painter->save();
485 painter->setRenderHints(QPainter::Antialiasing, true);
486 painter->drawPixmap(rect, QIcon::fromTheme(iconName).pixmap(ceButtonWidth, ceButtonWidth));
487 painter->restore();
488}
489
501
502void QWaylandAdwaitaDecoration::drawButton(Button button, QPainter *painter)
503{
504 const Qt::WindowStates windowStates = waylandWindow()->windowStates();
505 const bool maximized = windowStates & Qt::WindowMaximized;
506
507 const QRect btnRect = buttonRect(button).toRect();
508 renderFlatRoundedButtonFrame(painter, btnRect, color(ButtonBackground, button));
509
510 QRect adjustedBtnRect = btnRect;
511 adjustedBtnRect.setSize(QSize(16, 16));
512 adjustedBtnRect.translate(4, 4);
513 const QString svgIcon = m_icons[iconFromButtonAndState(button, maximized)];
514 if (!svgIcon.isEmpty())
515 renderButtonIcon(svgIcon, painter, adjustedBtnRect, color(Foreground));
516 else // Fallback to use QIcon
517 renderButtonIcon(iconFromButtonAndState(button, maximized), painter, adjustedBtnRect);
518}
519
520static QColor makeTransparent(const QColor &color, qreal level)
521{
522 QColor transparentColor = color;
523 transparentColor.setAlphaF(level);
524 return transparentColor;
525}
526
527QColor QWaylandAdwaitaDecoration::color(ColorType type, Button button)
528{
529 const bool active = waylandWindow()->windowStates() & Qt::WindowActive;
530
531 switch (type) {
532 case Background:
533 case BackgroundInactive:
534 return active ? m_colors[Background] : m_colors[BackgroundInactive];
535 case Foreground:
536 case ForegroundInactive:
537 return active ? m_colors[Foreground] : m_colors[ForegroundInactive];
538 case Border:
539 case BorderInactive:
540 return active ? makeTransparent(m_colors[Border], 0.5) : makeTransparent(m_colors[BorderInactive], 0.5);
541 case ButtonBackground:
542 case ButtonBackgroundInactive:
543 case HoveredButtonBackground: {
544 if (m_clicking == button) {
545 return m_colors[PressedButtonBackground];
546 } else if (m_hoveredButtons.testFlag(button)) {
547 return m_colors[HoveredButtonBackground];
548 }
549 return active ? m_colors[ButtonBackground] : m_colors[ButtonBackgroundInactive];
550 }
551 default:
552 return m_colors[Background];
553 }
554}
555
556bool QWaylandAdwaitaDecoration::clickButton(Qt::MouseButtons b, Button btn)
557{
558 auto repaint = qScopeGuard([this] { requestRepaint(); });
559
560 if (isLeftClicked(b)) {
561 m_clicking = btn;
562 return false;
563 } else if (isLeftReleased(b)) {
564 if (m_clicking == btn) {
565 m_clicking = None;
566 return true;
567 } else {
568 m_clicking = None;
569 }
570 }
571 return false;
572}
573
574bool QWaylandAdwaitaDecoration::doubleClickButton(Qt::MouseButtons b, const QPointF &local,
575 const QDateTime &currentTime)
576{
577 if (isLeftClicked(b)) {
578 const qint64 clickInterval = m_lastButtonClick.msecsTo(currentTime);
579 m_lastButtonClick = currentTime;
580 const int doubleClickDistance = 5;
581 const QPointF posDiff = m_lastButtonClickPosition - local;
582 if ((clickInterval <= 500)
583 && ((posDiff.x() <= doubleClickDistance && posDiff.x() >= -doubleClickDistance)
584 && ((posDiff.y() <= doubleClickDistance && posDiff.y() >= -doubleClickDistance)))) {
585 return true;
586 }
587
588 m_lastButtonClickPosition = local;
589 }
590
591 return false;
592}
593
594void QWaylandAdwaitaDecoration::updateButtonHoverState(Button hoveredButton)
595{
596 bool currentCloseButtonState = m_hoveredButtons.testFlag(Close);
597 bool currentMaximizeButtonState = m_hoveredButtons.testFlag(Maximize);
598 bool currentMinimizeButtonState = m_hoveredButtons.testFlag(Minimize);
599
600 m_hoveredButtons.setFlag(Close, hoveredButton == Button::Close);
601 m_hoveredButtons.setFlag(Maximize, hoveredButton == Button::Maximize);
602 m_hoveredButtons.setFlag(Minimize, hoveredButton == Button::Minimize);
603
604 if (m_hoveredButtons.testFlag(Close) != currentCloseButtonState
605 || m_hoveredButtons.testFlag(Maximize) != currentMaximizeButtonState
606 || m_hoveredButtons.testFlag(Minimize) != currentMinimizeButtonState) {
607 requestRepaint();
608 }
609}
610
611void QWaylandAdwaitaDecoration::processMouseTop(QWaylandInputDevice *inputDevice, const QPointF &local,
612 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
613{
614 Q_UNUSED(mods)
615
616 QDateTime currentDateTime = QDateTime::currentDateTime();
617 QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(ShadowsOnly);
618
619 if (!buttonRect(Close).contains(local) && !buttonRect(Maximize).contains(local)
620 && !buttonRect(Minimize).contains(local))
621 updateButtonHoverState(Button::None);
622
623 if (local.y() <= surfaceRect.top() + margins().bottom()) {
624 if (local.x() <= margins().left()) {
625 // top left bit
626#if QT_CONFIG(cursor)
627 waylandWindow()->applyCursor(inputDevice, Qt::SizeFDiagCursor);
628#endif
629 startResize(inputDevice, Qt::TopEdge | Qt::LeftEdge, b);
630 } else if (local.x() > surfaceRect.right() - margins().left()) {
631 // top right bit
632#if QT_CONFIG(cursor)
633 waylandWindow()->applyCursor(inputDevice, Qt::SizeBDiagCursor);
634#endif
635 startResize(inputDevice, Qt::TopEdge | Qt::RightEdge, b);
636 } else {
637 // top resize bit
638#if QT_CONFIG(cursor)
639 waylandWindow()->applyCursor(inputDevice, Qt::SizeVerCursor);
640#endif
641 startResize(inputDevice, Qt::TopEdge, b);
642 }
643 } else if (local.x() <= surfaceRect.left() + margins().left()) {
644 processMouseLeft(inputDevice, local, b, mods);
645 } else if (local.x() > surfaceRect.right() - margins().right()) {
646 processMouseRight(inputDevice, local, b, mods);
647 } else if (buttonRect(Close).contains(local)) {
648 if (clickButton(b, Close)) {
649 QWindowSystemInterface::handleCloseEvent(window());
650 m_hoveredButtons.setFlag(Close, false);
651 }
652 updateButtonHoverState(Close);
653 } else if (m_buttons.contains(Maximize) && buttonRect(Maximize).contains(local)) {
654 updateButtonHoverState(Maximize);
655 if (clickButton(b, Maximize)) {
656 window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized);
657 m_hoveredButtons.setFlag(Maximize, false);
658 }
659 } else if (m_buttons.contains(Minimize) && buttonRect(Minimize).contains(local)) {
660 updateButtonHoverState(Minimize);
661 if (clickButton(b, Minimize)) {
662 window()->setWindowState(Qt::WindowMinimized);
663 m_hoveredButtons.setFlag(Minimize, false);
664 }
665 } else if (doubleClickButton(b, local, currentDateTime)) {
666 window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized);
667 } else {
668 // Show window menu
669 if (b == Qt::MouseButton::RightButton)
670 waylandWindow()->shellSurface()->showWindowMenu(inputDevice);
671#if QT_CONFIG(cursor)
672 waylandWindow()->restoreMouseCursor(inputDevice);
673#endif
674 startMove(inputDevice, b);
675 }
676}
677
678void QWaylandAdwaitaDecoration::processMouseBottom(QWaylandInputDevice *inputDevice, const QPointF &local,
679 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
680{
681 Q_UNUSED(mods)
682 if (local.x() <= margins().left()) {
683 // bottom left bit
684#if QT_CONFIG(cursor)
685 waylandWindow()->applyCursor(inputDevice, Qt::SizeBDiagCursor);
686#endif
687 startResize(inputDevice, Qt::BottomEdge | Qt::LeftEdge, b);
688 } else if (local.x() > window()->width() + margins().right()) {
689 // bottom right bit
690#if QT_CONFIG(cursor)
691 waylandWindow()->applyCursor(inputDevice, Qt::SizeFDiagCursor);
692#endif
693 startResize(inputDevice, Qt::BottomEdge | Qt::RightEdge, b);
694 } else {
695 // bottom bit
696#if QT_CONFIG(cursor)
697 waylandWindow()->applyCursor(inputDevice, Qt::SizeVerCursor);
698#endif
699 startResize(inputDevice, Qt::BottomEdge, b);
700 }
701}
702
703void QWaylandAdwaitaDecoration::processMouseLeft(QWaylandInputDevice *inputDevice, const QPointF &local,
704 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
705{
706 Q_UNUSED(local)
707 Q_UNUSED(mods)
708#if QT_CONFIG(cursor)
709 waylandWindow()->applyCursor(inputDevice, Qt::SizeHorCursor);
710#endif
711 startResize(inputDevice, Qt::LeftEdge, b);
712}
713
714void QWaylandAdwaitaDecoration::processMouseRight(QWaylandInputDevice *inputDevice, const QPointF &local,
715 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
716{
717 Q_UNUSED(local)
718 Q_UNUSED(mods)
719#if QT_CONFIG(cursor)
720 waylandWindow()->applyCursor(inputDevice, Qt::SizeHorCursor);
721#endif
722 startResize(inputDevice, Qt::RightEdge, b);
723}
724
725void QWaylandAdwaitaDecoration::requestRepaint() const
726{
727 // Set dirty flag
728 if (waylandWindow()->decoration())
729 waylandWindow()->decoration()->update();
730
731 // Request re-paint
732 waylandWindow()->window()->requestUpdate();
733}
734
735} // namespace QtWaylandClient
736
737QT_END_NAMESPACE
738
739#include "moc_qwaylandadwaitadecoration_p.cpp"
\inmodule QtDBus
friend class QPainter
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
QMargins margins(MarginsType marginsType=Full) const override
Q_LOGGING_CATEGORY(lcQpaWayland, "qt.qpa.wayland")
static QWaylandAdwaitaDecoration::ButtonIcon iconFromButtonAndState(QWaylandAdwaitaDecoration::Button button, bool maximized)
static constexpr int ceTitlebarHeight
static constexpr int ceButtonWidth
static QMap< QWaylandAdwaitaDecoration::ButtonIcon, QString > buttonMap
static constexpr int ceButtonSpacing
static constexpr int ceWindowBorderWidth
static constexpr int ceShadowsWidth
static QColor makeTransparent(const QColor &color, qreal level)
const QDBusArgument & operator>>(const QDBusArgument &argument, QMap< QString, QVariantMap > &map)
static void renderButtonIcon(const QString &svgIcon, QPainter *painter, const QRect &rect, const QColor &color)
QString getIconSvg(const QString &iconName)
static constexpr int ceCornerRadius
static void renderButtonIcon(QWaylandAdwaitaDecoration::ButtonIcon buttonIcon, QPainter *painter, const QRect &rect)
static void renderFlatRoundedButtonFrame(QPainter *painter, const QRect &rect, const QColor &color)