QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskArcMetrics.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskArcMetrics.h"
7#include "QskFunctions.h"
8
9#include <qhashfunctions.h>
10#include <qpainterpath.h>
11#include <qvariant.h>
12
13static void qskRegisterArcMetrics()
14{
15 qRegisterMetaType< QskArcMetrics >();
16
17#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
18 QMetaType::registerEqualsComparator< QskArcMetrics >();
19#endif
20}
21
22Q_CONSTRUCTOR_FUNCTION( qskRegisterArcMetrics )
23
24static inline QPainterPath qskRadialPathPath(
25 const QRectF& rect, qreal startAngle, qreal spanAngle, qreal width )
26{
27 const auto sz = qMin( rect.width(), rect.height() );
28
29 const auto tx = width * rect.width() / sz;
30 const auto ty = width * rect.height() / sz;
31
32 const auto innerRect = rect.adjusted( tx, ty, -tx, -ty );
33
34 QPainterPath path;
35
36 if ( innerRect.isEmpty() )
37 {
38 if ( qAbs( spanAngle ) >= 360.0 )
39 {
40 path.addEllipse( rect );
41 }
42 else
43 {
44 // pie
45 path.arcMoveTo( rect, startAngle );
46 path.arcTo( rect, startAngle, spanAngle );
47 path.lineTo( rect.center() );
48 path.closeSubpath();
49 }
50 }
51 else
52 {
53 if ( qAbs( spanAngle ) >= 360.0 )
54 {
55 path.addEllipse( rect );
56
57 QPainterPath innerPath;
58 innerPath.addEllipse( innerRect );
59 path -= innerPath;
60 }
61 else
62 {
63 /*
64 We need the end point of the inner arc to add the line that connects
65 the inner/outer arcs. As QPainterPath does not offer such a method
66 we insert a dummy arcMoveTo and grab the calculated position.
67 */
68 path.arcMoveTo( innerRect, startAngle + spanAngle );
69 const auto pos = path.currentPosition();
70
71 path.arcMoveTo( rect, startAngle ); // replaces the dummy arcMoveTo above
72 path.arcTo( rect, startAngle, spanAngle );
73
74 path.lineTo( pos );
75 path.arcTo( innerRect, startAngle + spanAngle, -spanAngle );
76
77 path.closeSubpath();
78 }
79 }
80
81 return path;
82}
83
84static inline QPainterPath qskOrthogonalPath(
85 const QRectF& rect, qreal startAngle, qreal spanAngle, qreal width )
86{
87 const auto t2 = 0.5 * width;
88 const auto r = rect.adjusted( t2, t2, -t2, -t2 );
89
90 QPainterPath arcPath;
91 arcPath.arcMoveTo( r, startAngle );
92 arcPath.arcTo( r, startAngle, spanAngle );
93
94 QPainterPathStroker stroker;
95 stroker.setCapStyle( Qt::FlatCap );
96 stroker.setWidth( width );
97
98 return stroker.createStroke( arcPath );
99}
100
101static inline qreal qskInterpolated( qreal from, qreal to, qreal ratio )
102{
103 return from + ( to - from ) * ratio;
104}
105
106static inline qreal qskEffectiveThickness( qreal radius, qreal percentage )
107{
108 return qMax( radius, 0.0 ) * qBound( 0.0, percentage, 100.0 ) / 100.0;
109}
110
111void QskArcMetrics::setThickness( qreal thickness ) noexcept
112{
113 m_thickness = thickness;
114}
115
116void QskArcMetrics::setStartAngle( qreal startAngle ) noexcept
117{
118 m_startAngle = qskConstrainedDegrees( startAngle );
119}
120
121void QskArcMetrics::setSpanAngle( qreal spanAngle ) noexcept
122{
123 m_spanAngle = qBound( -360.0, spanAngle, 360.0 );
124}
125
126void QskArcMetrics::setSizeMode( Qt::SizeMode sizeMode ) noexcept
127{
128 m_relativeSize = ( sizeMode == Qt::RelativeSize );
129}
130
131bool QskArcMetrics::isClosed() const
132{
133 return qAbs( m_spanAngle ) >= 360.0;
134}
135
136bool QskArcMetrics::containsAngle( qreal angle ) const
137{
138 angle = qskConstrainedDegrees( angle );
139
140 if ( m_spanAngle >= 0.0 )
141 {
142 if ( angle < m_startAngle )
143 angle += 360.0;
144
145 return ( angle >= m_startAngle ) && ( angle <= m_startAngle + m_spanAngle );
146 }
147 else
148 {
149 if ( angle > m_startAngle )
150 angle -= 360.0;
151
152 return ( angle <= m_startAngle ) && ( angle >= m_startAngle + m_spanAngle );
153 }
154}
155
156QskArcMetrics QskArcMetrics::interpolated(
157 const QskArcMetrics& to, qreal ratio ) const noexcept
158{
159 if ( ( *this == to ) || ( m_relativeSize != to.m_relativeSize ) )
160 return to;
161
162 const qreal thickness = qskInterpolated( m_thickness, to.m_thickness, ratio );
163
164 const qreal s1 = qskInterpolated( m_startAngle, to.m_startAngle, ratio );
165 const qreal s2 = qskInterpolated( endAngle(), to.endAngle(), ratio );
166
167 return QskArcMetrics( s1, s2 - s1, thickness, sizeMode() );
168}
169
170QVariant QskArcMetrics::interpolate(
171 const QskArcMetrics& from, const QskArcMetrics& to,
172 qreal progress )
173{
174 return QVariant::fromValue( from.interpolated( to, progress ) );
175}
176
177QskArcMetrics QskArcMetrics::toAbsolute( const QSizeF& size ) const noexcept
178{
179 return toAbsolute( 0.5 * size.width(), 0.5 * size.height() );
180}
181
182QskArcMetrics QskArcMetrics::toAbsolute( qreal radiusX, qreal radiusY ) const noexcept
183{
184 if ( radiusX < 0.0 )
185 return toAbsolute( radiusY );
186
187 if ( radiusY < 0.0 )
188 return toAbsolute( radiusX );
189
190 return toAbsolute( qMin( radiusX, radiusY ) );
191}
192
193QskArcMetrics QskArcMetrics::toAbsolute( qreal radius ) const noexcept
194{
195 if ( !m_relativeSize )
196 return *this;
197
198 const qreal t = qskEffectiveThickness( radius, m_thickness );
199 return QskArcMetrics( m_startAngle, m_spanAngle, t, Qt::AbsoluteSize );
200}
201
202QPainterPath QskArcMetrics::painterPath( const QRectF& rect, bool radial ) const
203{
204 QPainterPath path;
205
206 if ( !qFuzzyIsNull( m_spanAngle ) )
207 {
208 qreal t = m_thickness;
209
210 if ( m_relativeSize )
211 {
212 const auto sz = qMin( rect.width(), rect.height() );
213 t = qskEffectiveThickness( 0.5 * sz, t );
214 }
215
216 if ( t > 0.0 )
217 {
218 if ( radial )
219 path = qskRadialPathPath( rect, m_startAngle, m_spanAngle, t );
220 else
221 path = qskOrthogonalPath( rect, m_startAngle, m_spanAngle, t );
222 }
223 }
224
225 return path;
226}
227
228QRectF QskArcMetrics::boundingRect( const QRectF& ellipseRect ) const
229{
230 if ( qFuzzyIsNull( m_spanAngle ) )
231 return QRectF( ellipseRect.center(), QSizeF() );
232
233 if ( qAbs( m_spanAngle ) >= 360.0 )
234 return ellipseRect;
235
236 return painterPath( ellipseRect ).controlPointRect();
237}
238
239QSizeF QskArcMetrics::boundingSize( const QSizeF& ellipseSize ) const
240{
241 if ( qFuzzyIsNull( m_spanAngle ) )
242 return QSizeF();
243
244 if ( qAbs( m_spanAngle ) >= 360.0 )
245 return ellipseSize;
246
247 const QRectF r( 0.0, 0.0, ellipseSize.width(), ellipseSize.height() );
248 return painterPath( r ).controlPointRect().size();
249}
250
251QskHashValue QskArcMetrics::hash( QskHashValue seed ) const noexcept
252{
253 auto hash = qHash( m_thickness, seed );
254 hash = qHash( m_startAngle, hash );
255 hash = qHash( m_spanAngle, hash );
256
257 return qHash( m_relativeSize, hash );
258}
259
260#ifndef QT_NO_DEBUG_STREAM
261
262#include <qdebug.h>
263
264QDebug operator<<( QDebug debug, const QskArcMetrics& metrics )
265{
266 QDebugStateSaver saver( debug );
267 debug.nospace();
268
269 debug << "QskArcMetrics" << '(';
270 debug << metrics.thickness() << ',' << metrics.sizeMode();
271 debug << ",[" << metrics.startAngle() << ',' << metrics.spanAngle() << ']';
272 debug << ')';
273
274 return debug;
275}
276
277#endif
278
279#include "moc_QskArcMetrics.cpp"