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