qskinny/src/layouts/QskStackBox.cpp

532 lines
12 KiB
C++
Raw Normal View History

2017-07-21 16:21:34 +00:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#include "QskStackBox.h"
2018-08-03 06:15:28 +00:00
#include "QskStackBoxAnimator.h"
#include "QskLayoutConstraint.h"
#include "QskEvent.h"
#include "QskQuick.h"
#include <QPointer>
2017-07-21 16:21:34 +00:00
static qreal qskConstrainedValue( QskLayoutConstraint::Type type,
const QskControl* control, qreal widthOrHeight )
{
using namespace QskLayoutConstraint;
auto constrainFunction =
( type == WidthForHeight ) ? widthForHeight : heightForWidth;
qreal constrainedValue = -1;
auto stackBox = static_cast< const QskStackBox* >( control );
for ( int i = 0; i < stackBox->itemCount(); i++ )
{
if ( const auto item = stackBox->itemAtIndex( i ) )
{
const qreal v = constrainFunction( item, widthOrHeight );
if ( v > constrainedValue )
constrainedValue = v;
}
2019-05-06 14:36:52 +00:00
}
return constrainedValue;
}
namespace
2017-07-21 16:21:34 +00:00
{
class ItemInfo
2017-07-21 16:21:34 +00:00
{
public:
inline ItemInfo()
: item( nullptr )
{
}
inline ItemInfo( Qt::Alignment alignment, QQuickItem* item )
: alignment( alignment )
, item( item )
{
}
Qt::Alignment alignment;
QQuickItem* item;
};
}
2017-07-21 16:21:34 +00:00
class QskStackBox::PrivateData
{
public:
QVector< ItemInfo > itemInfos;
2017-07-21 16:21:34 +00:00
QPointer< QskStackBoxAnimator > animator;
int currentIndex = -1;
Qt::Alignment defaultAlignment = Qt::AlignLeft | Qt::AlignVCenter;
2017-07-21 16:21:34 +00:00
};
2018-08-03 06:15:28 +00:00
QskStackBox::QskStackBox( QQuickItem* parent )
: QskStackBox( false, parent )
2017-07-21 16:21:34 +00:00
{
}
2018-08-03 06:15:28 +00:00
QskStackBox::QskStackBox( bool autoAddChildren, QQuickItem* parent )
: QskIndexedLayoutBox( parent )
2018-08-03 06:15:28 +00:00
, m_data( new PrivateData() )
2017-07-21 16:21:34 +00:00
{
setAutoAddChildren( autoAddChildren );
}
QskStackBox::~QskStackBox()
{
}
void QskStackBox::setDefaultAlignment( Qt::Alignment alignment )
{
if ( alignment != m_data->defaultAlignment )
{
m_data->defaultAlignment = alignment;
Q_EMIT defaultAlignmentChanged( alignment );
polish();
}
}
Qt::Alignment QskStackBox::defaultAlignment() const
{
return m_data->defaultAlignment;
}
2017-07-21 16:21:34 +00:00
void QskStackBox::setAnimator( QskStackBoxAnimator* animator )
{
if ( m_data->animator == animator )
return;
if ( m_data->animator )
{
m_data->animator->stop();
delete m_data->animator;
}
if ( animator )
{
animator->stop();
animator->setParent( this );
}
m_data->animator = animator;
}
const QskStackBoxAnimator* QskStackBox::animator() const
{
return m_data->animator;
}
QskStackBoxAnimator* QskStackBox::animator()
{
return m_data->animator;
}
QskStackBoxAnimator* QskStackBox::effectiveAnimator()
{
if ( m_data->animator )
return m_data->animator;
// getting an animation from the skin. TODO ...
return nullptr;
}
int QskStackBox::itemCount() const
2017-07-21 16:21:34 +00:00
{
return m_data->itemInfos.count();
2017-07-21 16:21:34 +00:00
}
QQuickItem* QskStackBox::itemAtIndex( int index ) const
2017-07-21 16:21:34 +00:00
{
if ( index >= 0 && index < m_data->itemInfos.count() )
return m_data->itemInfos[ index ].item;
return nullptr;
2017-07-21 16:21:34 +00:00
}
int QskStackBox::indexOf( const QQuickItem* item ) const
2017-07-21 16:21:34 +00:00
{
if ( item && ( item->parentItem() != this ) )
2017-07-21 16:21:34 +00:00
{
for ( int i = 0; i < m_data->itemInfos.count(); i++ )
{
if ( item == m_data->itemInfos[i].item )
return i;
}
}
2017-07-21 16:21:34 +00:00
return -1;
}
2017-07-21 16:21:34 +00:00
QQuickItem* QskStackBox::currentItem() const
{
return itemAtIndex( m_data->currentIndex );
}
int QskStackBox::currentIndex() const
{
return m_data->currentIndex;
2017-07-21 16:21:34 +00:00
}
void QskStackBox::setCurrentIndex( int index )
{
if ( index < 0 || index >= itemCount() )
{
// hide the current item
index = -1;
}
if ( index == m_data->currentIndex )
return;
// stop and complete the running transition
2019-04-08 11:25:06 +00:00
auto animator = effectiveAnimator();
2017-07-21 16:21:34 +00:00
if ( animator )
animator->stop();
if ( window() && isVisible() && isInitiallyPainted() && animator )
2017-07-21 16:21:34 +00:00
{
// start the animation
animator->setStartIndex( m_data->currentIndex );
animator->setEndIndex( index );
animator->setWindow( window() );
animator->start();
}
else
{
2019-04-08 11:25:06 +00:00
auto item1 = itemAtIndex( m_data->currentIndex );
auto item2 = itemAtIndex( index );
2017-07-21 16:21:34 +00:00
if ( item1 )
item1->setVisible( false );
if ( item2 )
item2->setVisible( true );
}
m_data->currentIndex = index;
Q_EMIT currentIndexChanged( m_data->currentIndex );
}
void QskStackBox::setCurrentItem( const QQuickItem* item )
{
setCurrentIndex( indexOf( item ) );
}
void QskStackBox::addItem( QQuickItem* item, Qt::Alignment alignment )
{
insertItem( -1, item, alignment );
}
void QskStackBox::insertItem(
int index, QQuickItem* item, Qt::Alignment alignment )
{
if ( item == nullptr )
return;
reparentItem( item );
if ( qskIsTransparentForPositioner( item ) )
{
// giving a warning, or ignoring the insert ???
qskSetTransparentForPositioner( item, false );
}
const bool doAppend = ( index < 0 ) || ( index >= itemCount() );
if ( item->parentItem() == this )
{
const int oldIndex = indexOf( item );
if ( oldIndex >= 0 )
{
// the item had been inserted before
if ( ( index == oldIndex ) || ( doAppend && ( oldIndex == itemCount() - 1 ) ) )
{
// already in place
auto& itemInfo = m_data->itemInfos[oldIndex];
if ( alignment != itemInfo.alignment )
{
itemInfo.alignment = alignment;
polish();
}
return;
}
m_data->itemInfos.removeAt( oldIndex );
}
}
if ( doAppend )
index = itemCount();
m_data->itemInfos.insert( index, { alignment, item } );
const int oldCurrentIndex = m_data->currentIndex;
if ( m_data->itemInfos.count() == 1 )
{
m_data->currentIndex = 0;
item->setVisible( true );
}
else
{
item->setVisible( false );
if ( index <= m_data->currentIndex )
m_data->currentIndex++;
}
if ( oldCurrentIndex != m_data->currentIndex )
Q_EMIT currentIndexChanged( m_data->currentIndex );
resetImplicitSize();
polish();
}
void QskStackBox::removeAt( int index )
{
removeItemInternal( index, false );
}
void QskStackBox::removeItemInternal( int index, bool unparent )
2017-07-21 16:21:34 +00:00
{
if ( index < 0 || index >= m_data->itemInfos.count() )
return;
if ( !unparent )
{
if ( auto item = m_data->itemInfos[ index ].item )
{
if ( item->parentItem() == this )
item->setParentItem( nullptr );
}
}
m_data->itemInfos.removeAt( index );
if ( index <= m_data->currentIndex )
Q_EMIT currentIndexChanged( --m_data->currentIndex );
resetImplicitSize();
polish();
}
void QskStackBox::removeItem( const QQuickItem* item )
{
removeAt( indexOf( item ) );
}
void QskStackBox::autoAddItem( QQuickItem* item )
{
removeAt( indexOf( item ) );
}
void QskStackBox::autoRemoveItem( QQuickItem* item )
{
removeItemInternal( indexOf( item ), false );
}
void QskStackBox::clear( bool autoDelete )
{
for ( const auto& itemInfo : qskAsConst( m_data->itemInfos ) )
{
auto item = itemInfo.item;
if( autoDelete && ( item->parent() == this ) )
{
delete item;
}
else
{
item->setParentItem( nullptr );
}
}
m_data->itemInfos.clear();
if ( m_data->currentIndex >= 0 )
{
m_data->currentIndex = -1;
Q_EMIT currentIndexChanged( m_data->currentIndex );
}
}
void QskStackBox::setAlignment( const QQuickItem* item, Qt::Alignment alignment )
{
setAlignmentAt( indexOf( item ), alignment );
}
Qt::Alignment QskStackBox::alignment( const QQuickItem* item ) const
{
return alignmentAt( indexOf( item ) );
}
void QskStackBox::setAlignmentAt( int index, Qt::Alignment alignment )
{
if ( index < 0 || index >= m_data->itemInfos.count() )
return;
m_data->itemInfos[ index ].alignment = alignment;
if ( index == m_data->currentIndex )
polish();
}
Qt::Alignment QskStackBox::alignmentAt( int index ) const
{
if ( index >= 0 && index < m_data->itemInfos.count() )
return m_data->itemInfos[ index ].alignment;
return Qt::Alignment();
}
QRectF QskStackBox::geometryForItemAt( int index ) const
{
const auto r = layoutRect();
if ( index >= 0 && index < m_data->itemInfos.count() )
{
const auto& info = m_data->itemInfos[ index ];
const auto align = info.alignment ? info.alignment : m_data->defaultAlignment;
return QskLayoutConstraint::itemRect( info.item, r, align );
}
return QRectF( r.x(), r.y(), 0.0, 0.0 );
}
void QskStackBox::updateLayout()
{
const auto idx = m_data->currentIndex;
if ( idx >= 0 )
{
const auto rect = geometryForItemAt( idx );
qskSetItemGeometry( m_data->itemInfos[ idx ].item, rect );
}
}
QSizeF QskStackBox::contentsSizeHint() const
{
#if 1
if ( itemCount() == 0 )
return QSizeF( 0, 0 );
#endif
2017-07-21 16:21:34 +00:00
qreal width = -1;
qreal height = -1;
using namespace QskLayoutConstraint;
2017-07-21 16:21:34 +00:00
int constraintTypes = Unconstrained;
for ( const auto& itemInfo : qskAsConst( m_data->itemInfos ) )
2017-07-21 16:21:34 +00:00
{
const auto item = itemInfo.item;
2017-07-21 16:21:34 +00:00
const auto type = constraintType( item );
if ( type != Unconstrained )
2017-07-21 16:21:34 +00:00
{
constraintTypes |= type;
2017-07-21 16:21:34 +00:00
}
else
{
const QSizeF hint = effectiveConstraint( item, Qt::PreferredSize );
2017-07-21 16:21:34 +00:00
if ( hint.width() >= width )
width = hint.width();
if ( hint.height() >= height )
height = hint.height();
}
}
#if 1
// does this work ???
if ( constraintTypes & WidthForHeight )
2017-07-21 16:21:34 +00:00
{
const QSizeF constraint( -1, height );
2017-07-21 16:21:34 +00:00
for ( const auto& itemInfo : qskAsConst( m_data->itemInfos ) )
2017-07-21 16:21:34 +00:00
{
const auto item = itemInfo.item;
2017-07-21 16:21:34 +00:00
if ( constraintType( item ) == WidthForHeight )
2017-07-21 16:21:34 +00:00
{
const QSizeF hint = QskLayoutConstraint::sizeHint(
item, Qt::PreferredSize, constraint );
width = qMax( width, hint.width() );
2017-07-21 16:21:34 +00:00
}
}
}
if ( constraintTypes & HeightForWidth )
2017-07-21 16:21:34 +00:00
{
const QSizeF constraint( width, -1 );
2017-07-21 16:21:34 +00:00
for ( const auto& itemInfo : qskAsConst( m_data->itemInfos ) )
2017-07-21 16:21:34 +00:00
{
const auto item = itemInfo.item;
2017-07-21 16:21:34 +00:00
if ( constraintType( item ) == HeightForWidth )
2017-07-21 16:21:34 +00:00
{
const QSizeF hint = QskLayoutConstraint::sizeHint(
item, Qt::PreferredSize, constraint );
height = qMax( height, hint.height() );
2017-07-21 16:21:34 +00:00
}
}
}
#endif
return QSizeF( width, height );
}
qreal QskStackBox::heightForWidth( qreal width ) const
{
return QskLayoutConstraint::constrainedMetric(
QskLayoutConstraint::HeightForWidth, this, width, qskConstrainedValue );
}
qreal QskStackBox::widthForHeight( qreal height ) const
{
return QskLayoutConstraint::constrainedMetric(
QskLayoutConstraint::WidthForHeight, this, height, qskConstrainedValue );
}
bool QskStackBox::event( QEvent* event )
2017-07-21 16:21:34 +00:00
{
switch ( static_cast< int >( event->type() ) )
2017-07-21 16:21:34 +00:00
{
case QEvent::LayoutRequest:
{
resetImplicitSize();
polish();
break;
}
case QEvent::ContentsRectChange:
case QskEvent::GeometryChange:
{
polish();
break;
}
2017-07-21 16:21:34 +00:00
}
return Inherited::event( event );
2017-07-21 16:21:34 +00:00
}
#include "moc_QskStackBox.cpp"