/****************************************************************************** * QSkinny - Copyright (C) 2016 Uwe Rathmann * This file may be used under the terms of the QSkinny License, Version 1.0 *****************************************************************************/ #include "QskLayoutConstraint.h" #include "QskControl.h" #include "QskSizePolicy.h" #include "QskLayoutHint.h" #include "QskQuick.h" #include "QskFunctions.h" #include static inline qreal qskHintFor( const QQuickItem* item, const char* method, qreal widthOrHeight ) { if ( item->metaObject()->indexOfMethod( method ) >= 0 ) { qreal value; ( void ) QMetaObject::invokeMethod( const_cast< QQuickItem* >( item ), method, Qt::DirectConnection, Q_RETURN_ARG( qreal, value ), Q_ARG( qreal, widthOrHeight ) ); return value; } return -1; } static inline bool qskHasHintFor( const QQuickItem* item, const char* method ) { if ( item->metaObject()->indexOfMethod( method ) >= 0 ) { bool enabled; ( void ) QMetaObject::invokeMethod( const_cast< QQuickItem* >( item ), method, Qt::DirectConnection, Q_RETURN_ARG( bool, enabled ) ); return enabled; } return false; } static inline QSizeF qskEffectiveSizeHint( const QQuickItem* item, Qt::SizeHint whichHint ) { if ( auto control = qskControlCast( item ) ) return control->effectiveSizeHint( whichHint ); QSizeF hint( -1.0, -1.0 ); // no hint const char* properties[] = { "minimumSize", "preferredSize", "maximumSize" }; const QVariant v = item->property( properties[ whichHint ] ); if ( v.canConvert( QMetaType::QSizeF ) ) hint = v.toSizeF(); switch ( whichHint ) { case Qt::MinimumSize: { if ( hint.width() < 0 ) hint.setWidth( 0.0 ); if ( hint.height() < 0 ) hint.setHeight( 0.0 ); break; } case Qt::PreferredSize: { if ( hint.width() < 0 ) hint.setWidth( item->implicitWidth() ); if ( hint.height() < 0 ) hint.setHeight( item->implicitHeight() ); break; } case Qt::MaximumSize: { if ( hint.width() < 0 ) hint.setWidth( QskLayoutConstraint::unlimited ); if ( hint.height() < 0 ) hint.setHeight( QskLayoutConstraint::unlimited ); break; } default: break; } return hint; } QskLayoutConstraint::Type QskLayoutConstraint::constraintType( const QQuickItem* item ) { if ( item == nullptr ) return Unconstrained; Type constraintType = Unconstrained; if ( auto control = qskControlCast( item ) ) { const auto policy = control->sizePolicy(); if ( policy.horizontalPolicy() == QskSizePolicy::Constrained ) { constraintType = WidthForHeight; } else if ( policy.verticalPolicy() == QskSizePolicy::Constrained ) { constraintType = HeightForWidth; } } else { if ( qskHasHintFor( item, "hasWidthForHeight" ) ) { constraintType = WidthForHeight; } else if ( qskHasHintFor( item, "hasHeightForWidth" ) ) { constraintType = HeightForWidth; } } return constraintType; } bool QskLayoutConstraint::isConstrained( const QQuickItem* item, Qt::Orientation orientation ) { switch( constraintType( item ) ) { case QskLayoutConstraint::WidthForHeight: return orientation == Qt::Horizontal; case QskLayoutConstraint::HeightForWidth: return orientation == Qt::Vertical; default: return false; } } qreal QskLayoutConstraint::heightForWidth( const QQuickItem* item, qreal width ) { if ( auto control = qskControlCast( item ) ) return control->heightForWidth( width ); return qskHintFor( item, "heightForWidth", width ); } qreal QskLayoutConstraint::widthForHeight( const QQuickItem* item, qreal height ) { if ( auto control = qskControlCast( item ) ) return control->widthForHeight( height ); return qskHintFor( item, "widthForHeight", height ); } qreal QskLayoutConstraint::constrainedMetric( Type type, const QskControl* control, qreal widthOrHeight, std::function< qreal( Type, const QskControl*, qreal ) > constrainFunction ) { #if 1 /* In case of having a corner radius of Qt::RelativeSize we might have a wrong result when using QskLayoutConstraint::unlimited. No idea how to solve this in a generic way: TODO ... */ #endif const qreal upperLimit = 1e6; if ( type == WidthForHeight ) { const QSizeF outer( upperLimit, widthOrHeight ); const QSizeF inner = control->layoutRectForSize( outer ).size(); qreal width = constrainFunction( type, control, inner.height() ); if ( width >= 0.0 ) width += outer.width() - inner.width(); return width; } else { const QSizeF outer( widthOrHeight, upperLimit ); const QSizeF inner = control->layoutRectForSize( outer ).size(); qreal height = constrainFunction( type, control, inner.width() ); if ( height >= 0.0 ) height += outer.height() - inner.height(); return height; } } qreal QskLayoutConstraint::constrainedChildrenMetric( Type type, const QskControl* control, qreal constraint ) { auto constrainFunction = ( type == WidthForHeight ) ? widthForHeight : heightForWidth; qreal constrainedValue = -1.0; const auto children = control->childItems(); for ( auto child : children ) { if ( !qskIsTransparentForPositioner( child ) ) { const auto v = constrainFunction( child, constraint ); if ( v > constrainedValue ) constrainedValue = v; } } return constrainedValue; } QskSizePolicy QskLayoutConstraint::sizePolicy( const QQuickItem* item ) { if ( item ) { if ( auto control = qskControlCast( item ) ) return control->sizePolicy(); const QVariant v = item->property( "sizePolicy" ); if ( v.canConvert< QskSizePolicy >() ) return qvariant_cast< QskSizePolicy >( v ); } return QskSizePolicy( QskSizePolicy::Preferred, QskSizePolicy::Preferred ); } QSizeF QskLayoutConstraint::boundedSize( const QQuickItem* item, const QSizeF& size ) { qreal width, height; switch( constraintType( item ) ) { case WidthForHeight: { const auto hintV = layoutHint( item, Qt::Vertical, -1 ); height = qBound( hintV.minimum(), size.height(), hintV.maximum() ); const auto hintH = layoutHint( item, Qt::Horizontal, height ); width = qBound( hintH.minimum(), size.width(), hintH.maximum() ); break; } case HeightForWidth: { const auto hintH = layoutHint( item, Qt::Horizontal, -1 ); width = qBound( hintH.minimum(), size.width(), hintH.maximum() ); const auto hintV = layoutHint( item, Qt::Vertical, width ); height = qBound( hintV.minimum(), size.height(), hintV.maximum() ); break; } default: { const auto hintH = layoutHint( item, Qt::Horizontal, -1 ); const auto hintV = layoutHint( item, Qt::Vertical, -1 ); width = qBound( hintH.minimum(), size.width(), hintH.maximum() ); height = qBound( hintV.minimum(), size.height(), hintV.maximum() ); } } return QSizeF( width, height ); } qreal QskLayoutConstraint::sizeHint( const QQuickItem* item, Qt::SizeHint whichHint, Qt::Orientation orientation, qreal constraint ) { if ( orientation == Qt::Horizontal ) return sizeHint( item, whichHint, QSizeF( -1.0, constraint ) ).width(); else return sizeHint( item, whichHint, QSizeF( constraint, -1.0 ) ).height(); } QSizeF QskLayoutConstraint::sizeHint( const QQuickItem* item, Qt::SizeHint whichHint, const QSizeF& constraint ) { if ( item == nullptr || whichHint < Qt::MinimumSize || whichHint > Qt::MaximumSize ) return QSizeF( 0, 0 ); if ( constraint.isValid() ) return constraint; QSizeF hint( 0, 0 ); Type constraintType = Unconstrained; if ( whichHint == Qt::PreferredSize ) constraintType = QskLayoutConstraint::constraintType( item ); if ( constraintType != Unconstrained ) { const quint32 growFlags = QskSizePolicy::GrowFlag | QskSizePolicy::ExpandFlag; if ( constraint.width() > 0 ) // && constrainedType == HeightForWidth ?? { qreal w = constraint.width(); if ( !( sizePolicy( item ).policy( Qt::Horizontal ) & growFlags ) ) { const auto maxW = qskEffectiveSizeHint( item, Qt::PreferredSize ).width(); if ( maxW >= 0.0 ) w = qMin( w, maxW ); } hint.setWidth( w ); hint.setHeight( heightForWidth( item, w ) ); } else if ( constraint.height() > 0 ) // && constrainedType == WidthForHeight ?? { qreal h = constraint.height(); if ( !( sizePolicy( item ).policy( Qt::Vertical ) & growFlags ) ) { const auto maxH = qskEffectiveSizeHint( item, Qt::PreferredSize ).height(); if ( maxH >= 0.0 ) h = qMin( h, maxH ); } hint.setWidth( widthForHeight( item, h ) ); hint.setHeight( h ); } else { hint = qskEffectiveSizeHint( item, Qt::PreferredSize ); if ( constraintType == WidthForHeight ) hint.setWidth( widthForHeight( item, hint.height() ) ); else hint.setHeight( heightForWidth( item, hint.width() ) ); } } else { hint = qskEffectiveSizeHint( item, whichHint ); } hint = hint.expandedTo( QSizeF( 0.0, 0.0 ) ); return hint; } QRectF QskLayoutConstraint::boundedRect( const QQuickItem* item, const QRectF& rect, Qt::Alignment alignment ) { auto size = boundedSize( item, rect.size() ); #if 0 size = size.boundedTo( rect.size() ); // ignoring minimumSize #endif return qskAlignedRectF( rect, size.width(), size.height(), alignment ); } QskLayoutHint QskLayoutConstraint::layoutHint( const QQuickItem* item, Qt::Orientation orientation, qreal constraint ) { if ( item == nullptr ) return QskLayoutHint(); const auto policy = sizePolicy( item ).policy( orientation ); if ( constraint >= 0.0 ) { if ( !isConstrained( item, orientation ) ) constraint = -1.0; } qreal minimum, preferred, maximum; 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 = sizeHint( item, Qt::MinimumSize, orientation, constraint ); maximum = sizeHint( item, Qt::MaximumSize, orientation, constraint ); preferred = minimum; } else { preferred = sizeHint( item, Qt::PreferredSize, orientation, constraint ); if ( policy & QskSizePolicy::ShrinkFlag ) minimum = sizeHint( item, Qt::MinimumSize, orientation, constraint ); else minimum = preferred; if ( policy & expandFlags ) maximum = sizeHint( item, Qt::MaximumSize, orientation, constraint ); else maximum = preferred; if ( policy & QskSizePolicy::IgnoreFlag ) preferred = minimum; } return QskLayoutHint( minimum, preferred, maximum ); } static const char s_alignmentProperty[] = "layoutAlignmentHint"; static const char s_retainSizeWhenHiddenProperty[] = "layoutRetainSizeWhenHidden"; Qt::Alignment QskLayoutConstraint::layoutAlignmentHint( const QQuickItem* item ) { if ( auto control = qskControlCast( item ) ) { return control->layoutAlignmentHint(); } else if ( item ) { const QVariant v = item->property( s_alignmentProperty ); if ( v.canConvert< Qt::Alignment >() ) return v.value< Qt::Alignment >(); } return Qt::Alignment(); } void QskLayoutConstraint::setLayoutAlignmentHint( QQuickItem* item, Qt::Alignment alignment ) { if ( auto control = qskControlCast( item ) ) { control->setLayoutAlignmentHint( alignment ); } else if ( item ) { QVariant v; if ( alignment ) v.setValue( alignment ); item->setProperty( s_alignmentProperty, v ); } } bool QskLayoutConstraint::retainSizeWhenHidden( const QQuickItem* item ) { if ( auto control = qskControlCast( item ) ) { return control->layoutHints() & QskControl::RetainSizeWhenHidden; } else if ( item ) { const QVariant v = item->property( s_retainSizeWhenHiddenProperty ); if ( v.canConvert< bool >() ) return v.toBool(); } return false; } void QskLayoutConstraint::setRetainSizeWhenHidden( QQuickItem* item, bool on ) { if ( auto control = qskControlCast( item ) ) { control->setLayoutHint( QskControl::RetainSizeWhenHidden, on ); } else if ( item ) { QVariant v; if ( on ) v.setValue( on ); item->setProperty( s_retainSizeWhenHiddenProperty, v ); } }