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