QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskStackBox.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskStackBox.h"
7#include "QskStackBoxAnimator.h"
8#include "QskEvent.h"
9#include "QskQuick.h"
10
11#include <QPointer>
12
13class QskStackBox::PrivateData
14{
15 public:
16 QVector< QQuickItem* > items;
17 QPointer< QskStackBoxAnimator > animator;
18
19 int currentIndex = -1;
20 Qt::Alignment defaultAlignment = Qt::AlignLeft | Qt::AlignVCenter;
21};
22
23QskStackBox::QskStackBox( QQuickItem* parent )
24 : QskStackBox( false, parent )
25{
26}
27
28QskStackBox::QskStackBox( bool autoAddChildren, QQuickItem* parent )
29 : QskIndexedLayoutBox( parent )
30 , m_data( new PrivateData() )
31{
32 setAutoAddChildren( autoAddChildren );
33}
34
35QskStackBox::~QskStackBox()
36{
37}
38
39void QskStackBox::setDefaultAlignment( Qt::Alignment alignment )
40{
41 if ( alignment != m_data->defaultAlignment )
42 {
43 m_data->defaultAlignment = alignment;
44 Q_EMIT defaultAlignmentChanged( alignment );
45
46 polish();
47 }
48}
49
50Qt::Alignment QskStackBox::defaultAlignment() const
51{
52 return m_data->defaultAlignment;
53}
54
55void QskStackBox::setAnimator( QskStackBoxAnimator* animator )
56{
57 if ( m_data->animator == animator )
58 return;
59
60 if ( m_data->animator )
61 {
62 m_data->animator->stop();
63 delete m_data->animator;
64 }
65
66 if ( animator )
67 {
68 animator->stop();
69 animator->setParent( this );
70 }
71
72 m_data->animator = animator;
73}
74
75const QskStackBoxAnimator* QskStackBox::animator() const
76{
77 return m_data->animator;
78}
79
80QskStackBoxAnimator* QskStackBox::animator()
81{
82 return m_data->animator;
83}
84
85QskStackBoxAnimator* QskStackBox::effectiveAnimator()
86{
87 if ( m_data->animator )
88 return m_data->animator;
89
90 // getting an animation from the skin. TODO ...
91
92 return nullptr;
93}
94
95int QskStackBox::itemCount() const
96{
97 return m_data->items.count();
98}
99
100QQuickItem* QskStackBox::itemAtIndex( int index ) const
101{
102 return m_data->items.value( index );
103}
104
105int QskStackBox::indexOf( const QQuickItem* item ) const
106{
107 if ( item && ( item->parentItem() == this ) )
108 {
109 for ( int i = 0; i < m_data->items.count(); i++ )
110 {
111 if ( item == m_data->items[i] )
112 return i;
113 }
114 }
115
116 return -1;
117}
118
119QQuickItem* QskStackBox::currentItem() const
120{
121 return itemAtIndex( m_data->currentIndex );
122}
123
124int QskStackBox::currentIndex() const
125{
126 return m_data->currentIndex;
127}
128
129void QskStackBox::setCurrentIndex( int index )
130{
131 if ( index < 0 || index >= itemCount() )
132 {
133 // hide the current item
134 index = -1;
135 }
136
137 if ( index == m_data->currentIndex )
138 return;
139
140 // stop and complete the running transition
141 auto animator = effectiveAnimator();
142 if ( animator )
143 animator->stop();
144
145 if ( window() && isVisible() && isInitiallyPainted() && animator )
146 {
147 // start the animation
148 animator->setStartIndex( m_data->currentIndex );
149 animator->setEndIndex( index );
150 animator->setWindow( window() );
151 animator->start();
152 }
153 else
154 {
155 auto item1 = itemAtIndex( m_data->currentIndex );
156 auto item2 = itemAtIndex( index );
157
158 if ( item1 )
159 item1->setVisible( false );
160
161 if ( item2 )
162 item2->setVisible( true );
163 }
164
165 m_data->currentIndex = index;
166 polish();
167
168 Q_EMIT currentIndexChanged( m_data->currentIndex );
169}
170
171qreal QskStackBox::transientIndex() const
172{
173 if ( auto animator = m_data->animator )
174 {
175 if ( animator->isRunning() )
176 return animator->transientIndex();
177 }
178
179 return currentIndex();
180}
181
182void QskStackBox::setCurrentItem( const QQuickItem* item )
183{
184 setCurrentIndex( indexOf( item ) );
185}
186
187void QskStackBox::addItem( QQuickItem* item )
188{
189 insertItem( -1, item );
190}
191
192void QskStackBox::addItem( QQuickItem* item, Qt::Alignment alignment )
193{
194 insertItem( -1, item, alignment );
195}
196
197void QskStackBox::insertItem( int index, QQuickItem* item )
198{
199 if ( item == nullptr || item == this )
200 return;
201
202 reparentItem( item );
203
204 if ( !qskPlacementPolicy( item ).isEffective() )
205 {
206 qWarning() << "Inserting an item that is to be ignored for layouting"
207 << item->metaObject()->className();
208
209 qskSetPlacementPolicy( item, QskPlacementPolicy() );
210 }
211
212 const bool doAppend = ( index < 0 ) || ( index >= itemCount() );
213
214 if ( item->parentItem() == this )
215 {
216 const int oldIndex = indexOf( item );
217 if ( oldIndex >= 0 )
218 {
219 // the item had been inserted before
220
221 if ( ( index == oldIndex ) || ( doAppend && ( oldIndex == itemCount() - 1 ) ) )
222 {
223 // already in place
224 return;
225 }
226
227 m_data->items.removeAt( oldIndex );
228 }
229 }
230
231 if ( doAppend )
232 index = itemCount();
233
234 m_data->items.insert( index, item );
235
236 const int oldCurrentIndex = m_data->currentIndex;
237
238 if ( m_data->items.count() == 1 )
239 {
240 m_data->currentIndex = 0;
241 item->setVisible( true );
242 }
243 else
244 {
245 item->setVisible( false );
246
247 if ( index <= m_data->currentIndex )
248 m_data->currentIndex++;
249 }
250
251 if ( oldCurrentIndex != m_data->currentIndex )
252 Q_EMIT currentIndexChanged( m_data->currentIndex );
253
255 polish();
256}
257
258void QskStackBox::insertItem(
259 int index, QQuickItem* item, Qt::Alignment alignment )
260{
261 if ( auto control = qskControlCast( item ) )
262 control->setLayoutAlignmentHint( alignment );
263
264 insertItem( index, item );
265}
266
267void QskStackBox::removeAt( int index )
268{
269 removeItemInternal( index, true );
270}
271
272void QskStackBox::removeItemInternal( int index, bool unparent )
273{
274 if ( index < 0 || index >= m_data->items.count() )
275 return;
276
277 if ( unparent )
278 {
279 if ( auto item = m_data->items[ index ] )
280 unparentItem( item );
281 }
282
283 m_data->items.removeAt( index );
284
285 auto& currentIndex = m_data->currentIndex;
286
287 if ( index <= currentIndex )
288 {
289 currentIndex--;
290
291 if ( currentIndex < 0 && !m_data->items.isEmpty() )
292 currentIndex = 0;
293
294 if ( currentIndex >= 0 )
295 m_data->items[ currentIndex ]->setVisible( true );
296
297 Q_EMIT currentIndexChanged( currentIndex );
298 }
299
301 polish();
302}
303
304void QskStackBox::removeItem( const QQuickItem* item )
305{
306 removeAt( indexOf( item ) );
307}
308
309void QskStackBox::autoAddItem( QQuickItem* item )
310{
311 removeAt( indexOf( item ) );
312}
313
314void QskStackBox::autoRemoveItem( QQuickItem* item )
315{
316 removeItemInternal( indexOf( item ), false );
317}
318
319void QskStackBox::clear( bool autoDelete )
320{
321 for ( const auto item : std::as_const( m_data->items ) )
322 {
323 if( autoDelete && ( item->parent() == this ) )
324 delete item;
325 else
326 item->setParentItem( nullptr );
327 }
328
329 m_data->items.clear();
330
331 if ( m_data->currentIndex >= 0 )
332 {
333 m_data->currentIndex = -1;
334 Q_EMIT currentIndexChanged( m_data->currentIndex );
335 }
336}
337
338QRectF QskStackBox::geometryForItemAt( int index ) const
339{
340 const auto r = layoutRect();
341
342 if ( const auto item = m_data->items.value( index ) )
343 {
344 auto alignment = qskLayoutAlignmentHint( item );
345 if ( alignment == 0 )
346 alignment = m_data->defaultAlignment;
347
348 return qskConstrainedItemRect( item, r, alignment );
349 }
350
351 return QRectF( r.x(), r.y(), 0.0, 0.0 );
352}
353
354void QskStackBox::updateLayout()
355{
356 if ( maybeUnresized() )
357 return;
358
359 for ( int i = 0; i < m_data->items.count(); i++ )
360 {
361 auto item = m_data->items[ i ];
362
363 const auto visibility =
364 ( i == m_data->currentIndex ) ? Qsk::Visible : Qsk::Hidden;
365
366 if ( qskPlacementPolicy( item ).isAdjusting( visibility ) )
367 {
368 const auto rect = geometryForItemAt( i );
369 qskSetItemGeometry( m_data->items[ i ], rect );
370 }
371 }
372}
373
374QSizeF QskStackBox::layoutSizeHint(
375 Qt::SizeHint which, const QSizeF& constraint ) const
376{
377 if ( which == Qt::MaximumSize )
378 return QSizeF();
379
380 qreal w = -1.0;
381 qreal h = -1.0;
382
383 for ( const auto item : std::as_const( m_data->items ) )
384 {
385 /*
386 We ignore the retainSizeWhenVisible flag and include all
387 invisible items. Maybe we should offer a flag to control this ?
388 */
389 const auto policy = qskSizePolicy( item );
390
391 if ( constraint.width() >= 0.0 && policy.isConstrained( Qt::Vertical ) )
392 {
393 const auto hint = qskSizeConstraint( item, which, constraint );
394 h = qMax( h, hint.height() );
395 }
396 else if ( constraint.height() >= 0.0 && policy.isConstrained( Qt::Horizontal ) )
397 {
398 const auto hint = qskSizeConstraint( item, which, constraint );
399 w = qMax( w, hint.width() );
400 }
401 else
402 {
403 const auto hint = qskSizeConstraint( item, which, QSizeF() );
404
405 w = qMax( w, hint.width() );
406 h = qMax( h, hint.height() );
407 }
408 }
409
410 // minimum layout hint needs to be cached TODO ...
411 return QSizeF( w, h );
412}
413
414bool QskStackBox::event( QEvent* event )
415{
416 switch ( static_cast< int >( event->type() ) )
417 {
418 case QEvent::LayoutRequest:
419 {
421 polish();
422 break;
423 }
424 case QEvent::ContentsRectChange:
425 case QskEvent::GeometryChange:
426 {
427 polish();
428 break;
429 }
430 }
431
432 return Inherited::event( event );
433}
434
435void QskStackBox::dump() const
436{
437 auto debug = qDebug();
438
439 QDebugStateSaver saver( debug );
440 debug.nospace();
441
442 const auto constraint = sizeConstraint();
443
444 debug << "QskStackBox"
445 << " w:" << constraint.width() << " h:" << constraint.height() << '\n';
446
447 for ( int i = 0; i < m_data->items.count(); i++ )
448 {
449 const auto item = m_data->items[i];
450
451 debug << " " << i << ": ";
452
453 const auto size = qskSizeConstraint( item, Qt::PreferredSize );
454 debug << item->metaObject()->className()
455 << " w:" << size.width() << " h:" << size.height();
456
457 if ( i == m_data->currentIndex )
458 debug << " [X]";
459
460 debug << '\n';
461 }
462}
463
464#include "moc_QskStackBox.cpp"
QRectF layoutRect() const
QSizeF sizeConstraint
Definition QskControl.h:50
Base class of layouts with index ordered elements.
QRectF rect
Definition QskItem.h:21
void resetImplicitSize()
Definition QskItem.cpp:721
bool isInitiallyPainted() const
Definition QskItem.cpp:578
bool maybeUnresized() const
Definition QskItem.cpp:583
void debug(QskAspect) const
Global definitions.
@ Visible
@ Hidden