qskinny/src/layouts/QskGridLayoutEngine.cpp
2019-09-20 07:06:52 +02:00

655 lines
16 KiB
C++

/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#include "QskGridLayoutEngine.h"
#include "QskLayoutHint.h"
#include "QskLayoutChain.h"
#include "QskSizePolicy.h"
#include "QskQuick.h"
#include <qvector.h>
#include <vector>
#include <functional>
static inline qreal qskSegmentLength(
const QskLayoutChain::Segments& s, int start, int end )
{
return s[ end ].start - s[ start ].start + s[ end ].length;
}
namespace
{
class Settings
{
public:
class Setting
{
public:
inline bool isDefault() const
{
return m_stretch < 0 && m_hint.isDefault();
}
bool setStretch( int stretch )
{
if ( stretch != m_stretch )
{
m_stretch = stretch;
return true;
}
return false;
}
bool setHint( Qt::SizeHint which, qreal size )
{
if ( size != m_hint.size( which ) )
{
m_hint.setSize( which, size );
return true;
}
return false;
}
QskLayoutChain::CellData cell() const
{
QskLayoutChain::CellData cell;
cell.hint = m_hint.normalized();
cell.stretch = m_stretch;
cell.canGrow = m_stretch != 0;
cell.isValid = true;
return cell;
}
inline int stretch() const { return m_stretch; }
inline QskLayoutHint hint() const { return m_hint; }
int position = -1;
private:
int m_stretch = -1;
QskLayoutHint m_hint;
};
void clear()
{
m_settings.clear();
}
bool setStretchAt( int index, int stretch )
{
auto setStretch = [stretch]( Setting& s )
{ return s.setStretch( stretch ); };
return setValueAt( index, setStretch );
}
bool setHintAt( int index, Qt::SizeHint which, qreal size )
{
auto setHint = [which, size]( Setting& s )
{ return s.setHint( which, size ); };
return setValueAt( index, setHint );
}
Setting settingAt( int index ) const
{
auto it = lowerBound( index );
if ( it != m_settings.end() )
return *it;
return Setting();
}
const std::vector< Setting >& settings() const { return m_settings; }
private:
inline bool setValueAt( int pos,
const std::function< bool( Setting& ) > modify )
{
if ( pos < 0 )
return false;
bool isModified;
auto it = lowerBound( pos );
if ( it != m_settings.end() && it->position == pos )
{
isModified = modify( *it );
if ( isModified && it->isDefault() )
m_settings.erase( it );
}
else
{
Setting setting;
isModified = modify( setting );
if ( isModified )
{
setting.position = pos;
m_settings.insert( it, setting );
}
}
return isModified;
}
inline std::vector< Setting >::iterator lowerBound( int index ) const
{
auto cmp = []( const Setting& setting, const int& pos )
{ return setting.position < pos; };
auto& settings = const_cast< std::vector< Setting >& >( m_settings );
return std::lower_bound( settings.begin(), settings.end(), index, cmp );
}
std::vector< Setting > m_settings; // a flat map
};
}
namespace
{
class Element
{
public:
Element( QQuickItem*, const QRect& );
Element( const QSizeF& spacing, const QRect& );
Element& operator=( const Element& );
QSizeF spacing() const;
QQuickItem* item() const;
QRect grid() const;
void setGrid( const QRect& );
QRect minimumGrid() const;
bool isIgnored() const;
QskLayoutChain::CellData cell( Qt::Orientation ) const;
void transpose();
private:
union
{
QQuickItem* m_item;
QSizeF m_spacing;
};
QRect m_grid;
bool m_isSpacer;
};
}
Element::Element( QQuickItem* item, const QRect& grid )
: m_item( item )
, m_grid( grid )
, m_isSpacer( false )
{
}
Element::Element( const QSizeF& spacing, const QRect& grid )
: m_spacing( spacing )
, m_grid( grid )
, m_isSpacer( true )
{
}
Element& Element::operator=( const Element& other )
{
m_isSpacer = other.m_isSpacer;
if ( other.m_isSpacer )
m_spacing = other.m_spacing;
else
m_item = other.m_item;
m_grid = other.m_grid;
return *this;
}
inline QSizeF Element::spacing() const
{
return m_isSpacer ? m_spacing : QSizeF();
}
inline QQuickItem* Element::item() const
{
return m_isSpacer ? nullptr : m_item;
}
QRect Element::grid() const
{
return m_grid;
}
void Element::setGrid( const QRect& grid )
{
m_grid = grid;
}
QRect Element::minimumGrid() const
{
return QRect( m_grid.left(), m_grid.top(),
qMax( m_grid.width(), 1 ), qMax( m_grid.height(), 1 ) );
}
bool Element::isIgnored() const
{
return !( m_isSpacer || qskIsVisibleToLayout( m_item ) );
}
QskLayoutChain::CellData Element::cell( Qt::Orientation orientation ) const
{
QskLayoutChain::CellData cell;
cell.isValid = true;
if ( m_isSpacer )
{
const qreal value = ( orientation == Qt::Horizontal )
? m_spacing.width() : m_spacing.height();
cell.hint.setMinimum( value );
cell.hint.setPreferred( value );
cell.hint.setMaximum( value );
}
else
{
const auto policy = qskSizePolicy( m_item ).policy( orientation );
cell.canGrow = policy & QskSizePolicy::GrowFlag;
if ( policy & QskSizePolicy::ExpandFlag )
cell.stretch = 1;
}
return cell;
}
void Element::transpose()
{
m_grid.setRect( m_grid.top(), m_grid.left(),
m_grid.height(), m_grid.width() );
}
class QskGridLayoutEngine::PrivateData
{
public:
inline Element* elementAt( int index ) const
{
const int count = this->elements.size();
if ( index < 0 || index >= count )
return nullptr;
return const_cast< Element* >( &this->elements[index] );
}
int insertElement( QQuickItem* item, QSizeF spacing, QRect grid )
{
// -1 means unlimited, while 0 does not make any sense
if ( grid.width() == 0 )
grid.setWidth( 1 );
if ( grid.height() == 0 )
grid.setHeight( 1 );
if ( item )
{
elements.push_back( Element( item, grid ) );
}
else
{
if ( spacing.width() < 0.0 )
spacing.setWidth( 0.0 );
if ( spacing.height() < 0.0 )
spacing.setHeight( 0.0 );
elements.push_back( Element( spacing, grid ) );
}
grid = effectiveGrid( elements.back() );
rowCount = qMax( rowCount, grid.bottom() + 1 );
columnCount = qMax( columnCount, grid.right() + 1 );
return this->elements.size() - 1;
}
QRect effectiveGrid( const Element& element ) const
{
QRect r = element.grid();
if ( r.width() <= 0 )
r.setRight( qMax( this->columnCount - 1, r.left() ) );
if ( r.height() <= 0 )
r.setBottom( qMax( this->rowCount - 1, r.top() ) );
return r;
}
Settings& settings( Qt::Orientation orientation ) const
{
auto that = const_cast< PrivateData* >( this );
return ( orientation == Qt::Horizontal )
? that->columnSettings : that->rowSettings;
}
std::vector< Element > elements;
Settings rowSettings;
Settings columnSettings;
int rowCount = 0;
int columnCount = 0;
};
QskGridLayoutEngine::QskGridLayoutEngine()
: m_data( new PrivateData() )
{
}
QskGridLayoutEngine::~QskGridLayoutEngine()
{
}
int QskGridLayoutEngine::count() const
{
return m_data->elements.size();
}
bool QskGridLayoutEngine::setStretchFactor(
int pos, int stretch, Qt::Orientation orientation )
{
if ( pos < 0 )
return false;
if ( stretch < 0 )
stretch = -1;
if ( m_data->settings( orientation ).setStretchAt( pos, stretch ) )
{
invalidate();
return true;
}
return false;
}
int QskGridLayoutEngine::stretchFactor(
int pos, Qt::Orientation orientation ) const
{
const auto setting = m_data->settings( orientation ).settingAt( pos );
return ( setting.position == pos ) ? setting.stretch() : 0;
}
bool QskGridLayoutEngine::setRowSizeHint(
int row, Qt::SizeHint which, qreal height )
{
if ( m_data->rowSettings.setHintAt( row, which, height ) )
{
invalidate();
return true;
}
return false;
}
qreal QskGridLayoutEngine::rowSizeHint( int row, Qt::SizeHint which ) const
{
const auto& settings = m_data->rowSettings;
return settings.settingAt( row ).hint().size( which );
}
bool QskGridLayoutEngine::setColumnSizeHint(
int column, Qt::SizeHint which, qreal width )
{
if ( m_data->columnSettings.setHintAt( column, which, width ) )
{
invalidate();
return true;
}
return false;
}
qreal QskGridLayoutEngine::columnSizeHint( int column, Qt::SizeHint which ) const
{
const auto& settings = m_data->columnSettings;
return settings.settingAt( column ).hint().size( which );
}
int QskGridLayoutEngine::insertItem( QQuickItem* item, const QRect& grid )
{
invalidate();
return m_data->insertElement( item, QSizeF(), grid );
}
int QskGridLayoutEngine::insertSpacer( const QSizeF& spacing, const QRect& grid )
{
return m_data->insertElement( nullptr, spacing, grid );
}
bool QskGridLayoutEngine::removeAt( int index )
{
const auto element = m_data->elementAt( index );
if ( element == nullptr )
return false;
const auto grid = element->minimumGrid();
auto& elements = m_data->elements;
elements.erase( elements.begin() + index );
// doing a lazy recalculation instead ??
if ( grid.bottom() >= m_data->rowCount
|| grid.right() >= m_data->columnCount )
{
int maxRow = -1;
int maxColumn = -1;
for ( const auto& element : elements )
{
const auto grid = element.minimumGrid();
maxRow = qMax( maxRow, grid.bottom() );
maxColumn = qMax( maxColumn, grid.right() );
}
m_data->rowCount = maxRow + 1;
m_data->columnCount = maxColumn + 1;
}
invalidate();
return true;
}
bool QskGridLayoutEngine::clear()
{
m_data->elements.clear();
m_data->rowSettings.clear();
m_data->columnSettings.clear();
invalidate();
return true;
}
int QskGridLayoutEngine::indexAt( int row, int column ) const
{
if ( row < m_data->rowCount && column < m_data->columnCount )
{
for ( uint i = 0; i < m_data->elements.size(); i++ )
{
const auto grid = m_data->effectiveGrid( m_data->elements[i] );
if ( grid.contains( column, row ) )
return i;
}
}
return -1;
}
QQuickItem* QskGridLayoutEngine::itemAt( int index ) const
{
if ( const auto element = m_data->elementAt( index ) )
return element->item();
return nullptr;
}
QSizeF QskGridLayoutEngine::spacerAt( int index ) const
{
if ( const auto element = m_data->elementAt( index ) )
return element->spacing();
return QSizeF();
}
QQuickItem* QskGridLayoutEngine::itemAt( int row, int column ) const
{
return itemAt( indexAt( row, column ) );
}
bool QskGridLayoutEngine::setGridAt( int index, const QRect& grid )
{
if ( auto element = m_data->elementAt( index ) )
{
if ( element->grid() != grid )
{
element->setGrid( grid );
invalidate();
return true;
}
}
return false;
}
QRect QskGridLayoutEngine::gridAt( int index ) const
{
if ( auto element = m_data->elementAt( index ) )
return element->grid();
return QRect();
}
QRect QskGridLayoutEngine::effectiveGridAt( int index ) const
{
if ( auto element = m_data->elementAt( index ) )
return m_data->effectiveGrid( *element );
return QRect();
}
void QskGridLayoutEngine::invalidateElementCache()
{
}
void QskGridLayoutEngine::layoutItems()
{
for ( const auto& element : m_data->elements )
{
auto item = element.item();
if ( item && qskIsVisibleToParent( item ) )
{
const auto grid = m_data->effectiveGrid( element );
layoutItem( item, grid );
}
}
}
void QskGridLayoutEngine::transpose()
{
for ( auto& element : m_data->elements )
element.transpose();
qSwap( m_data->columnSettings, m_data->rowSettings );
invalidate();
}
int QskGridLayoutEngine::effectiveCount(
Qt::Orientation orientation ) const
{
return ( orientation == Qt::Horizontal )
? m_data->columnCount : m_data->rowCount;
}
void QskGridLayoutEngine::setupChain( Qt::Orientation orientation,
const QskLayoutChain::Segments& constraints, QskLayoutChain& chain ) const
{
/*
We collect all information from the simple elements first
before adding those that occupy more than one cell
*/
QVarLengthArray< const Element* > postponed;
postponed.reserve( m_data->elements.size() );
for ( const auto& element : m_data->elements )
{
if ( element.isIgnored() )
continue;
auto grid = m_data->effectiveGrid( element );
if ( orientation == Qt::Horizontal )
grid.setRect( grid.y(), grid.x(), grid.height(), grid.width() );
if ( grid.height() == 1 )
{
qreal constraint = -1.0;
if ( !constraints.isEmpty() )
constraint = qskSegmentLength( constraints, grid.left(), grid.right() );
auto cell = element.cell( orientation );
if ( element.item() )
cell.hint = layoutHint( element.item(), orientation, constraint );
chain.expandCell( grid.top(), cell );
}
else
{
postponed += &element;
}
}
const auto& settings = m_data->settings( orientation );
for ( const auto& setting : settings.settings() )
{
if ( setting.position >= chain.count() )
{
qWarning() << "Extra settings for exceeding rows/columns not yet implemented.";
continue;
}
chain.shrinkCell( setting.position, setting.cell() );
}
for ( const auto element : postponed )
{
auto grid = m_data->effectiveGrid( *element );
if ( orientation == Qt::Horizontal )
grid.setRect( grid.y(), grid.x(), grid.height(), grid.width() );
qreal constraint = -1.0;
if ( !constraints.isEmpty() )
constraint = qskSegmentLength( constraints, grid.left(), grid.right() );
auto cell = element->cell( orientation );
cell.hint = layoutHint( element->item(), orientation, constraint );
chain.expandCells( grid.top(), grid.height(), cell );
}
}