qskinny/src/layouts/QskLinearLayoutEngine.cpp

1423 lines
35 KiB
C++
Raw Normal View History

#include "QskLinearLayoutEngine.h"
#include "QskLayoutHint.h"
#include "QskLayoutConstraint.h"
#include "QskSizePolicy.h"
#include "QskQuick.h"
#include <qvector.h>
#include <qvarlengtharray.h>
#include <vector>
#ifdef QSK_LAYOUT_COMPAT
#include <cmath>
#endif
static constexpr qreal qskDefaultSpacing()
{
// should be a skin hint
return 5.0;
}
namespace
{
class Range
{
public:
inline qreal end() const { return start + length; }
qreal start = 0.0;
qreal length = 0.0;
};
class CellData
{
public:
QskLayoutHint hint;
int stretch = 0;
bool growFlag = false; // using stretch: -1
};
class CellTable
{
public:
void invalidate();
void reset( int count, qreal constraint );
void addCellData( int index, const CellData& );
void finish();
bool setSpacing( qreal spacing );
qreal spacing() const { return m_spacing; }
void setExtraSpacingAt( Qt::Edges edges ) { m_extraSpacingAt = edges; }
QVector< Range > cellRanges( qreal size ) const;
QskLayoutHint boundingHint() const { return m_boundingHint; }
inline qreal constraint() const { return m_constraint; }
inline int count() const { return m_cells.size(); }
private:
QVector< Range > distributed( int which, qreal offset, qreal extra ) const;
QVector< Range > minimumExpanded( qreal size ) const;
QVector< Range > preferredStretched( qreal size ) const;
QskLayoutHint m_boundingHint;
qreal m_constraint = -2;
qreal m_spacing = 0;
Qt::Edges m_extraSpacingAt;
int m_sumStretches = 0;
std::vector< CellData > m_cells;
};
class CellGeometries
{
public:
void invalidate()
{
boundingSize = QSizeF();
rows.clear();
columns.clear();
}
QRectF geometryAt( int row, int col ) const
{
return QRectF(
columns[ col ].start, rows[ row ].start,
columns[ col ].length, rows[ row ].length );
}
QSizeF boundingSize;
QVector< Range > rows;
QVector< Range > columns;
};
}
void CellTable::invalidate()
{
m_cells.clear();
m_constraint = -2;
}
void CellTable::reset( int count, qreal constraint )
{
m_cells.assign( count, CellData() );
m_constraint = constraint;
}
void CellTable::addCellData( int index, const CellData& data )
{
auto& combinedData = m_cells[ index ];
combinedData.growFlag |= data.growFlag;
combinedData.stretch = qMax( combinedData.stretch, data.stretch );
m_sumStretches += data.stretch;
combinedData.hint.intersect( data.hint );
}
void CellTable::finish()
{
qreal minimum = 0.0;
qreal preferred = 0.0;
qreal maximum = 0.0;
if ( !m_cells.empty() )
{
for ( auto& cellData : m_cells )
{
minimum += cellData.hint.minimum();
preferred += cellData.hint.preferred();
if ( cellData.stretch == 0 && !cellData.growFlag )
maximum += cellData.hint.preferred();
else
maximum += cellData.hint.maximum(); // overflow ???
}
const qreal spacing = ( m_cells.size() - 1 ) * m_spacing;
minimum += spacing;
preferred += spacing;
maximum += spacing;
}
m_boundingHint.setMinimum( minimum );
m_boundingHint.setPreferred( preferred );
m_boundingHint.setMaximum( maximum );
}
bool CellTable::setSpacing( qreal spacing )
{
if ( m_spacing != spacing )
{
m_spacing = spacing;
return true;
}
return false;
}
QVector< Range > CellTable::cellRanges( qreal size ) const
{
QVector< Range > ranges;
if ( size <= m_boundingHint.minimum() )
{
ranges = distributed( Qt::MinimumSize, 0.0, 0.0 );
}
else if ( size < m_boundingHint.preferred() )
{
ranges = minimumExpanded( size );
}
else if ( size <= m_boundingHint.maximum() )
{
ranges = preferredStretched( size );
}
else
{
const qreal padding = size - m_boundingHint.maximum();
qreal offset = 0.0;
qreal extra = 0.0;;
if ( m_extraSpacingAt == Qt::LeftEdge )
{
offset = padding;
}
else if ( m_extraSpacingAt == Qt::RightEdge )
{
offset = 0.0;
}
else if ( m_extraSpacingAt == ( Qt::LeftEdge | Qt::RightEdge ) )
{
offset = 0.5 * padding;
}
else
{
extra = padding / m_cells.size();
}
ranges = distributed( Qt::MaximumSize, offset, extra );
}
return ranges;
}
QVector< Range > CellTable::distributed(
int which, qreal offset, const qreal extra ) const
{
qreal fillSpacing = 0.0;
QVector< Range > ranges( m_cells.size() );
for ( int i = 0; i < ranges.count(); i++ )
{
auto& range = ranges[i];
offset += fillSpacing;
fillSpacing = m_spacing;
range.start = offset;
range.length = m_cells[i].hint.size( which ) + extra;
offset += range.length;
}
return ranges;
}
QVector< Range > CellTable::minimumExpanded( qreal size ) const
{
QVector< Range > ranges( m_cells.size() );
qreal fillSpacing = 0.0;
qreal offset = 0.0;
/*
We have different options how to distribute the availabe space
- according to the preferred sizes
- items with a larger preferred size are stretchier: this is
what QSK_LAYOUT_COMPAT does ( compatible with QGridLayoutEngine )
- somehow using the stretch factors
*/
#if QSK_LAYOUT_COMPAT
/*
Code does not make much sense, but this is what QGridLayoutEngine does.
The implementation is intended to help during the migration, but is supposed
to be removed then TODO ...
*/
qreal sumFactors = 0.0;
QVarLengthArray< qreal > factors( m_cells.size() );
const qreal desired = m_boundingHint.preferred() - m_boundingHint.minimum();
const qreal available = size - m_boundingHint.minimum();
for ( uint i = 0; i < m_cells.size(); i++ )
{
const auto& hint = m_cells[i].hint;
const qreal l = hint.preferred() - hint.minimum();
factors[i] = l * std::pow( available / desired, l / desired );
sumFactors += factors[i];
}
for ( uint i = 0; i < m_cells.size(); i++ )
{
const auto& hint = m_cells[i].hint;
auto& range = ranges[i];
offset += fillSpacing;
fillSpacing = m_spacing;
range.start = offset;
range.length = hint.minimum() + available * ( factors[i] / sumFactors );
offset += range.length;
}
#else
const qreal factor = ( size - m_boundingHint.minimum() ) /
( m_boundingHint.preferred() - m_boundingHint.minimum() );
for ( uint i = 0; i < m_cells.size(); i++ )
{
const auto& hint = m_cells[i].hint;
auto& range = ranges[i];
offset += fillSpacing;
fillSpacing = m_spacing;
range.start = offset;
range.length = hint.minimum() + factor * ( hint.preferred() - hint.minimum() );
offset += range.length;
}
#endif
return ranges;
}
QVector< Range > CellTable::preferredStretched( qreal size ) const
{
const int count = m_cells.size();
auto sumSizes = size - ( count - 1 ) * m_spacing;
qreal sumFactors = 0.0;
QVarLengthArray< qreal > factors( count );
for ( int i = 0; i < count; i++ )
{
const auto& hint = m_cells[i].hint;
if ( hint.preferred() >= hint.maximum() )
{
factors[i] = 0.0;
}
else
{
if ( m_sumStretches == 0 )
factors[i] = m_cells[i].growFlag ? 1.0 : 0.0;
else
factors[i] = m_cells[i].stretch;
}
sumFactors += factors[i];
}
QVector< Range > ranges( count );
Q_FOREVER
{
bool done = true;
for ( int i = 0; i < count; i++ )
{
if ( factors[i] < 0.0 )
continue;
const auto size = sumSizes * factors[i] / sumFactors;
const auto& hint = m_cells[i].hint;
const auto boundedSize =
qBound( hint.preferred(), size, hint.maximum() );
if ( boundedSize != size )
{
ranges[i].length = boundedSize;
sumSizes -= boundedSize;
sumFactors -= factors[i];
factors[i] = -1.0;
done = false;
}
}
if ( done )
break;
}
qreal offset = 0;
qreal fillSpacing = 0.0;
for ( int i = 0; i < count; i++ )
{
auto& range = ranges[i];
const auto& factor = factors[i];
offset += fillSpacing;
fillSpacing = m_spacing;
range.start = offset;
if ( factor >= 0.0 )
{
if ( factor > 0.0 )
range.length = sumSizes * factor / sumFactors;
else
range.length = m_cells[i].hint.preferred();
}
offset += range.length;
}
return ranges;
}
namespace
{
class EntryData
{
public:
EntryData( QQuickItem* item );
EntryData( qreal spacing );
EntryData& operator=( const EntryData& );
bool isIgnored() const;
bool isConstrained( Qt::Orientation ) const;
qreal spacer() const { return m_isSpacer ? m_spacer : -1.0; }
QQuickItem* item() const { return m_isSpacer ? nullptr : m_item; }
inline Qt::Alignment alignment() const
{ return static_cast< Qt::Alignment >( m_alignment ); }
inline void setAlignment( Qt::Alignment alignment ) { m_alignment = alignment; }
inline int stretch() const { return m_stretch; }
inline void setStretch( int stretch ) { m_stretch = stretch; }
bool retainSizeWhenHidden() const { return m_retainSizeWhenHidden; }
void setRetainSizeWhenHidden( bool on ) { m_retainSizeWhenHidden = on; }
private:
union
{
QQuickItem* m_item;
qreal m_spacer;
};
int m_stretch;
unsigned int m_alignment : 8;
bool m_isSpacer : 1;
bool m_retainSizeWhenHidden : 1;
};
class EntryTable
{
public:
EntryTable( Qt::Orientation, uint dimension );
bool setOrientation( Qt::Orientation orientation );
Qt::Orientation orientation() const;
bool setDimension( uint dimension );
uint dimension() const;
bool setDefaultAlignment( Qt::Alignment );
Qt::Alignment defaultAlignment() const;
Qt::Alignment effectiveAlignmentAt( int index ) const;
void insertItem( int index, QQuickItem* );
void insertSpacer( int index, qreal spacing );
QQuickItem* itemAt( int index ) const;
int spacerAt( int index ) const;
bool removeAt( int index );
bool isIgnoredAt( int index ) const;
bool setAlignmentAt( int index, Qt::Alignment );
Qt::Alignment alignmentAt( int index ) const;
bool setStretchFactorAt( int index, int stretchFactor );
int stretchFactorAt( int index ) const;
bool setRetainSizeWhenHiddenAt( int index, bool on );
bool retainSizeWhenHiddenAt( int index ) const;
inline int count() const { return m_entries.size(); }
int effectiveCount() const;
int effectiveCount( Qt::Orientation orientation ) const;
void updateCellTable( Qt::Orientation,
const QVector< Range >& constraints, CellTable& ) const;
QskLayoutConstraint::Type constraintType() const;
void invalidate();
private:
CellData cellData( const EntryData&,
Qt::Orientation, qreal constraint ) const;
inline EntryData* entryAt( int index ) const
{
if ( index < 0 || index >= count() )
{
// qWarning, TODO ...
return nullptr;
}
return const_cast< EntryData* >( &m_entries[index] );
}
std::vector< EntryData > m_entries;
uint m_dimension;
mutable int m_sumIgnored : 19;
mutable int m_constrainedType : 3;
unsigned int m_defaultAlignment : 8;
unsigned int m_orientation : 2;
};
}
EntryData::EntryData( QQuickItem* item )
: m_item( item )
, m_stretch( -1 )
, m_alignment( 0 )
, m_isSpacer( false )
, m_retainSizeWhenHidden( false )
{
}
EntryData::EntryData( qreal spacing )
: m_spacer( spacing )
, m_stretch( -1 )
, m_alignment( 0 )
, m_isSpacer( true )
, m_retainSizeWhenHidden( false )
{
}
EntryData& EntryData::operator=( const EntryData& other )
{
m_isSpacer = other.m_isSpacer;
if ( other.m_isSpacer )
m_spacer = other.m_spacer;
else
m_item = other.m_item;
m_stretch = other.m_stretch;
m_alignment = other.m_alignment;
m_retainSizeWhenHidden = other.m_retainSizeWhenHidden;
return *this;
}
bool EntryData::isIgnored() const
{
if ( !m_isSpacer && !m_retainSizeWhenHidden )
return !qskIsVisibleToParent( m_item );
return false;
}
bool EntryData::isConstrained( Qt::Orientation orientation ) const
{
if ( m_isSpacer )
return false;
switch( QskLayoutConstraint::constraintType( m_item ) )
{
case QskLayoutConstraint::WidthForHeight:
return orientation == Qt::Horizontal;
case QskLayoutConstraint::HeightForWidth:
return orientation == Qt::Vertical;
default:
return false;
}
}
EntryTable::EntryTable( Qt::Orientation orientation, uint dimension )
: m_dimension( dimension )
, m_sumIgnored( -1 )
, m_constrainedType( -1 )
, m_defaultAlignment( Qt::AlignLeft | Qt::AlignVCenter )
, m_orientation( orientation )
{
}
bool EntryTable::setOrientation( Qt::Orientation orientation )
{
if ( m_orientation != orientation )
{
m_orientation = orientation;
return true;
}
return false;
}
Qt::Orientation EntryTable::orientation() const
{
return static_cast< Qt::Orientation >( m_orientation );
}
bool EntryTable::setDimension( uint dimension )
{
if ( m_dimension != dimension )
{
m_dimension = dimension;
return true;
}
return false;
}
uint EntryTable::dimension() const
{
return m_dimension;
}
bool EntryTable::setDefaultAlignment( Qt::Alignment alignment )
{
if ( defaultAlignment() != alignment )
{
m_defaultAlignment = alignment;
return true;
}
return false;
}
Qt::Alignment EntryTable::defaultAlignment() const
{
return static_cast< Qt::Alignment >( m_defaultAlignment );
}
Qt::Alignment EntryTable::effectiveAlignmentAt( int index ) const
{
Qt::Alignment alignment;
if ( const auto entry = entryAt( index ) )
alignment = entry->alignment();
if ( !( alignment & Qt::AlignVertical_Mask ) )
alignment |= ( defaultAlignment() & Qt::AlignVertical_Mask );
if ( !( alignment & Qt::AlignHorizontal_Mask ) )
alignment |= ( defaultAlignment() & Qt::AlignHorizontal_Mask );
return alignment;
}
bool EntryTable::isIgnoredAt( int index ) const
{
if ( index >= 0 && index < count() )
return m_entries[index].isIgnored();
return false;
}
void EntryTable::insertItem( int index, QQuickItem* item )
{
if ( index < 0 || index > count() )
m_entries.emplace_back( item );
else
m_entries.emplace( m_entries.begin() + index, item );
invalidate();
}
void EntryTable::insertSpacer( int index, qreal spacing )
{
spacing = qMax( spacing, static_cast< qreal >( 0.0 ) );
if ( index < 0 || index > count() )
m_entries.emplace_back( spacing );
else
m_entries.emplace( m_entries.begin() + index, spacing );
}
bool EntryTable::removeAt( int index )
{
if ( index >= 0 && index < count() )
{
m_entries.erase( m_entries.begin() + index );
invalidate();
return true;
}
return false;
}
QQuickItem* EntryTable::itemAt( int index ) const
{
if ( const auto entry = entryAt( index ) )
return entry->item();
return nullptr;
}
int EntryTable::spacerAt( int index ) const
{
if ( const auto entry = entryAt( index ) )
return entry->spacer();
return -1;
}
bool EntryTable::setAlignmentAt( int index, Qt::Alignment alignment )
{
if ( auto entry = entryAt( index ) )
{
if ( alignment != entry->alignment() )
entry->setAlignment( alignment );
return true;
}
return false;
}
Qt::Alignment EntryTable::alignmentAt( int index ) const
{
if ( const auto entry = entryAt( index ) )
return entry->alignment();
return Qt::Alignment();
}
bool EntryTable::setStretchFactorAt( int index, int stretchFactor )
{
if ( auto entry = entryAt( index ) )
{
if ( stretchFactor < 0 )
stretchFactor = -1;
if ( entry->stretch() != stretchFactor )
{
entry->setStretch( stretchFactor );
return true;
}
}
return false;
}
int EntryTable::stretchFactorAt( int index ) const
{
if ( const auto entry = entryAt( index ) )
return entry->stretch();
return -1;
}
bool EntryTable::setRetainSizeWhenHiddenAt( int index, bool on )
{
if ( auto entry = entryAt( index ) )
{
if ( on != entry->retainSizeWhenHidden() )
{
const bool isIgnored = entry->isIgnored();
entry->setRetainSizeWhenHidden( on );
if ( isIgnored != entry->isIgnored() )
{
if ( m_sumIgnored >= 0 )
m_sumIgnored += on ? 1 : -1;
return true;
}
}
}
return false;
}
bool EntryTable::retainSizeWhenHiddenAt( int index ) const
{
if ( const auto entry = entryAt( index ) )
return entry->retainSizeWhenHidden();
return false;
}
void EntryTable::invalidate()
{
m_sumIgnored = -1;
m_constrainedType = -1;
}
int EntryTable::effectiveCount() const
{
if ( m_sumIgnored < 0 )
{
m_sumIgnored = 0;
for ( const auto& entry : m_entries )
{
if ( entry.isIgnored() )
m_sumIgnored++;
}
}
return m_entries.size() - m_sumIgnored;
}
int EntryTable::effectiveCount( Qt::Orientation orientation ) const
{
const uint cellCount = effectiveCount();
if ( orientation == m_orientation )
{
return qMin( cellCount, m_dimension );
}
else
{
int count = cellCount / m_dimension;
if ( cellCount % m_dimension )
count++;
return count;
}
}
QskLayoutConstraint::Type EntryTable::constraintType() const
{
if ( m_constrainedType < 0 )
{
m_constrainedType = QskLayoutConstraint::Unconstrained;
for ( const auto& entry : m_entries )
{
const auto itemType = QskLayoutConstraint::constraintType( entry.item() );
if ( itemType != QskLayoutConstraint::Unconstrained )
{
if ( m_constrainedType == QskLayoutConstraint::Unconstrained )
{
m_constrainedType = itemType;
}
else if ( m_constrainedType != itemType )
{
qWarning( "QskLinearLayoutEngine: conflicting constraints");
m_constrainedType = QskLayoutConstraint::Unconstrained;
}
}
}
}
return static_cast< QskLayoutConstraint::Type >( m_constrainedType );
}
CellData EntryTable::cellData( const EntryData& entry,
Qt::Orientation orientation, qreal constraint ) const
{
int stretch = 0;
bool growFlag = true;
qreal minimum, preferred, maximum;
if ( const auto item = entry.item() )
{
const auto policy = QskLayoutConstraint::sizePolicy( item ).policy( orientation );
if ( constraint >= 0.0 )
{
if ( !entry.isConstrained( orientation ) )
constraint = -1.0;
}
const auto expandFlags = QskSizePolicy::GrowFlag | QskSizePolicy::ExpandFlag;
if ( ( policy & QskSizePolicy::ShrinkFlag ) &&
( policy & expandFlags ) && ( policy & QskSizePolicy::IgnoreFlag ) )
{
// we don't need to calculate the preferred size
minimum = QskLayoutConstraint::sizeHint(
item, Qt::MinimumSize, orientation, constraint );
maximum = QskLayoutConstraint::sizeHint(
item, Qt::MaximumSize, orientation, constraint );
preferred = minimum;
}
else
{
preferred = QskLayoutConstraint::sizeHint(
item, Qt::PreferredSize, orientation, constraint );
minimum = maximum = preferred;
if ( policy & QskSizePolicy::ShrinkFlag )
{
minimum = QskLayoutConstraint::sizeHint(
item, Qt::MinimumSize, orientation, constraint );
}
if ( policy & expandFlags )
{
maximum = QskLayoutConstraint::sizeHint(
item, Qt::MaximumSize, orientation, constraint );
}
if ( policy & QskSizePolicy::IgnoreFlag )
preferred = minimum;
}
if ( orientation == m_orientation )
{
if ( entry.stretch() < 0 )
stretch = ( policy & QskSizePolicy::ExpandFlag ) ? 1 : 0;
else
stretch = entry.stretch();
}
growFlag = policy & QskSizePolicy::GrowFlag;
}
else
{
// a spacer
if ( orientation == m_orientation )
{
minimum = preferred = maximum = entry.spacer();
// >= 0 ???
if ( entry.stretch() > 0 )
maximum = QskLayoutConstraint::unlimited;
stretch = qMax( entry.stretch(), 0 );
}
else
{
minimum = 0.0;
preferred = 0.0;
maximum = QskLayoutConstraint::unlimited;
}
}
CellData cellData;
cellData.hint = QskLayoutHint( minimum, preferred, maximum );
cellData.stretch = stretch;
cellData.growFlag = growFlag;
return cellData;
}
void EntryTable::updateCellTable( Qt::Orientation orientation,
const QVector< Range >& constraints, CellTable& cellTable ) const
{
const auto count = effectiveCount( orientation );
const qreal constraint =
constraints.isEmpty() ? -1.0 : constraints.last().end();
if ( ( cellTable.constraint() == constraint )
&& ( cellTable.count() == count ) )
{
return; // already up to date
}
cellTable.reset( count, constraint );
uint index1 = 0;
uint index2 = 0;
for ( const auto& entry : m_entries )
{
if ( entry.isIgnored() )
continue;
const qreal cellConstraint =
constraints.isEmpty() ? -1.0 : constraints[index1].length;
const auto data = cellData( entry, orientation, cellConstraint );
cellTable.addCellData( index2, data );
if ( m_orientation != orientation )
{
if ( ++index1 == m_dimension )
{
index1 = 0;
index2++;
}
}
else
{
if ( ++index2 == m_dimension )
{
index2 = 0;
index1++;
}
}
}
cellTable.finish();
}
// ---------
static inline void qskUpdateCellTable( Qt::Orientation orientation,
const QVector< Range >& constraints,
const EntryTable& entryTable, CellTable& cellTable )
{
entryTable.updateCellTable( orientation, constraints, cellTable );
}
static inline void qskUpdateCellTable( Qt::Orientation orientation,
const EntryTable& entryTable, CellTable& cellTable )
{
entryTable.updateCellTable( orientation, QVector< Range >(), cellTable );
}
class QskLinearLayoutEngine::PrivateData
{
public:
PrivateData( Qt::Orientation orientation, uint dimension )
: entryTable( orientation, dimension )
, blockInvalidate( false )
{
rowTable.setSpacing( qskDefaultSpacing() );
colTable.setSpacing( qskDefaultSpacing() );
}
EntryTable entryTable;
Qt::LayoutDirection visualDirection = Qt::LeftToRight;
Qt::Edges extraSpacingAt;
CellTable colTable;
CellTable rowTable;
CellGeometries geometries;
/*
Some weired controls do lazy updates inside of their sizeHint calculation
that lead to LayoutRequest events. While being in the process of
updating the tables we can't - and don't need to - handle invalidations
because of them.
*/
bool blockInvalidate : 1;
};
QskLinearLayoutEngine::QskLinearLayoutEngine( Qt::Orientation orientation, uint dimension )
: m_data( new PrivateData( orientation, dimension ) )
{
}
QskLinearLayoutEngine::~QskLinearLayoutEngine()
{
}
void QskLinearLayoutEngine::setOrientation( Qt::Orientation orientation )
{
if ( m_data->entryTable.setOrientation( orientation ) )
invalidate( CellCache | LayoutCache );
}
Qt::Orientation QskLinearLayoutEngine::orientation() const
{
return m_data->entryTable.orientation();
}
void QskLinearLayoutEngine::setDimension( uint dimension )
{
if ( dimension < 1 )
dimension = 1;
if ( m_data->entryTable.setDimension( dimension ) )
invalidate( CellCache | LayoutCache );
}
uint QskLinearLayoutEngine::dimension() const
{
return m_data->entryTable.dimension();
}
int QskLinearLayoutEngine::count() const
{
return m_data->entryTable.count();
}
int QskLinearLayoutEngine::rowCount() const
{
return m_data->entryTable.effectiveCount( Qt::Vertical );
}
int QskLinearLayoutEngine::columnCount() const
{
return m_data->entryTable.effectiveCount( Qt::Horizontal );
}
void QskLinearLayoutEngine::setRetainSizeWhenHiddenAt( int index, bool on )
{
if ( m_data->entryTable.setRetainSizeWhenHiddenAt( index, on ) )
invalidate( CellCache | LayoutCache );
}
bool QskLinearLayoutEngine::retainSizeWhenHiddenAt( int index ) const
{
return m_data->entryTable.retainSizeWhenHiddenAt( index );
}
void QskLinearLayoutEngine::setStretchFactorAt( int index, int stretchFactor )
{
if ( m_data->entryTable.setStretchFactorAt( index, stretchFactor ) )
invalidate( CellCache | LayoutCache );
}
int QskLinearLayoutEngine::stretchFactorAt( int index ) const
{
return m_data->entryTable.stretchFactorAt( index );
}
void QskLinearLayoutEngine::setAlignmentAt( int index, Qt::Alignment alignment )
{
m_data->entryTable.setAlignmentAt( index, alignment );
}
Qt::Alignment QskLinearLayoutEngine::alignmentAt( int index ) const
{
return m_data->entryTable.alignmentAt( index );
}
void QskLinearLayoutEngine::setSpacing( qreal spacing, Qt::Orientations orientations )
{
if ( spacing < 0.0 )
spacing = 0.0;
bool doInvalidate = false;
if ( orientations & Qt::Horizontal )
doInvalidate |= m_data->colTable.setSpacing( spacing );
if ( orientations & Qt::Vertical )
doInvalidate |= m_data->rowTable.setSpacing( spacing );
if ( doInvalidate )
invalidate( CellCache | LayoutCache );
}
qreal QskLinearLayoutEngine::spacing( Qt::Orientation orientation ) const
{
if ( orientation == Qt::Horizontal )
return m_data->colTable.spacing();
else
return m_data->rowTable.spacing();
}
void QskLinearLayoutEngine::setExtraSpacingAt( Qt::Edges edges )
{
if ( edges == m_data->extraSpacingAt )
return;
m_data->extraSpacingAt = edges;
Qt::Edges colEdges = edges & ~( Qt::TopEdge | Qt::BottomEdge );
m_data->colTable.setExtraSpacingAt( colEdges );
/*
FlowLayoutInfo does not have an orientation, so we always
set the position for potential fill spaces using Left/Right.
Maybe introducing another enum might be a good idea. TODO ...
*/
Qt::Edges rowEdges;
if ( edges & Qt::TopEdge )
rowEdges |= Qt::LeftEdge;
if ( edges & Qt::BottomEdge )
rowEdges |= Qt::RightEdge;
m_data->rowTable.setExtraSpacingAt( rowEdges );
invalidate( LayoutCache );
}
Qt::Edges QskLinearLayoutEngine::extraSpacingAt() const
{
return m_data->extraSpacingAt;
}
void QskLinearLayoutEngine::setDefaultAlignment( Qt::Alignment alignment )
{
m_data->entryTable.setDefaultAlignment( alignment );
}
Qt::Alignment QskLinearLayoutEngine::defaultAlignment() const
{
return m_data->entryTable.defaultAlignment();
}
void QskLinearLayoutEngine::insertItem( QQuickItem* item, int index )
{
m_data->entryTable.insertItem( index, item );
invalidate( CellCache | LayoutCache );
}
void QskLinearLayoutEngine::addItem( QQuickItem* item )
{
insertItem( item, -1 );
}
void QskLinearLayoutEngine::insertSpacerAt( int index, qreal spacing )
{
m_data->entryTable.insertSpacer( index, spacing );
invalidate( CellCache | LayoutCache );
}
void QskLinearLayoutEngine::addSpacer( qreal spacing )
{
insertSpacerAt( -1, spacing );
}
void QskLinearLayoutEngine::removeAt( int index )
{
if ( m_data->entryTable.removeAt( index ) )
invalidate( CellCache | LayoutCache );
}
QQuickItem* QskLinearLayoutEngine::itemAt( int index ) const
{
return m_data->entryTable.itemAt( index );
}
int QskLinearLayoutEngine::spacerAt( int index ) const
{
return m_data->entryTable.spacerAt( index );
}
void QskLinearLayoutEngine::invalidate( int what )
{
if ( m_data->blockInvalidate )
return;
if ( what & EntryCache )
m_data->entryTable.invalidate();
if ( what & CellCache )
{
m_data->rowTable.invalidate();
m_data->colTable.invalidate();
}
if ( what & LayoutCache )
m_data->geometries.invalidate();
}
void QskLinearLayoutEngine::setGeometries( const QRectF& rect )
{
if ( m_data->entryTable.count() == 0 )
return;
if ( m_data->geometries.boundingSize != rect.size() )
{
m_data->blockInvalidate = true;
updateCellGeometries( rect.size() );
m_data->blockInvalidate = false;
}
/*
In case we have items that send LayoutRequest events on
geometry changes - what doesn't make much sense - we
better make a ( implicitely shared ) copy of the geometries.
*/
const auto geometries = m_data->geometries;
uint row = 0;
uint col = 0;
const auto& entryTable = m_data->entryTable;
for ( int i = 0; i < entryTable.count(); i++ )
{
if ( entryTable.isIgnoredAt( i ) )
continue;
if ( auto item = entryTable.itemAt( i ) )
{
QRectF r = geometries.geometryAt( row, col );
r.translate( rect.x(), rect.y() );
const auto alignment = entryTable.effectiveAlignmentAt( i );
r = QskLayoutConstraint::itemRect( item, r, alignment );
if ( m_data->visualDirection == Qt::RightToLeft )
r.moveRight( rect.right() - ( r.left() - rect.left() ) );
qskSetItemGeometry( item, r );
}
if ( entryTable.orientation() == Qt::Horizontal )
{
if ( ++col == entryTable.dimension() )
{
col = 0;
row++;
}
}
else
{
if ( ++row == entryTable.dimension() )
{
row = 0;
col++;
}
}
}
}
QSizeF QskLinearLayoutEngine::sizeHint( Qt::SizeHint which, const QSizeF& constraint ) const
{
const auto& entryTable = m_data->entryTable;
if ( entryTable.effectiveCount() == 0 )
return QSizeF( 0.0, 0.0 );
const auto constraintType = m_data->entryTable.constraintType();
auto& colTable = m_data->colTable;
auto& rowTable = m_data->rowTable;
m_data->blockInvalidate = true;
if ( ( constraint.width() >= 0 ) &&
( constraintType == QskLayoutConstraint::HeightForWidth ) )
{
qskUpdateCellTable( Qt::Horizontal, entryTable, colTable );
const auto cellConstraints = colTable.cellRanges( constraint.width() );
qskUpdateCellTable( Qt::Vertical, cellConstraints, entryTable, rowTable );
}
else if ( ( constraint.height() >= 0 ) &&
( constraintType == QskLayoutConstraint::WidthForHeight ) )
{
qskUpdateCellTable( Qt::Vertical, entryTable, rowTable );
const auto cellConstraints = rowTable.cellRanges( constraint.height() );
qskUpdateCellTable( Qt::Horizontal, cellConstraints, entryTable, colTable );
}
else
{
qskUpdateCellTable( Qt::Horizontal, entryTable, colTable );
qskUpdateCellTable( Qt::Vertical, entryTable, rowTable );
}
m_data->blockInvalidate = false;
const qreal width = colTable.boundingHint().size( which );
const qreal height = rowTable.boundingHint().size( which );
return QSizeF( width, height );
}
qreal QskLinearLayoutEngine::widthForHeight( qreal height ) const
{
const QSizeF constraint( -1, height );
return sizeHint( Qt::PreferredSize, constraint ).width();
}
qreal QskLinearLayoutEngine::heightForWidth( qreal width ) const
{
const QSizeF constraint( width, -1 );
return sizeHint( Qt::PreferredSize, constraint ).height();
}
void QskLinearLayoutEngine::setVisualDirection(Qt::LayoutDirection direction)
{
m_data->visualDirection = direction;
}
Qt::LayoutDirection QskLinearLayoutEngine::visualDirection() const
{
return m_data->visualDirection;
}
void QskLinearLayoutEngine::updateCellGeometries( const QSizeF& size )
{
auto& geometries = m_data->geometries;
geometries.boundingSize = size;
auto& colTable = m_data->colTable;
auto& rowTable = m_data->rowTable;
auto& entryTable = m_data->entryTable;
const QVector< Range > noConstraints;
switch( entryTable.constraintType() )
{
case QskLayoutConstraint::WidthForHeight:
{
qskUpdateCellTable( Qt::Vertical, entryTable, rowTable );
geometries.rows = rowTable.cellRanges( size.height() );
qskUpdateCellTable( Qt::Horizontal, geometries.rows, entryTable, colTable );
geometries.columns = colTable.cellRanges( size.width() );
break;
}
case QskLayoutConstraint::HeightForWidth:
{
qskUpdateCellTable( Qt::Horizontal, entryTable, colTable );
geometries.columns = colTable.cellRanges( size.width() );
qskUpdateCellTable( Qt::Vertical, geometries.columns, entryTable, rowTable );
geometries.rows = rowTable.cellRanges( size.height() );
break;
}
default:
{
qskUpdateCellTable( Qt::Horizontal, entryTable, colTable );
geometries.columns = colTable.cellRanges( size.width() );
qskUpdateCellTable( Qt::Vertical, entryTable, rowTable );
geometries.rows = rowTable.cellRanges( size.height() );
}
}
}
qreal QskLinearLayoutEngine::defaultSpacing( Qt::Orientation ) const
{
return qskDefaultSpacing();
}