Modernize shapedclock example

Relying on the hard clipping of QRegion widget masks to create
non-rectangular windows is a solution from a bygone era. The result
looks horrible with today's eyes, particularly on a high-dpi
screen. Update the example to create smooth anti-aliased edges using
translucent window bacground.

Task-number: QTBUG-64229
Change-Id: I8859d61177d2a2dc446632c23f27f42050e0d7c7
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Reviewed-by: Andy Shaw <andy.shaw@qt.io>
This commit is contained in:
Eirik Aavitsland 2019-12-09 12:07:00 +01:00
parent 03dc30acca
commit 191ac31598
2 changed files with 41 additions and 18 deletions

View File

@ -29,16 +29,18 @@
\example widgets/shapedclock \example widgets/shapedclock
\title Shaped Clock Example \title Shaped Clock Example
\ingroup examples-widgets \ingroup examples-widgets
\brief The Shaped Clock example shows how to apply a widget mask to a top-level \brief The Shaped Clock example shows how to apply a translucent background
widget to produce a shaped window. and a widget mask to a top-level widget to produce a shaped window.
\borderedimage shapedclock-example.png \borderedimage shapedclock-example.png
Widget masks are used to customize the shapes of top-level widgets by restricting Widget masks are used to customize the shapes of top-level widgets by
the available area for painting. On some window systems, setting certain window flags restricting the area available for painting and mouse input. Using a
will cause the window decoration (title bar, window frame, buttons) to be disabled, translucent background facilitates partially transparent windows and smooth
allowing specially-shaped windows to be created. In this example, we use this feature edges. On most window systems, setting certain window flags will cause the
to create a circular window containing an analog clock. window decoration (title bar, window frame, buttons) to be disabled,
allowing specially-shaped windows to be created. In this example, we use
this feature to create a circular window containing an analog clock.
Since this example's window does not provide a \uicontrol File menu or a close Since this example's window does not provide a \uicontrol File menu or a close
button, we provide a context menu with an \uicontrol Exit entry so that the example button, we provide a context menu with an \uicontrol Exit entry so that the example
@ -52,8 +54,10 @@
\snippet widgets/shapedclock/shapedclock.h 0 \snippet widgets/shapedclock/shapedclock.h 0
The \l{QWidget::paintEvent()}{paintEvent()} implementation is the same as that found The \l{QWidget::paintEvent()}{paintEvent()} implementation is the same as
in the \c AnalogClock class. We implement \l{QWidget::sizeHint()}{sizeHint()} that found in the \c AnalogClock class, with one important exception: we
now must also draw background (the clock face) ourselves, since the widget
background is just transparent. We implement \l{QWidget::sizeHint()}{sizeHint()}
so that we don't have to resize the widget explicitly. We also provide an event so that we don't have to resize the widget explicitly. We also provide an event
handler for resize events. This allows us to update the mask if the clock is resized. handler for resize events. This allows us to update the mask if the clock is resized.
@ -70,9 +74,11 @@
\snippet widgets/shapedclock/shapedclock.cpp 0 \snippet widgets/shapedclock/shapedclock.cpp 0
We inform the window manager that the widget is not to be decorated with a window We request a transparent window by setting the Qt::WA_TranslucentBackground
frame by setting the Qt::FramelessWindowHint flag on the widget. As a result, we need widget attribute. We inform the window manager that the widget is not to be
to provide a way for the user to move the clock around the screen. decorated with a window frame by setting the Qt::FramelessWindowHint flag
on the widget. As a result, we need to provide a way for the user to move
the clock around the screen.
Mouse button events are delivered to the \c mousePressEvent() handler: Mouse button events are delivered to the \c mousePressEvent() handler:
@ -94,14 +100,20 @@
widget is moved to the point given by subtracting the \c dragPosition from the current widget is moved to the point given by subtracting the \c dragPosition from the current
cursor position in global coordinates. If we drag the widget, we also accept the event. cursor position in global coordinates. If we drag the widget, we also accept the event.
The \c paintEvent() function is given for completeness. See the The \c paintEvent() function is mainly the same as described in the
\l{Analog Clock Example}{Analog Clock} example for a description of the process used \l{Analog Clock Example}{Analog Clock} example. The one addition is that we
to render the clock. use QPainter::drawEllipse() to draw a round clock face with the current
palette's default background color. We make the clock face a bit smaller
than the widget mask, so that the anti-aliased, semi-transparent pixels on
the edge are not clipped away by the widget mask. This gives the shaped
window smooth edges on the screen.
\snippet widgets/shapedclock/shapedclock.cpp 3 \snippet widgets/shapedclock/shapedclock.cpp 3
In the \c resizeEvent() handler, we re-use some of the code from the \c paintEvent() In the \c resizeEvent() handler, we re-use some of the code from the \c
to determine the region of the widget that is visible to the user: paintEvent() to determine the region of the widget that is visible to the
user. This tells the system the area where mouse clicks should go to us,
and not to whatever window is behind us:
\snippet widgets/shapedclock/shapedclock.cpp 4 \snippet widgets/shapedclock/shapedclock.cpp 4
@ -121,6 +133,12 @@
\section1 Notes on Widget Masks \section1 Notes on Widget Masks
Widget masks are used to hint to the window system that the application
does not want mouse events for areas outside the mask. On most systems,
they also result in coarse visual clipping. To get smooth window edges, one
should use translucent background and anti-aliased painting, as shown in
this example.
Since QRegion allows arbitrarily complex regions to be created, widget masks can be Since QRegion allows arbitrarily complex regions to be created, widget masks can be
made to suit the most unconventionally-shaped windows, and even allow widgets to be made to suit the most unconventionally-shaped windows, and even allow widgets to be
displayed with holes in them. displayed with holes in them.

View File

@ -61,6 +61,7 @@
ShapedClock::ShapedClock(QWidget *parent) ShapedClock::ShapedClock(QWidget *parent)
: QWidget(parent, Qt::FramelessWindowHint | Qt::WindowSystemMenuHint) : QWidget(parent, Qt::FramelessWindowHint | Qt::WindowSystemMenuHint)
{ {
setAttribute(Qt::WA_TranslucentBackground);
QTimer *timer = new QTimer(this); QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, QOverload<>::of(&ShapedClock::update)); connect(timer, &QTimer::timeout, this, QOverload<>::of(&ShapedClock::update));
timer->start(1000); timer->start(1000);
@ -122,6 +123,10 @@ void ShapedClock::paintEvent(QPaintEvent *)
painter.translate(width() / 2, height() / 2); painter.translate(width() / 2, height() / 2);
painter.scale(side / 200.0, side / 200.0); painter.scale(side / 200.0, side / 200.0);
painter.setPen(Qt::NoPen);
painter.setBrush(palette().window());
painter.drawEllipse(QPoint(0, 0), 98, 98);
painter.setPen(Qt::NoPen); painter.setPen(Qt::NoPen);
painter.setBrush(hourColor); painter.setBrush(hourColor);
@ -168,6 +173,6 @@ void ShapedClock::resizeEvent(QResizeEvent * /* event */)
//! [5] //! [5]
QSize ShapedClock::sizeHint() const QSize ShapedClock::sizeHint() const
{ {
return QSize(100, 100); return QSize(200, 200);
} }
//! [5] //! [5]