diff --git a/playground/shapes/Gradient.cpp b/playground/shapes/Gradient.cpp new file mode 100644 index 00000000..4c83c47c --- /dev/null +++ b/playground/shapes/Gradient.cpp @@ -0,0 +1,238 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "Gradient.h" +#include + +bool Gradient::operator==( const Gradient& other ) const noexcept +{ + if ( m_type != other.m_type ) + return false; + +#if 0 + if ( m_type == QGradient::NoGradient ) + return true; +#endif + + return ( m_spread == other.m_spread ) + && ( m_values[0] == other.m_values[0] ) + && ( m_values[1] == other.m_values[1] ) + && ( m_values[2] == other.m_values[2] ) + && ( m_values[3] == other.m_values[3] ) + && ( m_values[4] == other.m_values[4] ) + && ( m_values[5] == other.m_values[5] ) + && ( m_stops == other.m_stops ); +} + +void Gradient::setStops( QGradient::Preset preset ) +{ + setStops( QGradient( preset ).stops() ); +} + +void Gradient::setStops( const QGradientStops &stops ) +{ + m_stops.resize( stops.count() ); + + for ( int i = 0; i < stops.count(); i++ ) + m_stops[i] = { stops[i].first, stops[i].second }; +} + +void Gradient::setStops( const QVector< QskGradientStop >& stops ) +{ + m_stops = stops; +} + +void Gradient::setStops( const QColor& c1, const QColor& c2 ) +{ + m_stops.resize( 2 ); + m_stops[0] = QskGradientStop( 0.0, c1 ); + m_stops[1] = QskGradientStop( 1.0, c2 ); +} + +QGradientStops Gradient::qtStops() const +{ + QGradientStops qstops; + qstops.reserve( m_stops.count() ); + + for ( const auto& stop : m_stops ) + qstops += { stop.position(), stop.color() }; + + return qstops; +} + +void Gradient::setSpread( QGradient::Spread spread ) +{ + m_spread = spread; +} + +bool Gradient::isMonochrome() const +{ + if ( m_type != QGradient::NoGradient ) + { + for ( int i = 1; i < m_stops.size(); i++ ) + { + if ( m_stops[ i ].color() != m_stops[ 0 ].color() ) + return false; + } + } + + return true; +} + +bool Gradient::isVisible() const +{ + if ( m_type != QGradient::NoGradient ) + { + for ( const auto& stop : m_stops ) + { + const auto& c = stop.color(); + if ( c.isValid() && c.alpha() > 0 ) + return true; + } + } + + return false; +} + +#ifndef QT_NO_DEBUG_STREAM + +#include + +QDebug operator<<( QDebug debug, const Gradient& gradient ) +{ + QDebugStateSaver saver( debug ); + debug.nospace(); + + debug << "Gradient"; + + if ( !gradient.isValid() ) + { + debug << "()"; + } + else + { + debug << "( "; + + debug << gradient.type(); + // ... + + debug << " )"; + } + + return debug; +} + +#endif + +LinearGradient& Gradient::asLinearGradient() +{ + assert( m_type == QGradient::LinearGradient ); + return *reinterpret_cast< LinearGradient* >( this ); +} + +const LinearGradient& Gradient::asLinearGradient() const +{ + assert( m_type == QGradient::LinearGradient ); + return *reinterpret_cast< const LinearGradient* >( this ); +} + +RadialGradient& Gradient::asRadialGradient() +{ + assert( m_type == QGradient::RadialGradient ); + return *reinterpret_cast< RadialGradient* >( this ); +} + +const RadialGradient& Gradient::asRadialGradient() const +{ + assert( m_type == QGradient::RadialGradient ); + return *reinterpret_cast< const RadialGradient* >( this ); +} + +ConicGradient& Gradient::asConicGradient() +{ + assert( m_type == QGradient::ConicalGradient ); + return *reinterpret_cast< ConicGradient* >( this ); +} + +const ConicGradient& Gradient::asConicGradient() const +{ + assert( m_type == QGradient::ConicalGradient ); + return *reinterpret_cast< const ConicGradient* >( this ); +} + +void LinearGradient::setStart( const QPointF& pos ) noexcept +{ + m_values[0] = pos.x(); + m_values[1] = pos.y(); +} + +void LinearGradient::setStart( qreal x, qreal y ) noexcept +{ + m_values[0] = x; + m_values[1] = y; +} + +void LinearGradient::setStop( const QPointF& pos ) noexcept +{ + m_values[2] = pos.x(); + m_values[3] = pos.y(); +} + +void LinearGradient::setStop( qreal x, qreal y ) noexcept +{ + m_values[2] = x; + m_values[3] = y; +} + +void RadialGradient::setCenter( const QPointF& center ) noexcept +{ + m_values[0] = center.x(); + m_values[1] = center.y(); +} + +void RadialGradient::setCenter( qreal x, qreal y ) noexcept +{ + m_values[0] = x; + m_values[1] = y; +} + +void RadialGradient::setFocalPoint( const QPointF& focalPoint ) noexcept +{ + m_values[3] = focalPoint.x(); + m_values[4] = focalPoint.y(); +} + +void RadialGradient::setFocalPoint( qreal x, qreal y ) noexcept +{ + m_values[3] = x; + m_values[4] = y; +} + +constexpr void RadialGradient::setCenterRadius( qreal radius ) noexcept +{ + m_values[2] = radius; +} + +constexpr void RadialGradient::setFocalRadius( qreal radius ) noexcept +{ + m_values[5] = radius; +} + +void ConicGradient::setCenter( const QPointF& center ) noexcept +{ + m_values[0] = center.x(); + m_values[1] = center.y(); +} + +void ConicGradient::setCenter( qreal x, qreal y ) noexcept +{ + m_values[0] = x; + m_values[1] = y; +} + +void ConicGradient::setDegrees( qreal degrees ) noexcept +{ + m_values[2] = degrees; +} diff --git a/playground/shapes/Gradient.h b/playground/shapes/Gradient.h new file mode 100644 index 00000000..56e464ae --- /dev/null +++ b/playground/shapes/Gradient.h @@ -0,0 +1,290 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +/* + These definitions will be somehow adjusted and merged with QskGradient + and moved to the lib then. + */ + +#include +#include +#include +#include + +class LinearGradient; +class RadialGradient; +class ConicGradient; + +class Gradient +{ + public: + constexpr Gradient() noexcept = default; + Gradient( const Gradient& ) noexcept = default; + + bool operator==( const Gradient& ) const noexcept; + bool operator!=( const Gradient& ) const noexcept; + + constexpr QGradient::Type type() const noexcept; + + constexpr bool isValid() const noexcept; + + bool isMonochrome() const; + bool isVisible() const; + + void setStops( const QVector< QskGradientStop >& ); + const QVector< QskGradientStop >& stops() const noexcept; + + void setStops( QGradient::Preset ); + void setStops( const QGradientStops& ); + void setStops( const QColor&, const QColor& ); + QGradientStops qtStops() const; + + void setSpread( QGradient::Spread ); + constexpr QGradient::Spread spread() const noexcept; + + LinearGradient& asLinearGradient(); + const LinearGradient& asLinearGradient() const; + + RadialGradient& asRadialGradient(); + const RadialGradient& asRadialGradient() const; + + ConicGradient& asConicGradient(); + const ConicGradient& asConicGradient() const; + + private: + friend class LinearGradient; + friend class RadialGradient; + friend class ConicGradient; + + constexpr Gradient( QGradient::Type ) noexcept; + constexpr Gradient( QGradient::Type, + qreal, qreal, qreal, qreal, qreal, qreal ) noexcept; + + QVector< QskGradientStop > m_stops; + + /* + Linear: x1, y1, x2, y2, n/a, n/a + Radial: centerX, centerY, centerRadius, focalX, focalY, focalRadius + Conic: centerX, centerY, degrees, n/a, n/a, n/a + */ + qreal m_values[6] = {}; + + QGradient::Type m_type = QGradient::NoGradient; + QGradient::Spread m_spread = QGradient::PadSpread; +}; + +class LinearGradient : public Gradient +{ + public: + constexpr LinearGradient() noexcept; + constexpr LinearGradient( const QPointF&, const QPointF& ) noexcept; + constexpr LinearGradient( qreal x1, qreal y1, qreal x2, qreal y2 ) noexcept; + + void setStart(const QPointF& start) noexcept; + void setStart( qreal x, qreal y ) noexcept; + + void setStop(const QPointF& start) noexcept; + void setStop( qreal x, qreal y ) noexcept; + + constexpr QPointF start() const noexcept; + constexpr QPointF stop() const noexcept; +}; + +#ifndef QT_NO_DEBUG_STREAM + +class QDebug; +QDebug operator<<( QDebug, const Gradient& ); + +#endif + +class RadialGradient : public Gradient +{ + public: + constexpr RadialGradient() noexcept; + + constexpr RadialGradient( const QPointF& center, qreal radius ) noexcept; + constexpr RadialGradient( qreal cx, qreal cy, qreal radius ) noexcept; + + constexpr RadialGradient( const QPointF& center, qreal centerRadius, + const QPointF& focalPoint, qreal focalRadius = 0.0 ) noexcept; + + constexpr RadialGradient(qreal cx, qreal cy, qreal centerRadius, + qreal fx, qreal fy, qreal focalRadius = 0.0 ) noexcept; + + constexpr QPointF center() const noexcept; + void setCenter(const QPointF& center) noexcept; + void setCenter(qreal x, qreal y) noexcept; + + constexpr QPointF focalPoint() const noexcept; + void setFocalPoint( const QPointF& focalPoint ) noexcept; + void setFocalPoint( qreal x, qreal y ) noexcept; + + constexpr qreal centerRadius() const noexcept; + constexpr void setCenterRadius( qreal radius ) noexcept; + + constexpr qreal focalRadius() const noexcept; + constexpr void setFocalRadius( qreal radius ) noexcept; +}; + +class ConicGradient : public Gradient +{ + public: + constexpr ConicGradient() noexcept; + constexpr ConicGradient( const QPointF&, qreal degrees ) noexcept; + constexpr ConicGradient( qreal cx, qreal cy, qreal degrees ) noexcept; + + constexpr QPointF center() const noexcept; + void setCenter(const QPointF& center) noexcept; + void setCenter(qreal x, qreal y) noexcept; + + constexpr qreal degrees() const noexcept; + void setDegrees(qreal ) noexcept; +}; + +constexpr Gradient::Gradient( QGradient::Type type ) noexcept + : m_type( type ) +{ +} + +constexpr Gradient::Gradient( QGradient::Type type, + qreal v1, qreal v2, qreal v3, + qreal v4 = 0.0, qreal v5 = 0.0, qreal v6 = 0.0 ) noexcept + : m_values{ v1, v2, v3, v4, v5, v6 } + , m_type( type ) +{ +} + +inline bool Gradient::operator!=( const Gradient& other ) const noexcept +{ + return !( *this == other ); +} + +inline constexpr QGradient::Type Gradient::type() const noexcept +{ + return m_type; +} + +constexpr bool Gradient::isValid() const noexcept +{ + return m_type != QGradient::NoGradient; +} + +inline const QVector< QskGradientStop >& Gradient::stops() const noexcept +{ + return m_stops; +} + +inline constexpr QGradient::Spread Gradient::spread() const noexcept +{ + return m_spread; +} + +inline constexpr LinearGradient::LinearGradient() noexcept + : Gradient( QGradient::LinearGradient, 0.0, 0.0, 1.0, 1.0 ) +{ +} + +inline constexpr LinearGradient::LinearGradient( + const QPointF& start, const QPointF& stop ) noexcept + : LinearGradient( start.x(), start.y(), stop.x(), stop.y() ) +{ +} + +inline constexpr LinearGradient::LinearGradient( + qreal x1, qreal y1, qreal x2, qreal y2 ) noexcept + : Gradient( QGradient::LinearGradient, x1, y1, x2, y2 ) +{ +} + +inline constexpr QPointF LinearGradient::start() const noexcept +{ + return QPointF( m_values[0], m_values[1] ); +} + +inline constexpr QPointF LinearGradient::stop() const noexcept +{ + return QPointF( m_values[2], m_values[3] ); +} + +inline constexpr RadialGradient::RadialGradient() noexcept + : Gradient( QGradient::RadialGradient, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0 ) +{ +} + +inline constexpr RadialGradient::RadialGradient( + const QPointF& center, qreal radius, + const QPointF& focalPoint, qreal focalRadius ) noexcept + : RadialGradient( center.x(), center.y(), radius, + focalPoint.x(), focalPoint.y(), focalRadius ) +{ +} + +inline constexpr RadialGradient::RadialGradient( + const QPointF& center, qreal radius ) noexcept + : RadialGradient( center.x(), center.y(), radius ) +{ +} + +inline constexpr RadialGradient::RadialGradient( + qreal cx, qreal cy, qreal radius ) noexcept + : RadialGradient( cx, cy, radius, cx, cy, 0.0 ) +{ +} + +inline constexpr RadialGradient::RadialGradient( + qreal cx, qreal cy, qreal radius, + qreal fx, qreal fy, qreal focalRadius ) noexcept + : Gradient( QGradient::RadialGradient, cx, cy, radius, fx, fy, focalRadius ) +{ +} + +inline constexpr QPointF RadialGradient::center() const noexcept +{ + return QPointF( m_values[0], m_values[1] ); +} + +inline constexpr qreal RadialGradient::centerRadius() const noexcept +{ + return m_values[2]; +} + +inline constexpr QPointF RadialGradient::focalPoint() const noexcept +{ + return QPointF( m_values[3], m_values[4] ); +} + +inline constexpr qreal RadialGradient::focalRadius() const noexcept +{ + return m_values[5]; +} + +inline constexpr ConicGradient::ConicGradient() noexcept + : Gradient( QGradient::ConicalGradient, 0.0, 0.0, 0.0 ) +{ +} + +inline constexpr ConicGradient::ConicGradient( + const QPointF& center, qreal degrees ) noexcept + : ConicGradient( center.x(), center.y(), degrees ) +{ +} + +inline constexpr ConicGradient::ConicGradient( + qreal cx, qreal cy, qreal degrees ) noexcept + : Gradient( QGradient::ConicalGradient, cx, cy, degrees ) +{ +} + +constexpr QPointF ConicGradient::center() const noexcept +{ + return QPointF( m_values[0], m_values[1] ); +} + +constexpr qreal ConicGradient::degrees() const noexcept +{ + return m_values[2]; +} diff --git a/playground/shapes/ShapeItem.cpp b/playground/shapes/ShapeItem.cpp index d0c3f7a0..c5d7bd73 100644 --- a/playground/shapes/ShapeItem.cpp +++ b/playground/shapes/ShapeItem.cpp @@ -42,10 +42,93 @@ static inline qreal effectivePenWidth( return width; } +namespace +{ + class ShapeNode : public QskShapeNode + { + public: + void updateShape( const QPainterPath& path, + const QTransform& transform, const QRectF& rect, const Gradient& gradient ) + { + if ( gradient.isMonochrome() ) + { + updateNode( path, transform, gradient.stops().first().color() ); + return; + } + + /* + Stupid code to map Gradient -> QGradient + Can be removed once Gradient has been merged into QskGradient. + */ + + switch( static_cast( gradient.type() ) ) + { + case QGradient::LinearGradient: + { + const auto& g = gradient.asLinearGradient(); + + const qreal x1 = rect.left() + g.start().x() * rect.width(); + const qreal y1 = rect.top() + g.start().y() * rect.height(); + + const qreal x2 = rect.left() + g.stop().x() * rect.width(); + const qreal y2 = rect.top() + g.stop().y() * rect.height(); + +#if 0 + QTransform t2( rect.width(), 0, 0, rect.height(), rect.x(), rect.y()); +#endif + QLinearGradient qgradient( x1, y1, x2, y2 ); + qgradient.setSpread( g.spread() ); + qgradient.setStops( g.qtStops() ); + + updateNode( path, transform, &qgradient ); + + break; + } + case QGradient::RadialGradient: + { + const auto& g = gradient.asRadialGradient(); + + const qreal x = rect.left() + g.center().x() * rect.width(); + const qreal y = rect.top() + g.center().y() * rect.height(); + const qreal r = g.centerRadius() * qMin( rect.width(), rect.height() ); + + const qreal fx = rect.left() + g.focalPoint().x() * rect.width(); + const qreal fy = rect.top() + g.focalPoint().y() * rect.height(); + const qreal fr = g.focalRadius() * qMin( rect.width(), rect.height() ); + + QRadialGradient qgradient( x, y, r, fx, fy, fr ); + qgradient.setSpread( g.spread() ); + qgradient.setStops( g.qtStops() ); + + updateNode( path, transform, &qgradient ); + + break; + } + case QGradient::ConicalGradient: + { + const auto& g = gradient.asConicGradient(); + + const qreal x = rect.left() + g.center().x() * rect.width(); + const qreal y = rect.top() + g.center().y() * rect.height(); + + QConicalGradient qgradient( x, y, g.degrees() ); + //qgradient.setSpread( g.spread() ); + qgradient.setStops( g.qtStops() ); + + updateNode( path, transform, &qgradient ); + + break; + } + } + } + }; +} + ShapeItem::ShapeItem( QQuickItem* parent ) : QskControl( parent ) { setMargins( 20 ); + setSizePolicy( QskSizePolicy::Ignored, QskSizePolicy::Ignored ); } ShapeItem::~ShapeItem() @@ -66,25 +149,18 @@ QPen ShapeItem::pen() const return m_pen; } -void ShapeItem::setGradient( const QColor& c1, const QColor& c2 ) +void ShapeItem::setGradient( const Gradient& gradient ) { - if ( c1 != m_fillColor[0] || c2 != m_fillColor[1] ) + if ( gradient != m_gradient ) { - m_fillColor[0] = c1; - m_fillColor[1] = c2; - + m_gradient = gradient; update(); } } -void ShapeItem::setGradient( QGradient::Preset preset ) +const Gradient& ShapeItem::gradient() const { - const auto stops = QGradient( preset ).stops(); - if ( !stops.isEmpty() ) - { - // gradients with more than 2 clors do not work TODO ... - setGradient( stops.first().second, stops.last().second ); - } + return m_gradient; } void ShapeItem::setPath( const QPainterPath& path ) @@ -112,7 +188,7 @@ void ShapeItem::updateNode( QSGNode* parentNode ) const auto rect = contentsRect(); const auto pathRect = m_path.controlPointRect(); - auto fillNode = static_cast< QskShapeNode* >( + auto fillNode = static_cast< ShapeNode* >( QskSGNode::findChildNode( parentNode, FillRole ) ); auto borderNode = static_cast< QskStrokeNode* >( @@ -126,11 +202,11 @@ void ShapeItem::updateNode( QSGNode* parentNode ) return; } - if ( ::isVisible( m_fillColor[0] ) || ::isVisible( m_fillColor[1] ) ) + if ( m_gradient.isVisible() ) { if ( fillNode == nullptr ) { - fillNode = new QskShapeNode; + fillNode = new ShapeNode; QskSGNode::setNodeRole( fillNode, FillRole ); parentNode->prependChildNode( fillNode ); @@ -144,21 +220,7 @@ void ShapeItem::updateNode( QSGNode* parentNode ) } const auto transform = ::transformForRects( pathRect, fillRect ); - - if ( m_fillColor[0] != m_fillColor[1] ) - { - QLinearGradient gradient; - gradient.setStart( rect.topLeft() ); - gradient.setFinalStop( rect.bottomRight() ); - gradient.setColorAt( 0.0, m_fillColor[0] ); - gradient.setColorAt( 1.0, m_fillColor[1] ); - - fillNode->updateNode( m_path, transform, &gradient ); - } - else - { - fillNode->updateNode( m_path, transform, m_fillColor[0] ); - } + fillNode->updateShape( m_path, transform, fillRect, m_gradient ); } else { diff --git a/playground/shapes/ShapeItem.h b/playground/shapes/ShapeItem.h index 661a64d7..16e05a25 100644 --- a/playground/shapes/ShapeItem.h +++ b/playground/shapes/ShapeItem.h @@ -5,10 +5,12 @@ #pragma once +#include "Gradient.h" #include #include #include -#include + +class Gradient; class ShapeItem : public QskControl { @@ -21,8 +23,8 @@ class ShapeItem : public QskControl void setPen( const QPen& ); QPen pen() const; - void setGradient( const QColor&, const QColor& ); - void setGradient( QGradient::Preset ); + void setGradient( const Gradient& ); + const Gradient& gradient() const; void setPath( const QPainterPath& ); QPainterPath path() const; @@ -34,6 +36,6 @@ class ShapeItem : public QskControl QPainterPath scaledPath( const QRectF& ) const; QPen m_pen; - QColor m_fillColor[2]; + Gradient m_gradient; QPainterPath m_path; }; diff --git a/playground/shapes/main.cpp b/playground/shapes/main.cpp index 613425d8..192fefa0 100644 --- a/playground/shapes/main.cpp +++ b/playground/shapes/main.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,68 @@ namespace setColor( QskRgb::toTransparent( color(), alpha ) ); } }; + + class Box : public QskLinearBox + { + public: + Box( QQuickItem* parent = nullptr ) + : QskLinearBox( Qt::Horizontal, 2, parent ) + { + { + auto shapeItem = new ShapeItem( this ); + + shapeItem->setPath( path( SkinnyShapeFactory::Hexagon ) ); + shapeItem->setPen( Pen( QColorConstants::Svg::indigo ) ); + + LinearGradient gradient( 0.0, 0.0, 0.2, 0.5 ); + gradient.setSpread( QGradient::ReflectSpread ); + gradient.setStops( QGradient::PhoenixStart ); + + shapeItem->setGradient( gradient ); + } + + { + auto shapeItem = new ShapeItem( this ); + + shapeItem->setPath( path( SkinnyShapeFactory::Ellipse ) ); + + RadialGradient gradient( 0.5, 0.5, 0.5 ); + gradient.setSpread( QGradient::RepeatSpread ); + +#if 1 + QVector< QskGradientStop > stops; + + stops += QskGradientStop( 0.0, Qt::green ); + stops += QskGradientStop( 0.2, Qt::green ); + stops += QskGradientStop( 0.201, Qt::red ); + stops += QskGradientStop( 0.4, Qt::red ); + stops += QskGradientStop( 0.401, Qt::yellow ); + stops += QskGradientStop( 0.6, Qt::yellow ); + stops += QskGradientStop( 0.601, Qt::cyan ); + stops += QskGradientStop( 0.8, Qt::cyan ); + stops += QskGradientStop( 0.801, Qt::darkCyan ); + stops += QskGradientStop( 1.0, Qt::darkCyan ); + + gradient.setStops( stops ); +#else + gradient.setStops( QGradient::DirtyBeauty ); +#endif + + shapeItem->setGradient( gradient ); + } + + { + auto shapeItem = new ShapeItem( this ); + + shapeItem->setPath( path( SkinnyShapeFactory::Arc ) ); + + ConicGradient gradient( 0.5, 0.5, -60.0 ); + gradient.setStops( QGradient::BurningSpring ); + + shapeItem->setGradient( gradient ); + } + } + }; } int main( int argc, char* argv[] ) @@ -56,16 +119,8 @@ int main( int argc, char* argv[] ) QskWindow window; window.setColor( Qt::gray ); - - auto shapeItem = new ShapeItem(); - - shapeItem->setPath( path( SkinnyShapeFactory::Hexagon ) ); - - shapeItem->setPen( Pen( QColorConstants::Svg::indigo ) ); - shapeItem->setGradient( QGradient::PhoenixStart ); - - window.addItem( shapeItem ); - window.resize( 600, 600 ); + window.addItem( new Box() ); + window.resize( 800, 600 ); window.show(); return app.exec(); diff --git a/playground/shapes/shapes.pro b/playground/shapes/shapes.pro index bbf1741a..fb2d05f5 100644 --- a/playground/shapes/shapes.pro +++ b/playground/shapes/shapes.pro @@ -1,8 +1,10 @@ CONFIG += qskexample HEADERS += \ + Gradient.h \ ShapeItem.h \ SOURCES += \ + Gradient.cpp \ ShapeItem.cpp \ main.cpp