QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskHintAnimator.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskHintAnimator.h"
7#include "QskAnimationHint.h"
8#include "QskControl.h"
9#include "QskEvent.h"
10
11#include <qobject.h>
12#include <qthread.h>
13
14#include <algorithm>
15#include <vector>
16
17#define ALIGN_VALUES 0
18
19#if ALIGN_VALUES
20
21static inline qreal qskAligned05( qreal value )
22{
23 // aligned to 0.5
24 return qRound( 2.0 * value ) / 2.0;
25}
26
27static inline QSizeF qskAligned05( const QSizeF& size )
28{
29 return QSizeF( qskAligned05( size.width() ), qskAligned05( size.height() ) );
30}
31
32static inline QskMargins qskAligned05( const QskMargins& margins )
33{
34 const qreal left = qskAligned05( margins.left() );
35 const qreal top = qskAligned05( margins.top() );
36 const qreal right = qskAligned05( margins.right() );
37 const qreal bottom = qskAligned05( margins.bottom() );
38
39 return QskMargins( left, top, right, bottom );
40}
41
42static inline QVariant qskAligned05( const QVariant& value )
43{
44 if ( value.canConvert< QskBoxBorderMetrics >() )
45 {
46 auto metrics = value.value< QskBoxBorderMetrics >();
47
48 if ( metrics.sizeMode() == Qt::AbsoluteSize )
49 {
50 metrics.setWidths( qskAligned05( metrics.widths() ) );
51 return QVariant::fromValue( metrics );
52 }
53 }
54 else if ( value.canConvert< QskBoxShapeMetrics >() )
55 {
56 auto metrics = value.value< QskBoxShapeMetrics >();
57 if ( metrics.sizeMode() == Qt::AbsoluteSize )
58 {
59 for ( int i = Qt::TopLeftCorner; i <= Qt::BottomRightCorner; i++ )
60 {
61 const auto corner = static_cast< Qt::Corner >( i );
62 metrics.setRadius( corner, qskAligned05( metrics.radius( corner ) ) );
63 }
64
65 return QVariant::fromValue( metrics );
66 }
67 }
68 else if ( value.canConvert< QskMargins >() )
69 {
70 const auto margins = value.value< QskMargins >();
71 return QVariant::fromValue( qskAligned05( margins ) );
72 }
73
74 return value;
75}
76
77#endif
78
79static inline void qskSendAnimatorEvent(
80 const QskAspect aspect, int index, bool on, QObject* receiver )
81{
82 const auto state = on ? QskAnimatorEvent::Started : QskAnimatorEvent::Terminated;
83
84 const auto thread = receiver->thread();
85 if ( thread && ( thread != QThread::currentThread() ) )
86 {
87 /*
88 QskInputPanelSkinlet changes the skin state, what leads to
89 sending events from the wrong thread. We can't use
90 QCoreApplication::sendEvent then, TODO ...
91 */
92
93 auto event = new QskAnimatorEvent( aspect, index, state );
94 QCoreApplication::postEvent( receiver, event );
95 }
96 else
97 {
98 QskAnimatorEvent event( aspect, index, state );
99 QCoreApplication::sendEvent( receiver, &event );
100 }
101}
102
103QskHintAnimator::QskHintAnimator() noexcept
104 : m_index( -1 )
105{
106}
107
108QskHintAnimator::QskHintAnimator( const QskAspect aspect, int index ) noexcept
109 : m_aspect( aspect )
110 , m_index( index )
111{
112}
113
114QskHintAnimator::~QskHintAnimator()
115{
116}
117
118void QskHintAnimator::setAspect( const QskAspect aspect ) noexcept
119{
120 m_aspect = aspect;
121}
122
123void QskHintAnimator::setIndex( int index ) noexcept
124{
125 m_index = index;
126}
127
128void QskHintAnimator::setUpdateFlags( QskAnimationHint::UpdateFlags flags ) noexcept
129{
130 m_updateFlags = flags;
131}
132
133void QskHintAnimator::setControl( QskControl* control ) noexcept
134{
135 m_control = control;
136}
137
138void QskHintAnimator::advance( qreal progress )
139{
140 const auto oldValue = currentValue();
141
142 Inherited::advance( progress );
143
144#if ALIGN_VALUES
145 setCurrentValue( qskAligned05( currentValue() ) );
146#endif
147
148 if ( m_control && ( currentValue() != oldValue ) )
149 {
150 if ( m_updateFlags == QskAnimationHint::UpdateAuto )
151 {
152 if ( !m_aspect.isColor() )
153 {
154 m_control->resetImplicitSize();
155
156 if ( !m_control->childItems().isEmpty() )
157 m_control->polish();
158 }
159
160 m_control->update();
161 }
162 else
163 {
164 if ( m_updateFlags & QskAnimationHint::UpdateSizeHint )
165 m_control->resetImplicitSize();
166
167 if ( m_updateFlags & QskAnimationHint::UpdatePolish )
168 m_control->polish();
169
170 if ( m_updateFlags & QskAnimationHint::UpdateNode )
171 m_control->update();
172 }
173 }
174}
175
176#ifndef QT_NO_DEBUG_STREAM
177
178#include <qdebug.h>
179
180QDebug operator<<( QDebug debug, const QskHintAnimator& animator )
181{
182 QDebugStateSaver saver( debug );
183 debug.nospace();
184
185 debug << "Animator" << "( ";
186
187 debug << animator.aspect() << ", " << animator.endValue().typeName() << ", ";
188
189 if ( animator.index() >= 0 )
190 debug << animator.index() << ", ";
191
192 if ( animator.isRunning() )
193 debug << "R: " << animator.duration() << ", " << animator.elapsed();
194 else
195 debug << "S" << animator.duration();
196
197 if ( auto control = animator.control() )
198 debug << ", " << control->className() << ", " << (void*) control;
199
200 debug << " )";
201
202 return debug;
203}
204
205#endif
206
207namespace
208{
209 class AnimatorMap : public std::vector< QskHintAnimator* >
210 {
211 public:
212 ~AnimatorMap()
213 {
214 qDeleteAll( *this );
215 }
216
217 inline const QskHintAnimator* find( const QskAspect aspect, int index ) const
218 {
219 const Key key { aspect, index };
220
221 auto it = std::lower_bound( cbegin(), cend(), key, lessThan );
222 if ( it != cend() )
223 {
224 if ( ( ( *it )->aspect() == aspect ) && ( ( *it )->index() == index ) )
225 return *it;
226 }
227
228 return nullptr;
229 }
230
231 inline QskHintAnimator* findOrInsert( const QskAspect aspect, int index )
232 {
233 const Key key { aspect, index };
234
235 auto it = std::lower_bound( begin(), end(), key, lessThan );
236 if ( it == end() || ( *it )->aspect() != aspect || ( *it )->index() != index )
237 {
238 it = insert( it, new QskHintAnimator( aspect, index ) );
239 }
240
241 return *it;
242 }
243
244 private:
245 struct Key
246 {
247 QskAspect aspect;
248 int index;
249 };
250
251 static inline bool lessThan( const QskHintAnimator* animator, const Key& key )
252 {
253 if ( animator->aspect() == key.aspect )
254 return animator->index() < key.index;
255
256 return animator->aspect() < key.aspect;
257 }
258 };
259
260 class AnimatorGuard final : public QObject
261 {
262 Q_OBJECT
263
264 public:
265 AnimatorGuard()
266 {
267 QskAnimator::addCleanupHandler( this,
268 SLOT(cleanup()), Qt::QueuedConnection );
269 }
270
271 void registerTable( QskHintAnimatorTable* table )
272 {
273 auto it = std::lower_bound( m_tables.begin(), m_tables.end(), table );
274 if ( it == m_tables.end() || *it != table )
275 m_tables.insert( it, table );
276 }
277
278 void unregisterTable( QskHintAnimatorTable* table )
279 {
280 auto it = std::lower_bound( m_tables.begin(), m_tables.end(), table );
281 if ( it != m_tables.end() && *it == table )
282 m_tables.erase( it );
283 }
284
285 private Q_SLOTS:
286 void cleanup()
287 {
288 for ( auto it = m_tables.begin(); it != m_tables.end(); )
289 {
290 if ( ( *it )->cleanup() )
291 it = m_tables.erase( it );
292 else
293 ++it;
294 }
295 }
296
297 private:
298 // a vector as iteration is more important than insertion
299 std::vector< QskHintAnimatorTable* > m_tables;
300 };
301
302 Q_GLOBAL_STATIC( AnimatorGuard, qskAnimatorGuard )
303}
304
305class QskHintAnimatorTable::PrivateData
306{
307 public:
308 AnimatorMap animators; // a flat map
309};
310
311QskHintAnimatorTable::QskHintAnimatorTable()
312{
313}
314
315QskHintAnimatorTable::~QskHintAnimatorTable()
316{
317 if ( qskAnimatorGuard )
318 qskAnimatorGuard->unregisterTable( this );
319
320 delete m_data;
321}
322
323void QskHintAnimatorTable::start( QskControl* control,
324 const QskAspect aspect, int index, QskAnimationHint animationHint,
325 const QVariant& from, const QVariant& to )
326{
327 if ( m_data == nullptr )
328 {
329 m_data = new PrivateData();
330
331 if ( qskAnimatorGuard )
332 qskAnimatorGuard->registerTable( this );
333 }
334
335 auto animator = m_data->animators.findOrInsert( aspect, index );
336
337 animator->setStartValue( from );
338 animator->setEndValue( to );
339
340 animator->setDuration( animationHint.duration );
341 animator->setEasingCurve( animationHint.type );
342 animator->setUpdateFlags( animationHint.updateFlags );
343
344 animator->setControl( control );
345 animator->setWindow( control->window() );
346
347 animator->start();
348
349 qskSendAnimatorEvent( aspect, index, true, control );
350}
351
352const QskHintAnimator* QskHintAnimatorTable::animator( QskAspect aspect, int index ) const
353{
354 if ( m_data )
355 return m_data->animators.find( aspect, index );
356
357 return nullptr;
358}
359
360QVariant QskHintAnimatorTable::currentValue( QskAspect aspect, int index ) const
361{
362 if ( m_data )
363 {
364 if ( auto animator = m_data->animators.find( aspect, index ) )
365 {
366 if ( animator->isRunning() )
367 return animator->currentValue();
368 }
369 }
370
371 return QVariant();
372}
373
374bool QskHintAnimatorTable::cleanup()
375{
376 if ( m_data == nullptr )
377 return true;
378
379 auto& animators = m_data->animators;
380
381 for ( auto it = animators.begin(); it != animators.end(); )
382 {
383 auto animator = *it;
384
385 // remove all terminated animators
386 if ( !animator->isRunning() )
387 {
388 const auto control = animator->control();
389 const auto aspect = animator->aspect();
390 const auto index = animator->index();
391
392 delete animator;
393
394 it = animators.erase( it );
395
396 if ( control )
397 qskSendAnimatorEvent( aspect, index, false, control );
398 }
399 else
400 {
401 ++it;
402 }
403 }
404
405 if ( animators.empty() )
406 {
407 delete m_data;
408 m_data = nullptr;
409
410 return true;
411 }
412
413 return false;
414}
415
416#include "QskHintAnimator.moc"
Lookup key for a QskSkinHintTable.
Definition QskAspect.h:15
constexpr bool isColor() const noexcept
Definition QskAspect.h:462
Base class of all controls.
Definition QskControl.h:23