Perceptual Color

abstractdiagram.cpp
1// SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR MIT
3
4// Own headers
5// First the interface, which forces the header to be self-contained.
6#include "abstractdiagram.h"
7// Second, the private implementation.
8#include "abstractdiagram_p.h" // IWYU pragma: associated
9
10#include "helper.h"
11#include <qcolor.h>
12#include <qglobal.h>
13#include <qimage.h>
14#include <qnamespace.h>
15#include <qpalette.h>
16#include <qsize.h>
17#include <qstyle.h>
18#include <qstyleoption.h>
19#include <qwidget.h>
20class QHideEvent;
21class QShowEvent;
22
23namespace PerceptualColor
24{
25/** @brief The constructor.
26 * @param parent The widget’s parent widget. This parameter will be passed
27 * to the base class’s constructor. */
29 : QWidget(parent)
30 , d_pointer(new AbstractDiagramPrivate())
31{
32}
33
34/** @brief Destructor */
38
39/** @brief The color for painting focus indicators
40 * @returns The color for painting focus indicators. This color is based on
41 * the current widget style at the moment this function is called. The value
42 * might therefore be different on the next function call, if the widget style
43 * has been switched by the user in the meantime.
44 * @note As there is no build-in support in Qt to get this information, we
45 * have to do some best guess, which might go wrong on some styles. */
47{
48 return palette().color(QPalette::ColorGroup::Active, QPalette::ColorRole::Highlight);
49}
50
51/** @brief The rounded size of the widget measured in
52 * <em>physical pixels</em>.
53 *
54 * @returns The rounded size of this widget,
55 * measured in <em>physical pixels</em>, based on
56 * <tt>QPaintDevice::devicePixelRatioF()</tt>. This is the recommended
57 * image size for calling <tt>QPainter::drawImage()</tt> during a paint event.
58 * Both, width and height are guaranteed to be ≥ 0.
59 *
60 * Example: You want to prepare a <tt>QImage</tt> of the hole widget to be
61 * used in <tt>QWidget::paintEvent()</tt>. To make sure a crisp rendering,
62 * you have to
63 *
64 * - Prepare an image with the size that this function returns.
65 * - Set <tt>QImage::setDevicePixelRatio()</tt> of the image to the same
66 * value as <tt>QPaintDevice::devicePixelRatioF()</tt> of the widget.
67 * - Actually paint the image on the widget at position <tt>(0, 0)</tt>
68 * <em>without</em> anti-aliasing.
69 *
70 * @note If <tt>QPaintDevice::devicePixelRatioF()</tt> is not an integer,
71 * the result of this function is rounded down. Qt’s widget geometry code
72 * has no documentation about how this is handled. However, Qt seems to
73 * round up starting with 0.5, at least on Linux/X11. But there are a few
74 * themes (for example the “Kvantum style engine” with the style
75 * “MildGradientKvantum”) that seem to round down: This becomes visible, as
76 * the corresponding last physical pixels are not automatically redrawn before
77 * executing the <tt>paintEvent()</tt> code. To avoid relying on undocumented
78 * behaviour and to avoid known problems with some styles, this function
79 * is conservative and always rounds down. */
81{
82 // Assert that static_cast<int> always rounds down.
83 static_assert(static_cast<int>(1.9) == 1);
84 static_assert(static_cast<int>(1.5) == 1);
85 static_assert(static_cast<int>(1.0) == 1);
86 // Multiply the size with the (floating point) scale factor
87 // and than round down (by using static_cast<int>).
88 const int width = static_cast<int>(size().width() * devicePixelRatioF());
89 const int height = static_cast<int>(size().height() * devicePixelRatioF());
90 return QSize(qMax(width, 0), qMax(height, 0));
91}
92
93/** @brief The maximum possible size of a square within the widget, measured
94 * in <em>physical pixels</em>.
95 *
96 * This is the shorter value of width and height of the widget.
97 *
98 * @returns The maximum possible size of a square within the widget, measured
99 * in <em>physical pixels</em>. Both, width and height are guaranteed
100 * to be ≥ 0.
101 *
102 * @sa @ref maximumWidgetSquareSize */
107
108/** @brief The maximum possible size of a square within the widget, measured
109 * in <em>device-independent pixels</em>.
110 *
111 * This is the conversion of @ref maximumPhysicalSquareSize to the unit
112 * <em>device-independent pixels</em>. It might be <em>smaller</em> than
113 * the shortest value of <tt>QWidget::width()</tt> and
114 * <tt>QWidget::height()</tt> because @ref maximumPhysicalSquareSize
115 * might have rounded down.
116 *
117 * @returns The maximum possible size of a square within the widget, measured
118 * in <em>device-independent pixels</em>. */
123
124/** @brief Background for semi-transparent colors.
125 *
126 * When showing a semi-transparent color, there has to be a background
127 * on which it is shown. This function provides a suitable background
128 * for showcasing a color.
129 *
130 * Example code (to use within a class that inherits from
131 * @ref PerceptualColor::AbstractDiagram):
132 * @snippet testabstractdiagram.cpp useTransparencyBackground
133 *
134 * @returns An image of a mosaic of neutral gray rectangles of different
135 * lightness. You can use this as tiles to paint a background.
136 *
137 * @note The image is considering QWidget::devicePixelRatioF() to deliver
138 * crisp (correctly scaled) images also for high-DPI devices.
139 * The painting does not use floating point drawing, but rounds
140 * to full integers. Therefore, the result is always a sharp image.
141 * This function takes care that each square has the same physical pixel
142 * size, without scaling errors or anti-aliasing errors.
143 *
144 * @internal
145 * @sa @ref transparencyBackground(qreal devicePixelRatioF)
146 * @endinternal */
148{
149 return PerceptualColor::transparencyBackground(devicePixelRatioF());
150}
151
152/** @brief The outline thickness of a handle.
153 *
154 * @returns The outline thickness of a (either circular or linear) handle.
155 * Measured in <em>device-independent pixels</em>. */
157{
158 /** @note The return value is constant. For a given object instance, this
159 * function returns the same value every time it is called. This constant
160 * value may be different for different instances of the object. */
161 return 2;
162}
163
164/** @brief The radius of a circular handle.
165 * @returns The radius of a circular handle, measured in
166 * <em>device-independent pixels</em>. */
168{
169 /** @note The return value is constant. For a given object instance, this
170 * function returns the same value every time it is called. This constant
171 * value may be different for different instances of the object. */
172 return handleOutlineThickness() * 2.5;
173}
174
175/** @brief The thickness of a color gradient.
176 *
177 * This is the thickness of a one-dimensional gradient, for example in
178 * a slider or a color wheel.
179 *
180 * @returns The thickness of a slider or a color wheel, measured in
181 * <em>device-independent pixels</em>.
182 *
183 * @sa @ref gradientMinimumLength() */
185{
187 int result = 0;
188 QStyleOptionSlider styleOption;
189 styleOption.initFrom(this); // Sets also QStyle::State_MouseOver
190 styleOption.orientation = Qt::Horizontal;
191 result = qMax(result, style()->pixelMetric(QStyle::PM_SliderThickness, &styleOption, this));
192 styleOption.orientation = Qt::Vertical;
193 result = qMax(result, style()->pixelMetric(QStyle::PM_SliderThickness, &styleOption, this));
194 result = qMax(result, qRound(handleRadius()));
195 // No supplementary space for ticks is added.
196 return result;
197}
198
199/** @brief The minimum length of a color gradient.
200 *
201 * This is the minimum length of a one-dimensional gradient, for example in
202 * a slider or a color wheel. This is also the minimum width and minimum
203 * height of two-dimensional gradients.
204 *
205 * @returns The length of a gradient, measured in
206 * <em>device-independent pixels</em>.
207 *
208 * @sa @ref gradientThickness() */
210{
212 QStyleOptionSlider option;
213 option.initFrom(this);
214 return qMax(
215 // Parameter: style-based value:
216 qMax(
217 // Similar to QSlider sizeHint():
218 84,
219 // Similar to QSlider::minimumSizeHint():
220 style()->pixelMetric(QStyle::PM_SliderLength, &option, this)),
221 // Parameter:
223}
224
225/** @brief The empty space around diagrams reserved for the focus indicator.
226 *
227 * Measured in <em>device-independent pixels</em>.
228 *
229 * @returns The empty space around diagrams reserved for the focus
230 * indicator. */
232{
233 // 1 × handleOutlineThickness() for the focus indicator itself.
234 // 2 × handleOutlineThickness() for the space between the focus indicator
235 // and the diagram.
236 return 3 * handleOutlineThickness();
237}
238
239/** @brief An appropriate color for a handle, depending on the background
240 * lightness.
241 * @param lightness The background lightness. Valid range: <tt>[0, 100]</tt>.
242 * @returns An appropriate color for a handle. This color will provide
243 * contrast to the background. */
245{
246 if (lightness >= 50) {
247 return Qt::black;
248 }
249 return Qt::white;
250}
251
252/** @brief If this widget is actually visible.
253 *
254 * Unlike <tt>QWidget::isVisible</tt>, minimized windows are <em>not</em>
255 * considered visible.
256 *
257 * Changes can be observed with
258 * @ref AbstractDiagram::actualVisibilityToggledEvent.
259 *
260 * @returns If this widget is actually visible.
261 *
262 * @internal
263 *
264 * This information is based on the last @ref AbstractDiagram::showEvent
265 * or @ref AbstractDiagram::hideEvent that was received. */
267{
268 return d_pointer->m_isActuallyVisible;
269}
270
271/** @brief Event occurring after @ref isActuallyVisible has been toggled.
272 *
273 * This function is called if and only if @ref isActuallyVisible has
274 * actually been toggled. */
278
279/** @brief React on a show event.
280 *
281 * Reimplemented from base class.
282 *
283 * @param event The show event.
284 *
285 * @internal
286 *
287 * @sa @ref AbstractDiagram::isActuallyVisible */
289{
291 if (d_pointer->m_isActuallyVisible == false) {
292 d_pointer->m_isActuallyVisible = true;
294 }
295}
296
297/** @brief React on a hide event.
298 *
299 * Reimplemented from base class.
300 *
301 * @param event The hide event.
302 *
303 * @internal
304 *
305 * @sa @ref AbstractDiagram::isActuallyVisible */
307{
309 if (d_pointer->m_isActuallyVisible == true) {
310 d_pointer->m_isActuallyVisible = false;
312 }
313}
314
315/** @brief An alternative to QWidget::update(). It’s a workaround
316 * that avoids trouble with overload resolution.
317 *
318 * Connecting a signal to the slot <tt>
319 * <a href="https://doc.qt.io/qt-6/qwidget.html#update">QWidget::update()</a>
320 * </tt> is surprisingly difficult, at least if you want to use the functor
321 * syntax (which provides compile-time checks) for the connection. A simple
322 * connection fails to compile because it fails to do a correct overload
323 * resolution, as there is more than one slot called <tt>update</tt>. Now, <tt>
324 * <a href="https://doc.qt.io/qt-6/qtglobal.html#qOverload">qOverload&lt;&gt;()
325 * </a></tt> can be used to choose the correct overload, but in this special
326 * case, <tt><a href="https://doc.qt.io/qt-6/qtglobal.html#qOverload">
327 * qOverload&lt;&gt;()</a></tt> generates compiler warnings.
328 *
329 * Instead of connecting to <tt>
330 * <a href="https://doc.qt.io/qt-6/qwidget.html#update">QWidget::update()
331 * </a></tt> directly, simply connect to this slot instead. It calls
332 * the actual <tt><a href="https://doc.qt.io/qt-6/qwidget.html#update">
333 * QWidget::update()</a></tt>, but avoids the annoyance with the overload
334 * resolution */
336{
337 update();
338}
339
340} // namespace PerceptualColor
bool isActuallyVisible() const
If this widget is actually visible.
Q_INVOKABLE AbstractDiagram(QWidget *parent=nullptr)
The constructor.
qreal handleRadius() const
The radius of a circular handle.
virtual ~AbstractDiagram() noexcept override
Default destructor.
QSize physicalPixelSize() const
The rounded size of the widget measured in physical pixels.
QColor handleColorFromBackgroundLightness(qreal lightness) const
An appropriate color for a handle, depending on the background lightness.
int gradientMinimumLength() const
The minimum length of a color gradient.
int spaceForFocusIndicator() const
The empty space around diagrams reserved for the focus indicator.
void callUpdate()
An alternative to QWidget::update().
int handleOutlineThickness() const
The outline thickness of a handle.
int gradientThickness() const
The thickness of a color gradient.
virtual void actualVisibilityToggledEvent()
Event occurring after isActuallyVisible has been toggled.
QColor focusIndicatorColor() const
The color for painting focus indicators.
virtual void showEvent(QShowEvent *event) override
React on a show event.
QImage transparencyBackground() const
Background for semi-transparent colors.
int maximumPhysicalSquareSize() const
The maximum possible size of a square within the widget, measured in physical pixels.
virtual void hideEvent(QHideEvent *event) override
React on a hide event.
qreal maximumWidgetSquareSize() const
The maximum possible size of a square within the widget, measured in device-independent pixels.
The namespace of this library.
qreal devicePixelRatioF() const const
PM_SliderThickness
void initFrom(const QWidget *widget)
Horizontal
void ensurePolished() const const
virtual bool event(QEvent *event) override
virtual void hideEvent(QHideEvent *event)
virtual void showEvent(QShowEvent *event)
QStyle * style() const const
void update()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:36 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.