1// Copyright (C) 2016 Robin Burchell <robin.burchell@viroteck.net>
2// Copyright (C) 2016 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
5#include <QtGui/QCursor>
6#include <QtGui/QPainter>
7#include <QtGui/QPainterPath>
8#include <QtGui/QPalette>
9#include <QtGui/QLinearGradient>
10#include <QtGui/QPainterPath>
11
12#include <qpa/qwindowsysteminterface.h>
13
14#include <QtWaylandClient/private/qwaylanddecorationplugin_p.h>
15#include <QtWaylandClient/private/qwaylandabstractdecoration_p.h>
16#include <QtWaylandClient/private/qwaylandwindow_p.h>
17#include <QtWaylandClient/private/qwaylandshellsurface_p.h>
18
19QT_BEGIN_NAMESPACE
20
21namespace QtWaylandClient {
22
23#define BUTTON_SPACING 5
24#define BUTTON_WIDTH 18
25#define BUTTONS_RIGHT_MARGIN 8
26
27enum Button
28{
29 None,
30 Close,
31 Maximize,
32 Minimize
33};
34
35class Q_WAYLANDCLIENT_EXPORT QWaylandBradientDecoration : public QWaylandAbstractDecoration
36{
37public:
38 QWaylandBradientDecoration();
39protected:
40 QMargins margins(MarginsType marginsType = Full) const override;
41 void paint(QPaintDevice *device) override;
42 bool handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global,Qt::MouseButtons b,Qt::KeyboardModifiers mods) override;
43 bool handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, QEventPoint::State state, Qt::KeyboardModifiers mods) override;
44private:
45 enum class PointerType {
46 Mouse,
47 Touch
48 };
49
50 void processPointerTop(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods, PointerType type);
51 void processPointerBottom(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods, PointerType type);
52 void processPointerLeft(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods, PointerType type);
53 void processPointerRight(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods, PointerType type);
54 bool clickButton(Qt::MouseButtons b, Button btn);
55
56 QRectF closeButtonRect() const;
57 QRectF maximizeButtonRect() const;
58 QRectF minimizeButtonRect() const;
59
60 QColor m_foregroundColor;
61 QColor m_foregroundInactiveColor;
62 QColor m_backgroundColor;
63 QStaticText m_windowTitle;
64 Button m_clicking = None;
65};
66
67
68
69QWaylandBradientDecoration::QWaylandBradientDecoration()
70{
71 QPalette palette;
72 m_foregroundColor = palette.color(cg: QPalette::Active, cr: QPalette::WindowText);
73 m_backgroundColor = palette.color(cg: QPalette::Active, cr: QPalette::Window);
74 m_foregroundInactiveColor = palette.color(cg: QPalette::Disabled, cr: QPalette::WindowText);
75
76 QTextOption option(Qt::AlignHCenter | Qt::AlignVCenter);
77 option.setWrapMode(QTextOption::NoWrap);
78 m_windowTitle.setTextOption(option);
79}
80
81QRectF QWaylandBradientDecoration::closeButtonRect() const
82{
83 const int windowRight = waylandWindow()->windowContentGeometry().right() + 1;
84 return QRectF(windowRight - BUTTON_WIDTH - BUTTON_SPACING * 0 - BUTTONS_RIGHT_MARGIN,
85 (margins().top() - BUTTON_WIDTH) / 2, BUTTON_WIDTH, BUTTON_WIDTH);
86}
87
88QRectF QWaylandBradientDecoration::maximizeButtonRect() const
89{
90 const int windowRight = waylandWindow()->windowContentGeometry().right() + 1;
91 return QRectF(windowRight - BUTTON_WIDTH * 2 - BUTTON_SPACING * 1 - BUTTONS_RIGHT_MARGIN,
92 (margins().top() - BUTTON_WIDTH) / 2, BUTTON_WIDTH, BUTTON_WIDTH);
93}
94
95QRectF QWaylandBradientDecoration::minimizeButtonRect() const
96{
97 const int windowRight = waylandWindow()->windowContentGeometry().right() + 1;
98 return QRectF(windowRight - BUTTON_WIDTH * 3 - BUTTON_SPACING * 2 - BUTTONS_RIGHT_MARGIN,
99 (margins().top() - BUTTON_WIDTH) / 2, BUTTON_WIDTH, BUTTON_WIDTH);
100}
101
102QMargins QWaylandBradientDecoration::margins(MarginsType marginsType) const
103{
104 if (marginsType == ShadowsOnly)
105 return QMargins();
106
107 return QMargins(3, 30, 3, 3);
108}
109
110void QWaylandBradientDecoration::paint(QPaintDevice *device)
111{
112 bool active = window()->handle()->isActive();
113 QRect wg = waylandWindow()->windowContentGeometry();
114 QRect clips[] =
115 {
116 QRect(wg.left(), wg.top(), wg.width(), margins().top()),
117 QRect(wg.left(), (wg.bottom() + 1) - margins().bottom(), wg.width(), margins().bottom()),
118 QRect(wg.left(), margins().top(), margins().left(), wg.height() - margins().top() - margins().bottom()),
119 QRect((wg.right() + 1) - margins().right(), wg.top() + margins().top(), margins().right(), wg.height() - margins().top() - margins().bottom())
120 };
121
122 QRect top = clips[0];
123
124 QPainter p(device);
125 p.setRenderHint(hint: QPainter::Antialiasing);
126
127 // Title bar
128 QPainterPath roundedRect;
129 roundedRect.addRoundedRect(rect: wg, xRadius: 3, yRadius: 3);
130 for (int i = 0; i < 4; ++i) {
131 p.save();
132 p.setClipRect(clips[i]);
133 p.fillPath(path: roundedRect, brush: m_backgroundColor);
134 p.restore();
135 }
136
137 // Window icon
138 QIcon icon = waylandWindow()->windowIcon();
139 if (!icon.isNull()) {
140 QRectF iconRect(0, 0, 22, 22);
141 iconRect.adjust(xp1: margins().left() + BUTTON_SPACING, yp1: 4,
142 xp2: margins().left() + BUTTON_SPACING, yp2: 4),
143 icon.paint(painter: &p, rect: iconRect.toRect());
144 }
145
146 // Window title
147 QString windowTitleText = window()->title();
148 if (!windowTitleText.isEmpty()) {
149 if (m_windowTitle.text() != windowTitleText) {
150 m_windowTitle.setText(windowTitleText);
151 m_windowTitle.prepare();
152 }
153
154 QRect titleBar = top;
155 titleBar.setLeft(margins().left() + BUTTON_SPACING +
156 (icon.isNull() ? 0 : 22 + BUTTON_SPACING));
157 titleBar.setRight(minimizeButtonRect().left() - BUTTON_SPACING);
158
159 p.save();
160 p.setClipRect(titleBar);
161 p.setPen(active ? m_foregroundColor : m_foregroundInactiveColor);
162 QSizeF size = m_windowTitle.size();
163 int dx = (top.width() - size.width()) /2;
164 int dy = (top.height()- size.height()) /2;
165 QFont font = p.font();
166 font.setPixelSize(14);
167 p.setFont(font);
168 QPoint windowTitlePoint(top.topLeft().x() + dx,
169 top.topLeft().y() + dy);
170 p.drawStaticText(p: windowTitlePoint, staticText: m_windowTitle);
171 p.restore();
172 }
173
174 QRectF rect;
175
176 // Default pen
177 QPen pen(active ? m_foregroundColor : m_foregroundInactiveColor);
178 p.setPen(pen);
179
180 // Close button
181 p.save();
182 rect = closeButtonRect();
183 qreal crossSize = rect.height() / 2.3;
184 QPointF crossCenter(rect.center());
185 QRectF crossRect(crossCenter.x() - crossSize / 2, crossCenter.y() - crossSize / 2, crossSize, crossSize);
186 pen.setWidth(2);
187 p.setPen(pen);
188 p.drawLine(p1: crossRect.topLeft(), p2: crossRect.bottomRight());
189 p.drawLine(p1: crossRect.bottomLeft(), p2: crossRect.topRight());
190 p.restore();
191
192 // Maximize button
193 p.save();
194 p.setRenderHint(hint: QPainter::Antialiasing, on: false);
195 rect = maximizeButtonRect().adjusted(xp1: 4, yp1: 5, xp2: -4, yp2: -5);
196 if ((window()->windowStates() & Qt::WindowMaximized)) {
197 qreal inset = 2;
198 QRectF rect1 = rect.adjusted(xp1: inset, yp1: 0, xp2: 0, yp2: -inset);
199 QRectF rect2 = rect.adjusted(xp1: 0, yp1: inset, xp2: -inset, yp2: 0);
200 p.drawRect(rect: rect1);
201 p.setBrush(m_backgroundColor); // need to cover up some lines from the other rect
202 p.drawRect(rect: rect2);
203 } else {
204 p.drawRect(rect);
205 p.drawLine(x1: rect.left(), y1: rect.top() + 1, x2: rect.right(), y2: rect.top() + 1);
206 }
207 p.restore();
208
209 // Minimize button
210 p.save();
211 p.setRenderHint(hint: QPainter::Antialiasing, on: false);
212 rect = minimizeButtonRect().adjusted(xp1: 5, yp1: 5, xp2: -5, yp2: -5);
213 pen.setWidth(2);
214 p.setPen(pen);
215 p.drawLine(p1: rect.bottomLeft(), p2: rect.bottomRight());
216 p.restore();
217}
218
219bool QWaylandBradientDecoration::clickButton(Qt::MouseButtons b, Button btn)
220{
221 if (isLeftClicked(newMouseButtonState: b)) {
222 m_clicking = btn;
223 return false;
224 } else if (isLeftReleased(newMouseButtonState: b)) {
225 if (m_clicking == btn) {
226 m_clicking = None;
227 return true;
228 } else {
229 m_clicking = None;
230 }
231 }
232 return false;
233}
234
235bool QWaylandBradientDecoration::handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, Qt::MouseButtons b, Qt::KeyboardModifiers mods)
236
237{
238 Q_UNUSED(global);
239
240 // Figure out what area mouse is in
241 QRect wg = waylandWindow()->windowContentGeometry();
242 if (local.y() <= wg.top() + margins().top()) {
243 processPointerTop(inputDevice, local, b, mods, type: PointerType::Mouse);
244 } else if (local.y() > wg.bottom() - margins().bottom()) {
245 processPointerBottom(inputDevice, local, b, mods, type: PointerType::Mouse);
246 } else if (local.x() <= wg.left() + margins().left()) {
247 processPointerLeft(inputDevice, local, b, mods, type: PointerType::Mouse);
248 } else if (local.x() > wg.right() - margins().right()) {
249 processPointerRight(inputDevice, local, b, mods, type: PointerType::Mouse);
250 } else {
251#if QT_CONFIG(cursor)
252 waylandWindow()->restoreMouseCursor(device: inputDevice);
253#endif
254 setMouseButtons(b);
255 return false;
256 }
257
258 setMouseButtons(b);
259 return true;
260}
261
262bool QWaylandBradientDecoration::handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, QEventPoint::State state, Qt::KeyboardModifiers mods)
263{
264 Q_UNUSED(global);
265 QRect wg = waylandWindow()->windowContentGeometry();
266
267 bool handled = state == QEventPoint::Pressed;
268 if (handled) {
269 if (local.y() <= wg.top() + margins().top()) {
270 processPointerTop(inputDevice, local, b: Qt::LeftButton, mods, type: PointerType::Touch);
271 } else if (local.y() > wg.bottom() - margins().bottom()) {
272 processPointerBottom(inputDevice, local, b: Qt::LeftButton, mods, type: PointerType::Touch);
273 } else if (local.x() <= wg.left() + margins().left()) {
274 processPointerLeft(inputDevice, local, b: Qt::LeftButton, mods, type: PointerType::Touch);
275 } else if (local.x() > wg.right() - margins().right()) {
276 processPointerRight(inputDevice, local, b: Qt::LeftButton, mods, type: PointerType::Touch);
277 } else {
278 handled = false;
279 }
280 }
281
282 return handled;
283}
284
285void QWaylandBradientDecoration::processPointerTop(QWaylandInputDevice *inputDevice,
286 const QPointF &local,
287 Qt::MouseButtons b,
288 Qt::KeyboardModifiers mods,
289 PointerType type)
290{
291#if !QT_CONFIG(cursor)
292 Q_UNUSED(type);
293#endif
294
295 QRect wg = waylandWindow()->windowContentGeometry();
296 Q_UNUSED(mods);
297 if (local.y() <= wg.top() + margins().bottom()) {
298 if (local.x() <= margins().left()) {
299 //top left bit
300#if QT_CONFIG(cursor)
301 if (type == PointerType::Mouse)
302 waylandWindow()->setMouseCursor(device: inputDevice, cursor: Qt::SizeFDiagCursor);
303#endif
304 startResize(inputDevice, edges: Qt::TopEdge | Qt::LeftEdge, buttons: b);
305 } else if (local.x() > wg.right() - margins().right()) {
306 //top right bit
307#if QT_CONFIG(cursor)
308 if (type == PointerType::Mouse)
309 waylandWindow()->setMouseCursor(device: inputDevice, cursor: Qt::SizeBDiagCursor);
310#endif
311 startResize(inputDevice, edges: Qt::TopEdge | Qt::RightEdge, buttons: b);
312 } else {
313 //top resize bit
314#if QT_CONFIG(cursor)
315 if (type == PointerType::Mouse)
316 waylandWindow()->setMouseCursor(device: inputDevice, cursor: Qt::SplitVCursor);
317#endif
318 startResize(inputDevice, edges: Qt::TopEdge, buttons: b);
319 }
320 } else if (local.x() <= wg.left() + margins().left()) {
321 processPointerLeft(inputDevice, local, b, mods, type);
322 } else if (local.x() > wg.right() - margins().right()) {
323 processPointerRight(inputDevice, local, b, mods, type);
324 } else if (isRightClicked(newMouseButtonState: b)) {
325 showWindowMenu(inputDevice);
326 } else if (closeButtonRect().contains(p: local)) {
327 if (type == PointerType::Touch || clickButton(b, btn: Close))
328 QWindowSystemInterface::handleCloseEvent(window: window());
329 } else if (maximizeButtonRect().contains(p: local)) {
330 if (type == PointerType::Touch || clickButton(b, btn: Maximize))
331 window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized);
332 } else if (minimizeButtonRect().contains(p: local)) {
333 if (type == PointerType::Touch || clickButton(b, btn: Minimize))
334 window()->setWindowState(Qt::WindowMinimized);
335 } else {
336#if QT_CONFIG(cursor)
337 if (type == PointerType::Mouse)
338 waylandWindow()->restoreMouseCursor(device: inputDevice);
339#endif
340 startMove(inputDevice,buttons: b);
341 }
342}
343
344void QWaylandBradientDecoration::processPointerBottom(QWaylandInputDevice *inputDevice,
345 const QPointF &local,
346 Qt::MouseButtons b,
347 Qt::KeyboardModifiers mods,
348 PointerType type)
349{
350 Q_UNUSED(mods);
351#if !QT_CONFIG(cursor)
352 Q_UNUSED(type);
353#endif
354
355 if (local.x() <= margins().left()) {
356 //bottom left bit
357#if QT_CONFIG(cursor)
358 if (type == PointerType::Mouse)
359 waylandWindow()->setMouseCursor(device: inputDevice, cursor: Qt::SizeBDiagCursor);
360#endif
361 startResize(inputDevice, edges: Qt::BottomEdge | Qt::LeftEdge, buttons: b);
362 } else if (local.x() > window()->width() + margins().left()) {
363 //bottom right bit
364#if QT_CONFIG(cursor)
365 if (type == PointerType::Mouse)
366 waylandWindow()->setMouseCursor(device: inputDevice, cursor: Qt::SizeFDiagCursor);
367#endif
368 startResize(inputDevice, edges: Qt::BottomEdge | Qt::RightEdge, buttons: b);
369 } else {
370 //bottom bit
371#if QT_CONFIG(cursor)
372 if (type == PointerType::Mouse)
373 waylandWindow()->setMouseCursor(device: inputDevice, cursor: Qt::SplitVCursor);
374#endif
375 startResize(inputDevice, edges: Qt::BottomEdge, buttons: b);
376 }
377}
378
379void QWaylandBradientDecoration::processPointerLeft(QWaylandInputDevice *inputDevice,
380 const QPointF &local,
381 Qt::MouseButtons b,
382 Qt::KeyboardModifiers mods,
383 PointerType type)
384{
385 Q_UNUSED(local);
386 Q_UNUSED(mods);
387#if QT_CONFIG(cursor)
388 if (type == PointerType::Mouse)
389 waylandWindow()->setMouseCursor(device: inputDevice, cursor: Qt::SplitHCursor);
390#else
391 Q_UNUSED(type);
392#endif
393 startResize(inputDevice, edges: Qt::LeftEdge, buttons: b);
394}
395
396void QWaylandBradientDecoration::processPointerRight(QWaylandInputDevice *inputDevice,
397 const QPointF &local,
398 Qt::MouseButtons b,
399 Qt::KeyboardModifiers mods,
400 PointerType type)
401{
402 Q_UNUSED(local);
403 Q_UNUSED(mods);
404#if QT_CONFIG(cursor)
405 if (type == PointerType::Mouse)
406 waylandWindow()->setMouseCursor(device: inputDevice, cursor: Qt::SplitHCursor);
407#else
408 Q_UNUSED(type);
409#endif
410 startResize(inputDevice, edges: Qt::RightEdge, buttons: b);
411}
412
413class QWaylandBradientDecorationPlugin : public QWaylandDecorationPlugin
414{
415 Q_OBJECT
416 Q_PLUGIN_METADATA(IID QWaylandDecorationFactoryInterface_iid FILE "bradient.json")
417public:
418 QWaylandAbstractDecoration *create(const QString&, const QStringList&) override;
419};
420
421QWaylandAbstractDecoration *QWaylandBradientDecorationPlugin::create(const QString& system, const QStringList& paramList)
422{
423 Q_UNUSED(paramList);
424 Q_UNUSED(system);
425 return new QWaylandBradientDecoration();
426}
427
428}
429
430QT_END_NAMESPACE
431
432#include "main.moc"
433

source code of qtwayland/src/plugins/decorations/bradient/main.cpp