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 frameWidth = surfaceRect.width() - margins(ShadowsOnly).left()
134 - margins(ShadowsOnly).right() - 0.5;
135 const int frameHeight = surfaceRect.height() - margins(ShadowsOnly).top()
136 - margins(ShadowsOnly).bottom() + 0.5;
137 const QRectF fullFrameRect = QRectF(topLeft, QSizeF(frameWidth, frameHeight));
138
139 // Maximized or tiled won't have rounded corners
140 if (waylandWindow()->windowStates() & Qt::WindowMaximized
141 || waylandWindow()->toplevelWindowTilingStates() != QWaylandWindow::WindowNoState) {
142 path.addRect(fullFrameRect);
143 } else {
144 const QSizeF radiusRectSize = QSizeF(ceCornerRadius * 2, ceCornerRadius * 2);
145 path.moveTo(fullFrameRect.bottomLeft());
146 path.lineTo(fullFrameRect.topLeft() + QPointF(0, ceCornerRadius));
147 path.arcTo(QRectF(fullFrameRect.topLeft(), radiusRectSize), 180, -90);
148 path.lineTo(fullFrameRect.topRight() - QPointF(ceCornerRadius, 0));
149 path.arcTo(QRectF(fullFrameRect.topRight() - QPointF(ceCornerRadius * 2, 0),
150 radiusRectSize),
151 90, -90);
152 path.lineTo(fullFrameRect.bottomRight());
153 path.closeSubpath();
154 }
155
156 p.save();
157 p.setPen(color(Border));
158 p.fillPath(path.simplified(), color(Background));
159 p.drawPath(path);
160 p.restore();
161
162 /*
163 * Window title
164 */
165 const QRect top = QRect(margins().left(), margins().bottom(), surfaceRect.width(),
166 margins().top() - margins().bottom());
167 const QString windowTitleText = waylandWindow()->windowTitle();
168 if (!windowTitleText.isEmpty()) {
169 if (m_windowTitle.text() != windowTitleText) {
170 m_windowTitle.setText(windowTitleText);
171 m_windowTitle.prepare();
172 }
173
174 QRect titleBar = top;
175 if (m_placement == Right) {
176 titleBar.setLeft(margins().left());
177 titleBar.setRight(static_cast<int>(buttonRect(Minimize).left()) - 8);
178 } else {
179 titleBar.setLeft(static_cast<int>(buttonRect(Minimize).right()) + 8);
180 titleBar.setRight(surfaceRect.width() - margins().right());
181 }
182
183 p.save();
184 p.setClipRect(titleBar);
185 p.setPen(color(Foreground));
186 QSize size = m_windowTitle.size().toSize();
187 int dx = (top.width() - size.width()) / 2;
188 int dy = (top.height() - size.height()) / 2;
189 p.setFont(*m_font);
190 QPoint windowTitlePoint(top.topLeft().x() + dx, top.topLeft().y() + dy);
191 p.drawStaticText(windowTitlePoint, m_windowTitle);
192 p.restore();
193 }
194
195
196 /*
197 * Buttons
198 */
199 if (m_buttons.contains(Close))
200 drawButton(Close, &p);
201
202 if (m_buttons.contains(Maximize))
203 drawButton(Maximize, &p);
204
205 if (m_buttons.contains(Minimize))
206 drawButton(Minimize, &p);
207}
208
209bool QWaylandAdwaitaDecoration::handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local,
210 const QPointF &global, Qt::MouseButtons b,
211 Qt::KeyboardModifiers mods)
212{
213 Q_UNUSED(global)
214
215 if (local.y() > margins().top())
216 updateButtonHoverState(Button::None);
217
218 // Figure out what area mouse is in
219 QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(ShadowsOnly);
220 if (local.y() <= surfaceRect.top() + margins().top())
221 processMouseTop(inputDevice, local, b, mods);
222 else if (local.y() > surfaceRect.bottom() - margins().bottom())
223 processMouseBottom(inputDevice, local, b, mods);
224 else if (local.x() <= surfaceRect.left() + margins().left())
225 processMouseLeft(inputDevice, local, b, mods);
226 else if (local.x() > surfaceRect.right() - margins().right())
227 processMouseRight(inputDevice, local, b, mods);
228 else {
229#if QT_CONFIG(cursor)
230 waylandWindow()->restoreMouseCursor(inputDevice);
231#endif
232 }
233
234 // Reset clicking state in case a button press is released outside
235 // the button area
236 if (isLeftReleased(b)) {
237 m_clicking = None;
238 requestRepaint();
239 }
240
241 setMouseButtons(b);
242 return false;
243}
244
245bool QWaylandAdwaitaDecoration::handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local,
246 const QPointF &global, QEventPoint::State state,
247 Qt::KeyboardModifiers mods)
248{
249 Q_UNUSED(inputDevice)
250 Q_UNUSED(global)
251 Q_UNUSED(mods)
252
253 bool handled = state == QEventPoint::Pressed;
254
255 if (handled) {
256 if (buttonRect(Close).contains(local))
257 QWindowSystemInterface::handleCloseEvent(window());
258 else if (m_buttons.contains(Maximize) && buttonRect(Maximize).contains(local))
259 window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized);
260 else if (m_buttons.contains(Minimize) && buttonRect(Minimize).contains(local))
261 window()->setWindowState(Qt::WindowMinimized);
262 else if (local.y() <= margins().top())
263 waylandWindow()->shellSurface()->move(inputDevice);
264 else
265 handled = false;
266 }
267
268 return handled;
269}
270
271QString getIconSvg(const QString &iconName)
272{
273 const QStringList themeNames = { QIcon::themeName(), QIcon::fallbackThemeName(), "Adwaita"_L1 };
274
275 qCDebug(lcQWaylandAdwaitaDecorationLog) << "Searched icon themes: " << themeNames;
276
277 for (const QString &themeName : themeNames) {
278 if (themeName.isEmpty())
279 continue;
280
281 for (const QString &path : QIcon::themeSearchPaths()) {
282 if (path.startsWith(QLatin1Char(':')))
283 continue;
284
285 const QString fullPath = QString("%1/%2").arg(path).arg(themeName);
286 QDirIterator dirIt(fullPath, {"*.svg"}, QDir::Files, QDirIterator::Subdirectories);
287 while (dirIt.hasNext()) {
288 const QString fileName = dirIt.next();
289 const QFileInfo fileInfo(fileName);
290
291 if (fileInfo.fileName() == iconName) {
292 qCDebug(lcQWaylandAdwaitaDecorationLog) << "Using " << iconName << " from " << themeName << " theme";
293 QFile readFile(fileInfo.filePath());
294 if (!readFile.open(QFile::ReadOnly)) {
295 qCWarning(lcQWaylandAdwaitaDecorationLog, "Failed to open %ls: %ls",
296 qUtf16Printable(readFile.fileName()),
297 qUtf16Printable(readFile.errorString()));
298 }
299 return readFile.readAll();
300 }
301 }
302 }
303 }
304
305 qCWarning(lcQWaylandAdwaitaDecorationLog) << "Failed to find an svg icon for " << iconName;
306
307 return QString();
308}
309
310void QWaylandAdwaitaDecoration::loadConfiguration()
311{
312 qRegisterMetaType<QDBusVariant>();
313 qDBusRegisterMetaType<QMap<QString, QVariantMap>>();
314
315 QDBusConnection connection = QDBusConnection::sessionBus();
316
317 QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1,
318 "/org/freedesktop/portal/desktop"_L1,
319 "org.freedesktop.portal.Settings"_L1,
320 "ReadAll"_L1);
321 message << QStringList{ { "org.gnome.desktop.wm.preferences"_L1 },
322 { "org.freedesktop.appearance"_L1 } };
323
324 QDBusPendingCall pendingCall = connection.asyncCall(message);
325 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
326 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
327 QDBusPendingReply<QMap<QString, QVariantMap>> reply = *watcher;
328 if (reply.isValid()) {
329 QMap<QString, QVariantMap> settings = reply.value();
330 if (!settings.isEmpty()) {
331 const uint colorScheme = settings.value("org.freedesktop.appearance"_L1).value("color-scheme"_L1).toUInt();
332 updateColors(colorScheme == 1); // 1 == Prefer Dark
333 const QString buttonLayout = settings.value("org.gnome.desktop.wm.preferences"_L1).value("button-layout"_L1).toString();
334 if (!buttonLayout.isEmpty())
335 updateTitlebarLayout(buttonLayout);
336 // Do not rely on titlebar-font in case titlebar-uses-desktop-font is set to true
337 // https://gitlab.gnome.org/GNOME/gsettings-desktop-schemas/-/blob/main/schemas/org.gnome.desktop.wm.preferences.gschema.xml.in
338 const bool titlebarUseDesktopFont = settings.value("org.gnome.desktop.wm.preferences"_L1).value("titlebar-uses-desktop-font"_L1).toBool();
339 if (!titlebarUseDesktopFont) {
340 // Workaround for QGtkStyle not having correct titlebar font
341 const QString titlebarFont =
342 settings.value("org.gnome.desktop.wm.preferences"_L1).value("titlebar-font"_L1).toString();
343 if (titlebarFont.contains("bold"_L1, Qt::CaseInsensitive)) {
344 m_font->setBold(true);
345 }
346 }
347 }
348 }
349 watcher->deleteLater();
350 });
351
352 QDBusConnection::sessionBus().connect(QString(), "/org/freedesktop/portal/desktop"_L1,
353 "org.freedesktop.portal.Settings"_L1, "SettingChanged"_L1, this,
354 SLOT(settingChanged(QString,QString,QDBusVariant)));
355
356 // Load SVG icons
357 for (auto mapIt = buttonMap.constBegin(); mapIt != buttonMap.constEnd(); mapIt++) {
358 const QString fullName = mapIt.value() + QStringLiteral(".svg");
359 m_icons[mapIt.key()] = getIconSvg(fullName);
360 }
361
362 updateColors(false);
363}
364
365void QWaylandAdwaitaDecoration::updateColors(bool isDark)
366{
367 qCDebug(lcQWaylandAdwaitaDecorationLog) << "Color scheme changed to: " << (isDark ? "dark" : "light");
368
369 m_colors = { { Background, isDark ? QColor(0x2e2e32) : QColor(0xffffff) },
370 { BackgroundInactive, isDark ? QColor(0x222226) : QColor(0xfafafb) },
371 { Foreground, isDark ? QColor(0xffffff) : QColor(0x333338) },
372 { ForegroundInactive, isDark ? QColor(0x919193) : QColor(0x969699) },
373 { Border, isDark ? QColor(0x2e2e32) : QColor(0xffffff) },
374 { BorderInactive, isDark ? QColor(0x2e2e32) : QColor(0xffffff) },
375 { ButtonBackground, isDark ? QColor(0x434347) : QColor(0xebebeb) },
376 { ButtonBackgroundInactive, isDark ? QColor(0x2d2d31) : QColor(0xf0f0f1) },
377 { HoveredButtonBackground, isDark ? QColor(0x4d4d51) : QColor(0xe0e0e1) },
378 { PressedButtonBackground, isDark ? QColor(0x6c6c6f) : QColor(0xc2c2c3) } };
379 requestRepaint();
380}
381
382void QWaylandAdwaitaDecoration::updateTitlebarLayout(const QString &layout)
383{
384 const QStringList layouts = layout.split(QLatin1Char(':'));
385 if (layouts.count() != 2)
386 return;
387
388 // Remove previous configuration
389 m_buttons.clear();
390
391 const QString &leftLayout = layouts.at(0);
392 const QString &rightLayout = layouts.at(1);
393 m_placement = leftLayout.contains("close"_L1) ? Left : Right;
394
395 int pos = 1;
396 const QString &buttonLayout = m_placement == Right ? rightLayout : leftLayout;
397
398 QStringList buttonList = buttonLayout.split(QLatin1Char(','));
399 if (m_placement == Right)
400 std::reverse(buttonList.begin(), buttonList.end());
401
402 for (const QString &button : buttonList) {
403 if (button == "close"_L1)
404 m_buttons.insert(Close, pos);
405 else if (button == "maximize"_L1)
406 m_buttons.insert(Maximize, pos);
407 else if (button == "minimize"_L1)
408 m_buttons.insert(Minimize, pos);
409
410 pos++;
411 }
412
413 qCDebug(lcQWaylandAdwaitaDecorationLog) << "Button layout changed to: " << layout;
414
415 requestRepaint();
416}
417
418void QWaylandAdwaitaDecoration::settingChanged(const QString &group, const QString &key,
419 const QDBusVariant &value)
420{
421 if (group == "org.gnome.desktop.wm.preferences"_L1 && key == "button-layout"_L1) {
422 const QString layout = value.variant().toString();
423 updateTitlebarLayout(layout);
424 } else if (group == "org.freedesktop.appearance"_L1 && key == "color-scheme"_L1) {
425 const uint colorScheme = value.variant().toUInt();
426 updateColors(colorScheme == 1); // 1 == Prefer Dark
427 }
428}
429
430QRectF QWaylandAdwaitaDecoration::buttonRect(Button button) const
431{
432 int xPos;
433 int yPos;
434 const int btnPos = m_buttons.value(button);
435
436 const QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(QWaylandAbstractDecoration::ShadowsOnly);
437 if (m_placement == Right) {
438 xPos = surfaceRect.width();
439 xPos -= ceButtonWidth * btnPos;
440 xPos -= ceButtonSpacing * btnPos;
441 xPos -= margins(ShadowsOnly).right();
442 } else {
443 xPos = 0;
444 xPos += ceButtonWidth * btnPos;
445 xPos += ceButtonSpacing * btnPos;
446 xPos += margins(ShadowsOnly).left();
447 // We are painting from the left to the right so the real
448 // position doesn't need to by moved by the size of the button.
449 xPos -= ceButtonWidth;
450 }
451
452 yPos = margins().top();
453 yPos += margins().bottom();
454 yPos -= ceButtonWidth;
455 yPos /= 2;
456
457 return QRectF(xPos, yPos, ceButtonWidth, ceButtonWidth);
458}
459
460static void renderFlatRoundedButtonFrame(QPainter *painter, const QRect &rect, const QColor &color)
461{
462 painter->save();
463 painter->setRenderHint(QPainter::Antialiasing, true);
464 painter->setPen(Qt::NoPen);
465 painter->setBrush(color);
466 painter->drawEllipse(rect);
467 painter->restore();
468}
469
470static void renderButtonIcon(const QString &svgIcon, QPainter *painter, const QRect &rect, const QColor &color)
471{
472 painter->save();
473 painter->setRenderHints(QPainter::Antialiasing, true);
474
475 QString icon = svgIcon;
476 QRegularExpression regexp("fill=[\"']#[0-9A-F]{6}[\"']", QRegularExpression::CaseInsensitiveOption);
477 QRegularExpression regexpAlt("fill:#[0-9A-F]{6}", QRegularExpression::CaseInsensitiveOption);
478 QRegularExpression regexpCurrentColor("fill=[\"']currentColor[\"']");
479 icon.replace(regexp, QString("fill=\"%1\"").arg(color.name()));
480 icon.replace(regexpAlt, QString("fill:%1").arg(color.name()));
481 icon.replace(regexpCurrentColor, QString("fill=\"%1\"").arg(color.name()));
482 QSvgRenderer svgRenderer(icon.toLocal8Bit());
483 svgRenderer.render(painter, rect);
484
485 painter->restore();
486}
487
488static void renderButtonIcon(QWaylandAdwaitaDecoration::ButtonIcon buttonIcon, QPainter *painter, const QRect &rect)
489{
490 QString iconName = buttonMap[buttonIcon];
491
492 painter->save();
493 painter->setRenderHints(QPainter::Antialiasing, true);
494 painter->drawPixmap(rect, QIcon::fromTheme(iconName).pixmap(ceButtonWidth, ceButtonWidth));
495 painter->restore();
496}
497
509
510void QWaylandAdwaitaDecoration::drawButton(Button button, QPainter *painter)
511{
512 const Qt::WindowStates windowStates = waylandWindow()->windowStates();
513 const bool maximized = windowStates & Qt::WindowMaximized;
514
515 const QRect btnRect = buttonRect(button).toRect();
516 renderFlatRoundedButtonFrame(painter, btnRect, color(ButtonBackground, button));
517
518 QRect adjustedBtnRect = btnRect;
519 adjustedBtnRect.setSize(QSize(16, 16));
520 adjustedBtnRect.translate(4, 4);
521 const QString svgIcon = m_icons[iconFromButtonAndState(button, maximized)];
522 if (!svgIcon.isEmpty())
523 renderButtonIcon(svgIcon, painter, adjustedBtnRect, color(Foreground));
524 else // Fallback to use QIcon
525 renderButtonIcon(iconFromButtonAndState(button, maximized), painter, adjustedBtnRect);
526}
527
528static QColor makeTransparent(const QColor &color, qreal level)
529{
530 QColor transparentColor = color;
531 transparentColor.setAlphaF(level);
532 return transparentColor;
533}
534
535QColor QWaylandAdwaitaDecoration::color(ColorType type, Button button)
536{
537 const bool active = waylandWindow()->windowStates() & Qt::WindowActive;
538
539 switch (type) {
540 case Background:
541 case BackgroundInactive:
542 return active ? m_colors[Background] : m_colors[BackgroundInactive];
543 case Foreground:
544 case ForegroundInactive:
545 return active ? m_colors[Foreground] : m_colors[ForegroundInactive];
546 case Border:
547 case BorderInactive:
548 return active ? makeTransparent(m_colors[Border], 0.5) : makeTransparent(m_colors[BorderInactive], 0.5);
549 case ButtonBackground:
550 case ButtonBackgroundInactive:
551 case HoveredButtonBackground: {
552 if (m_clicking == button) {
553 return m_colors[PressedButtonBackground];
554 } else if (m_hoveredButtons.testFlag(button)) {
555 return m_colors[HoveredButtonBackground];
556 }
557 return active ? m_colors[ButtonBackground] : m_colors[ButtonBackgroundInactive];
558 }
559 default:
560 return m_colors[Background];
561 }
562}
563
564bool QWaylandAdwaitaDecoration::clickButton(Qt::MouseButtons b, Button btn)
565{
566 auto repaint = qScopeGuard([this] { requestRepaint(); });
567
568 if (isLeftClicked(b)) {
569 m_clicking = btn;
570 return false;
571 } else if (isLeftReleased(b)) {
572 if (m_clicking == btn) {
573 m_clicking = None;
574 return true;
575 } else {
576 m_clicking = None;
577 }
578 }
579 return false;
580}
581
582bool QWaylandAdwaitaDecoration::doubleClickButton(Qt::MouseButtons b, const QPointF &local,
583 const QDateTime &currentTime)
584{
585 if (isLeftClicked(b)) {
586 const qint64 clickInterval = m_lastButtonClick.msecsTo(currentTime);
587 m_lastButtonClick = currentTime;
588 const int doubleClickDistance = 5;
589 const QPointF posDiff = m_lastButtonClickPosition - local;
590 if ((clickInterval <= 500)
591 && ((posDiff.x() <= doubleClickDistance && posDiff.x() >= -doubleClickDistance)
592 && ((posDiff.y() <= doubleClickDistance && posDiff.y() >= -doubleClickDistance)))) {
593 return true;
594 }
595
596 m_lastButtonClickPosition = local;
597 }
598
599 return false;
600}
601
602void QWaylandAdwaitaDecoration::updateButtonHoverState(Button hoveredButton)
603{
604 bool currentCloseButtonState = m_hoveredButtons.testFlag(Close);
605 bool currentMaximizeButtonState = m_hoveredButtons.testFlag(Maximize);
606 bool currentMinimizeButtonState = m_hoveredButtons.testFlag(Minimize);
607
608 m_hoveredButtons.setFlag(Close, hoveredButton == Button::Close);
609 m_hoveredButtons.setFlag(Maximize, hoveredButton == Button::Maximize);
610 m_hoveredButtons.setFlag(Minimize, hoveredButton == Button::Minimize);
611
612 if (m_hoveredButtons.testFlag(Close) != currentCloseButtonState
613 || m_hoveredButtons.testFlag(Maximize) != currentMaximizeButtonState
614 || m_hoveredButtons.testFlag(Minimize) != currentMinimizeButtonState) {
615 requestRepaint();
616 }
617}
618
619void QWaylandAdwaitaDecoration::processMouseTop(QWaylandInputDevice *inputDevice, const QPointF &local,
620 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
621{
622 Q_UNUSED(mods)
623
624 QDateTime currentDateTime = QDateTime::currentDateTime();
625 QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(ShadowsOnly);
626
627 if (!buttonRect(Close).contains(local) && !buttonRect(Maximize).contains(local)
628 && !buttonRect(Minimize).contains(local))
629 updateButtonHoverState(Button::None);
630
631 if (local.y() <= surfaceRect.top() + margins().bottom()) {
632 if (local.x() <= margins().left()) {
633 // top left bit
634#if QT_CONFIG(cursor)
635 waylandWindow()->applyCursor(inputDevice, Qt::SizeFDiagCursor);
636#endif
637 startResize(inputDevice, Qt::TopEdge | Qt::LeftEdge, b);
638 } else if (local.x() > surfaceRect.right() - margins().left()) {
639 // top right bit
640#if QT_CONFIG(cursor)
641 waylandWindow()->applyCursor(inputDevice, Qt::SizeBDiagCursor);
642#endif
643 startResize(inputDevice, Qt::TopEdge | Qt::RightEdge, b);
644 } else {
645 // top resize bit
646#if QT_CONFIG(cursor)
647 waylandWindow()->applyCursor(inputDevice, Qt::SizeVerCursor);
648#endif
649 startResize(inputDevice, Qt::TopEdge, b);
650 }
651 } else if (local.x() <= surfaceRect.left() + margins().left()) {
652 processMouseLeft(inputDevice, local, b, mods);
653 } else if (local.x() > surfaceRect.right() - margins().right()) {
654 processMouseRight(inputDevice, local, b, mods);
655 } else if (buttonRect(Close).contains(local)) {
656 if (clickButton(b, Close)) {
657 QWindowSystemInterface::handleCloseEvent(window());
658 m_hoveredButtons.setFlag(Close, false);
659 }
660 updateButtonHoverState(Close);
661 } else if (m_buttons.contains(Maximize) && buttonRect(Maximize).contains(local)) {
662 updateButtonHoverState(Maximize);
663 if (clickButton(b, Maximize)) {
664 window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized);
665 m_hoveredButtons.setFlag(Maximize, false);
666 }
667 } else if (m_buttons.contains(Minimize) && buttonRect(Minimize).contains(local)) {
668 updateButtonHoverState(Minimize);
669 if (clickButton(b, Minimize)) {
670 window()->setWindowState(Qt::WindowMinimized);
671 m_hoveredButtons.setFlag(Minimize, false);
672 }
673 } else if (doubleClickButton(b, local, currentDateTime)) {
674 window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized);
675 } else {
676 // Show window menu
677 if (b == Qt::MouseButton::RightButton)
678 waylandWindow()->shellSurface()->showWindowMenu(inputDevice);
679#if QT_CONFIG(cursor)
680 waylandWindow()->restoreMouseCursor(inputDevice);
681#endif
682 startMove(inputDevice, b);
683 }
684}
685
686void QWaylandAdwaitaDecoration::processMouseBottom(QWaylandInputDevice *inputDevice, const QPointF &local,
687 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
688{
689 Q_UNUSED(mods)
690 if (local.x() <= margins().left()) {
691 // bottom left bit
692#if QT_CONFIG(cursor)
693 waylandWindow()->applyCursor(inputDevice, Qt::SizeBDiagCursor);
694#endif
695 startResize(inputDevice, Qt::BottomEdge | Qt::LeftEdge, b);
696 } else if (local.x() > window()->width() + margins().right()) {
697 // bottom right bit
698#if QT_CONFIG(cursor)
699 waylandWindow()->applyCursor(inputDevice, Qt::SizeFDiagCursor);
700#endif
701 startResize(inputDevice, Qt::BottomEdge | Qt::RightEdge, b);
702 } else {
703 // bottom bit
704#if QT_CONFIG(cursor)
705 waylandWindow()->applyCursor(inputDevice, Qt::SizeVerCursor);
706#endif
707 startResize(inputDevice, Qt::BottomEdge, b);
708 }
709}
710
711void QWaylandAdwaitaDecoration::processMouseLeft(QWaylandInputDevice *inputDevice, const QPointF &local,
712 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
713{
714 Q_UNUSED(local)
715 Q_UNUSED(mods)
716#if QT_CONFIG(cursor)
717 waylandWindow()->applyCursor(inputDevice, Qt::SizeHorCursor);
718#endif
719 startResize(inputDevice, Qt::LeftEdge, b);
720}
721
722void QWaylandAdwaitaDecoration::processMouseRight(QWaylandInputDevice *inputDevice, const QPointF &local,
723 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
724{
725 Q_UNUSED(local)
726 Q_UNUSED(mods)
727#if QT_CONFIG(cursor)
728 waylandWindow()->applyCursor(inputDevice, Qt::SizeHorCursor);
729#endif
730 startResize(inputDevice, Qt::RightEdge, b);
731}
732
733void QWaylandAdwaitaDecoration::requestRepaint() const
734{
735 // Set dirty flag
736 if (waylandWindow()->decoration())
737 waylandWindow()->decoration()->update();
738
739 // Request re-paint
740 waylandWindow()->window()->requestUpdate();
741}
742
743} // namespace QtWaylandClient
744
745QT_END_NAMESPACE
746
747#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
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)